@bjlee2024/claude-mem 13.4.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/.agents/plugins/marketplace.json +20 -0
- package/.codex-plugin/plugin.json +46 -0
- package/LICENSE +202 -0
- package/README.md +419 -0
- package/dist/npx-cli/index.js +10001 -0
- package/dist/opencode-plugin/index.js +67 -0
- package/openclaw/Dockerfile.e2e +46 -0
- package/openclaw/SKILL.md +462 -0
- package/openclaw/TESTING.md +279 -0
- package/openclaw/dist/index.js +15 -0
- package/openclaw/e2e-verify.sh +222 -0
- package/openclaw/install.sh +1653 -0
- package/openclaw/openclaw.plugin.json +98 -0
- package/openclaw/package.json +21 -0
- package/openclaw/src/index.test.ts +1124 -0
- package/openclaw/src/index.ts +1092 -0
- package/openclaw/test-e2e.sh +40 -0
- package/openclaw/test-install.sh +2086 -0
- package/openclaw/test-sse-consumer.js +98 -0
- package/openclaw/tsconfig.json +26 -0
- package/package.json +211 -0
- package/plugin/.claude-plugin/plugin.json +24 -0
- package/plugin/.codex-plugin/plugin.json +46 -0
- package/plugin/.mcp.json +12 -0
- package/plugin/hooks/bugfixes-2026-01-10.md +92 -0
- package/plugin/hooks/codex-hooks.json +74 -0
- package/plugin/hooks/hooks.json +87 -0
- package/plugin/modes/code--ar.json +24 -0
- package/plugin/modes/code--bn.json +24 -0
- package/plugin/modes/code--chill.json +8 -0
- package/plugin/modes/code--cs.json +24 -0
- package/plugin/modes/code--da.json +24 -0
- package/plugin/modes/code--de.json +24 -0
- package/plugin/modes/code--el.json +24 -0
- package/plugin/modes/code--es.json +24 -0
- package/plugin/modes/code--fi.json +24 -0
- package/plugin/modes/code--fr.json +24 -0
- package/plugin/modes/code--he.json +24 -0
- package/plugin/modes/code--hi.json +24 -0
- package/plugin/modes/code--hu.json +24 -0
- package/plugin/modes/code--id.json +24 -0
- package/plugin/modes/code--it.json +24 -0
- package/plugin/modes/code--ja.json +24 -0
- package/plugin/modes/code--ko.json +24 -0
- package/plugin/modes/code--nl.json +24 -0
- package/plugin/modes/code--no.json +24 -0
- package/plugin/modes/code--pl.json +24 -0
- package/plugin/modes/code--pt-br.json +24 -0
- package/plugin/modes/code--ro.json +24 -0
- package/plugin/modes/code--ru.json +24 -0
- package/plugin/modes/code--sv.json +24 -0
- package/plugin/modes/code--th.json +24 -0
- package/plugin/modes/code--tr.json +24 -0
- package/plugin/modes/code--uk.json +24 -0
- package/plugin/modes/code--ur.json +25 -0
- package/plugin/modes/code--vi.json +24 -0
- package/plugin/modes/code--zh.json +24 -0
- package/plugin/modes/code.json +139 -0
- package/plugin/modes/email-investigation.json +120 -0
- package/plugin/modes/law-study--chill.json +7 -0
- package/plugin/modes/law-study-CLAUDE.md +85 -0
- package/plugin/modes/law-study.json +120 -0
- package/plugin/modes/meme-tokens.json +125 -0
- package/plugin/package.json +46 -0
- package/plugin/scripts/bun-runner.js +216 -0
- package/plugin/scripts/context-generator.cjs +795 -0
- package/plugin/scripts/mcp-server.cjs +239 -0
- package/plugin/scripts/server-beta-service.cjs +9856 -0
- package/plugin/scripts/statusline-counts.js +40 -0
- package/plugin/scripts/version-check.js +69 -0
- package/plugin/scripts/worker-cli.js +19 -0
- package/plugin/scripts/worker-service.cjs +2368 -0
- package/plugin/scripts/worker-wrapper.cjs +2 -0
- package/plugin/skills/babysit/SKILL.md +87 -0
- package/plugin/skills/design-is/SKILL.md +312 -0
- package/plugin/skills/do/SKILL.md +45 -0
- package/plugin/skills/how-it-works/SKILL.md +22 -0
- package/plugin/skills/how-it-works/onboarding-explainer.md +17 -0
- package/plugin/skills/knowledge-agent/SKILL.md +80 -0
- package/plugin/skills/learn-codebase/SKILL.md +21 -0
- package/plugin/skills/make-plan/SKILL.md +67 -0
- package/plugin/skills/mem-search/SKILL.md +131 -0
- package/plugin/skills/oh-my-issues/SKILL.md +226 -0
- package/plugin/skills/pathfinder/SKILL.md +111 -0
- package/plugin/skills/smart-explore/SKILL.md +190 -0
- package/plugin/skills/timeline-report/SKILL.md +211 -0
- package/plugin/skills/version-bump/SKILL.md +68 -0
- package/plugin/skills/version-bump/scripts/generate_changelog.js +34 -0
- package/plugin/skills/weekly-digests/SKILL.md +262 -0
- package/plugin/skills/wowerpoint/SKILL.md +205 -0
- package/plugin/ui/assets/fonts/monaspace-radon-var.woff +0 -0
- package/plugin/ui/assets/fonts/monaspace-radon-var.woff2 +0 -0
- package/plugin/ui/claude-mem-logo-for-dark-mode.webp +0 -0
- package/plugin/ui/claude-mem-logo-stylized.png +0 -0
- package/plugin/ui/claude-mem-logomark.webp +0 -0
- package/plugin/ui/icon-thick-completed.svg +8 -0
- package/plugin/ui/icon-thick-investigated.svg +8 -0
- package/plugin/ui/icon-thick-learned.svg +12 -0
- package/plugin/ui/icon-thick-next-steps.svg +8 -0
- package/plugin/ui/viewer-bundle.js +65 -0
- package/plugin/ui/viewer.html +3145 -0
|
@@ -0,0 +1,1653 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
readonly MIN_BUN_VERSION="1.1.14"
|
|
5
|
+
readonly INSTALLER_VERSION="1.0.0"
|
|
6
|
+
|
|
7
|
+
NON_INTERACTIVE=""
|
|
8
|
+
CLI_PROVIDER=""
|
|
9
|
+
CLI_API_KEY=""
|
|
10
|
+
UPGRADE_MODE=""
|
|
11
|
+
CLI_BRANCH=""
|
|
12
|
+
|
|
13
|
+
while [[ $# -gt 0 ]]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--non-interactive)
|
|
16
|
+
NON_INTERACTIVE="true"
|
|
17
|
+
shift
|
|
18
|
+
;;
|
|
19
|
+
--upgrade)
|
|
20
|
+
UPGRADE_MODE="true"
|
|
21
|
+
shift
|
|
22
|
+
;;
|
|
23
|
+
--branch=*)
|
|
24
|
+
CLI_BRANCH="${1#--branch=}"
|
|
25
|
+
shift
|
|
26
|
+
;;
|
|
27
|
+
--branch)
|
|
28
|
+
CLI_BRANCH="${2:-}"
|
|
29
|
+
shift 2
|
|
30
|
+
;;
|
|
31
|
+
--provider=*)
|
|
32
|
+
CLI_PROVIDER="${1#--provider=}"
|
|
33
|
+
shift
|
|
34
|
+
;;
|
|
35
|
+
--provider)
|
|
36
|
+
CLI_PROVIDER="${2:-}"
|
|
37
|
+
shift 2
|
|
38
|
+
;;
|
|
39
|
+
--api-key=*)
|
|
40
|
+
CLI_API_KEY="${1#--api-key=}"
|
|
41
|
+
shift
|
|
42
|
+
;;
|
|
43
|
+
--api-key)
|
|
44
|
+
CLI_API_KEY="${2:-}"
|
|
45
|
+
shift 2
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
TTY_FD=0
|
|
54
|
+
|
|
55
|
+
setup_tty() {
|
|
56
|
+
if [[ -t 0 ]]; then
|
|
57
|
+
TTY_FD=0
|
|
58
|
+
elif [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
59
|
+
TTY_FD=0
|
|
60
|
+
elif [[ -r /dev/tty ]]; then
|
|
61
|
+
exec 3</dev/tty
|
|
62
|
+
TTY_FD=3
|
|
63
|
+
else
|
|
64
|
+
echo "Error: No terminal available for interactive prompts." >&2
|
|
65
|
+
echo "Use --non-interactive or run directly: bash install.sh" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then
|
|
71
|
+
readonly COLOR_RED='\033[0;31m'
|
|
72
|
+
readonly COLOR_GREEN='\033[0;32m'
|
|
73
|
+
readonly COLOR_YELLOW='\033[0;33m'
|
|
74
|
+
readonly COLOR_BLUE='\033[0;34m'
|
|
75
|
+
readonly COLOR_MAGENTA='\033[0;35m'
|
|
76
|
+
readonly COLOR_CYAN='\033[0;36m'
|
|
77
|
+
readonly COLOR_BOLD='\033[1m'
|
|
78
|
+
readonly COLOR_RESET='\033[0m'
|
|
79
|
+
else
|
|
80
|
+
readonly COLOR_RED=''
|
|
81
|
+
readonly COLOR_GREEN=''
|
|
82
|
+
readonly COLOR_YELLOW=''
|
|
83
|
+
readonly COLOR_BLUE=''
|
|
84
|
+
readonly COLOR_MAGENTA=''
|
|
85
|
+
readonly COLOR_CYAN=''
|
|
86
|
+
readonly COLOR_BOLD=''
|
|
87
|
+
readonly COLOR_RESET=''
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
info() { echo -e "${COLOR_BLUE}ℹ${COLOR_RESET} $*"; }
|
|
91
|
+
success() { echo -e "${COLOR_GREEN}✓${COLOR_RESET} $*"; }
|
|
92
|
+
warn() { echo -e "${COLOR_YELLOW}⚠${COLOR_RESET} $*"; }
|
|
93
|
+
error() { echo -e "${COLOR_RED}✗${COLOR_RESET} $*" >&2; }
|
|
94
|
+
|
|
95
|
+
prompt_user() {
|
|
96
|
+
if [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
97
|
+
error "Cannot prompt in non-interactive mode: $*"
|
|
98
|
+
return 1
|
|
99
|
+
fi
|
|
100
|
+
echo -en "${COLOR_CYAN}?${COLOR_RESET} $* "
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
read_tty() {
|
|
104
|
+
read "$@" <&"$TTY_FD"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
CLEANUP_DIRS=()
|
|
108
|
+
|
|
109
|
+
register_cleanup_dir() {
|
|
110
|
+
CLEANUP_DIRS+=("$1")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
cleanup_on_exit() {
|
|
114
|
+
local exit_code=$?
|
|
115
|
+
for dir in "${CLEANUP_DIRS[@]+"${CLEANUP_DIRS[@]}"}"; do
|
|
116
|
+
if [[ -d "$dir" ]]; then
|
|
117
|
+
rm -rf "$dir"
|
|
118
|
+
fi
|
|
119
|
+
done
|
|
120
|
+
if [[ $exit_code -ne 0 ]]; then
|
|
121
|
+
echo "" >&2
|
|
122
|
+
error "Installation failed (exit code: ${exit_code})"
|
|
123
|
+
error "Any temporary files have been cleaned up."
|
|
124
|
+
error "Fix the issue above and re-run the installer."
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
trap cleanup_on_exit EXIT
|
|
129
|
+
|
|
130
|
+
check_git() {
|
|
131
|
+
if command -v git &>/dev/null; then
|
|
132
|
+
return 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
error "git is not installed"
|
|
136
|
+
echo "" >&2
|
|
137
|
+
case "${PLATFORM:-}" in
|
|
138
|
+
macos)
|
|
139
|
+
error "Install git on macOS with:"
|
|
140
|
+
error " xcode-select --install"
|
|
141
|
+
error " # or: brew install git"
|
|
142
|
+
;;
|
|
143
|
+
linux)
|
|
144
|
+
error "Install git on Linux with:"
|
|
145
|
+
error " sudo apt install git # Debian/Ubuntu"
|
|
146
|
+
error " sudo dnf install git # Fedora/RHEL"
|
|
147
|
+
error " sudo pacman -S git # Arch"
|
|
148
|
+
;;
|
|
149
|
+
*)
|
|
150
|
+
error "Please install git and re-run this installer."
|
|
151
|
+
;;
|
|
152
|
+
esac
|
|
153
|
+
exit 1
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
check_port_37777() {
|
|
157
|
+
local port_in_use=""
|
|
158
|
+
|
|
159
|
+
if command -v lsof &>/dev/null; then
|
|
160
|
+
if lsof -i :37777 -sTCP:LISTEN &>/dev/null; then
|
|
161
|
+
port_in_use="true"
|
|
162
|
+
fi
|
|
163
|
+
elif command -v ss &>/dev/null; then
|
|
164
|
+
if ss -tlnp 2>/dev/null | grep -q ':37777 '; then
|
|
165
|
+
port_in_use="true"
|
|
166
|
+
fi
|
|
167
|
+
elif command -v curl &>/dev/null; then
|
|
168
|
+
local response
|
|
169
|
+
response="$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:37777/api/health" 2>/dev/null)" || true
|
|
170
|
+
if [[ "$response" == "200" ]]; then
|
|
171
|
+
port_in_use="true"
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if [[ "$port_in_use" == "true" ]]; then
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
return 1
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
is_claude_mem_installed() {
|
|
182
|
+
if find_claude_mem_install_dir 2>/dev/null; then
|
|
183
|
+
return 0
|
|
184
|
+
fi
|
|
185
|
+
return 1
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
ensure_jq_or_fallback() {
|
|
189
|
+
local json_file="$1"
|
|
190
|
+
shift
|
|
191
|
+
local jq_filter="$1"
|
|
192
|
+
shift
|
|
193
|
+
|
|
194
|
+
if command -v jq &>/dev/null; then
|
|
195
|
+
local tmp_file
|
|
196
|
+
tmp_file="$(mktemp)"
|
|
197
|
+
jq "$@" "$jq_filter" "$json_file" > "$tmp_file" && mv "$tmp_file" "$json_file"
|
|
198
|
+
return $?
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if command -v python3 &>/dev/null; then
|
|
202
|
+
:
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
warn "jq not found — using node for JSON manipulation"
|
|
206
|
+
return 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
parse_health_json() {
|
|
210
|
+
local raw_json="$1"
|
|
211
|
+
|
|
212
|
+
WORKER_VERSION=""
|
|
213
|
+
WORKER_AI_PROVIDER=""
|
|
214
|
+
WORKER_AI_AUTH_METHOD=""
|
|
215
|
+
WORKER_INITIALIZED=""
|
|
216
|
+
WORKER_REPORTED_PID=""
|
|
217
|
+
WORKER_UPTIME=""
|
|
218
|
+
|
|
219
|
+
if [[ -z "$raw_json" ]]; then
|
|
220
|
+
return 0
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
if command -v jq &>/dev/null; then
|
|
224
|
+
WORKER_VERSION="$(echo "$raw_json" | jq -r '.version // empty' 2>/dev/null)" || true
|
|
225
|
+
WORKER_AI_PROVIDER="$(echo "$raw_json" | jq -r '.ai.provider // empty' 2>/dev/null)" || true
|
|
226
|
+
WORKER_AI_AUTH_METHOD="$(echo "$raw_json" | jq -r '.ai.authMethod // empty' 2>/dev/null)" || true
|
|
227
|
+
WORKER_INITIALIZED="$(echo "$raw_json" | jq -r '.initialized // empty' 2>/dev/null)" || true
|
|
228
|
+
WORKER_REPORTED_PID="$(echo "$raw_json" | jq -r '.pid // empty' 2>/dev/null)" || true
|
|
229
|
+
WORKER_UPTIME="$(echo "$raw_json" | jq -r '.uptime // empty' 2>/dev/null)" || true
|
|
230
|
+
return 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if command -v python3 &>/dev/null; then
|
|
234
|
+
local parsed
|
|
235
|
+
parsed="$(INSTALLER_HEALTH_JSON="$raw_json" python3 -c "
|
|
236
|
+
import json, os, sys
|
|
237
|
+
try:
|
|
238
|
+
data = json.loads(os.environ['INSTALLER_HEALTH_JSON'])
|
|
239
|
+
ai = data.get('ai') or {}
|
|
240
|
+
fields = [
|
|
241
|
+
str(data.get('version', '')),
|
|
242
|
+
str(ai.get('provider', '')),
|
|
243
|
+
str(ai.get('authMethod', '')),
|
|
244
|
+
str(data.get('initialized', '')),
|
|
245
|
+
str(data.get('pid', '')),
|
|
246
|
+
str(data.get('uptime', '')),
|
|
247
|
+
]
|
|
248
|
+
sys.stdout.write('\n'.join(fields))
|
|
249
|
+
except Exception:
|
|
250
|
+
sys.stdout.write('\n\n\n\n\n')
|
|
251
|
+
" 2>/dev/null)" || true
|
|
252
|
+
|
|
253
|
+
if [[ -n "$parsed" ]]; then
|
|
254
|
+
local -a health_fields
|
|
255
|
+
IFS=$'\n' read -r -d '' -a health_fields <<< "$parsed" || true
|
|
256
|
+
WORKER_VERSION="${health_fields[0]:-}"
|
|
257
|
+
WORKER_AI_PROVIDER="${health_fields[1]:-}"
|
|
258
|
+
WORKER_AI_AUTH_METHOD="${health_fields[2]:-}"
|
|
259
|
+
WORKER_INITIALIZED="${health_fields[3]:-}"
|
|
260
|
+
WORKER_REPORTED_PID="${health_fields[4]:-}"
|
|
261
|
+
WORKER_UPTIME="${health_fields[5]:-}"
|
|
262
|
+
[[ "$WORKER_VERSION" == "None" ]] && WORKER_VERSION=""
|
|
263
|
+
[[ "$WORKER_AI_PROVIDER" == "None" ]] && WORKER_AI_PROVIDER=""
|
|
264
|
+
[[ "$WORKER_AI_AUTH_METHOD" == "None" ]] && WORKER_AI_AUTH_METHOD=""
|
|
265
|
+
[[ "$WORKER_INITIALIZED" == "None" ]] && WORKER_INITIALIZED=""
|
|
266
|
+
[[ "$WORKER_REPORTED_PID" == "None" ]] && WORKER_REPORTED_PID=""
|
|
267
|
+
[[ "$WORKER_UPTIME" == "None" ]] && WORKER_UPTIME=""
|
|
268
|
+
fi
|
|
269
|
+
return 0
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
local parsed
|
|
273
|
+
parsed="$(INSTALLER_HEALTH_JSON="$raw_json" node -e "
|
|
274
|
+
try {
|
|
275
|
+
const data = JSON.parse(process.env.INSTALLER_HEALTH_JSON);
|
|
276
|
+
const ai = data.ai || {};
|
|
277
|
+
const fields = [
|
|
278
|
+
data.version ?? '',
|
|
279
|
+
ai.provider ?? '',
|
|
280
|
+
ai.authMethod ?? '',
|
|
281
|
+
data.initialized != null ? String(data.initialized) : '',
|
|
282
|
+
data.pid != null ? String(data.pid) : '',
|
|
283
|
+
data.uptime != null ? String(data.uptime) : '',
|
|
284
|
+
];
|
|
285
|
+
process.stdout.write(fields.join('\n'));
|
|
286
|
+
} catch (e) {
|
|
287
|
+
process.stdout.write('\n\n\n\n\n');
|
|
288
|
+
}
|
|
289
|
+
" 2>/dev/null)" || true
|
|
290
|
+
|
|
291
|
+
if [[ -n "$parsed" ]]; then
|
|
292
|
+
local -a health_fields
|
|
293
|
+
IFS=$'\n' read -r -d '' -a health_fields <<< "$parsed" || true
|
|
294
|
+
WORKER_VERSION="${health_fields[0]:-}"
|
|
295
|
+
WORKER_AI_PROVIDER="${health_fields[1]:-}"
|
|
296
|
+
WORKER_AI_AUTH_METHOD="${health_fields[2]:-}"
|
|
297
|
+
WORKER_INITIALIZED="${health_fields[3]:-}"
|
|
298
|
+
WORKER_REPORTED_PID="${health_fields[4]:-}"
|
|
299
|
+
WORKER_UPTIME="${health_fields[5]:-}"
|
|
300
|
+
fi
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
format_uptime_ms() {
|
|
304
|
+
local ms="$1"
|
|
305
|
+
local secs=$((ms / 1000))
|
|
306
|
+
if (( secs >= 3600 )); then
|
|
307
|
+
echo "$((secs / 3600))h $((secs % 3600 / 60))m"
|
|
308
|
+
elif (( secs >= 60 )); then
|
|
309
|
+
echo "$((secs / 60))m $((secs % 60))s"
|
|
310
|
+
else
|
|
311
|
+
echo "${secs}s"
|
|
312
|
+
fi
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
print_banner() {
|
|
316
|
+
echo -e "${COLOR_MAGENTA}${COLOR_BOLD}"
|
|
317
|
+
cat << 'BANNER'
|
|
318
|
+
┌─────────────────────────────────────────┐
|
|
319
|
+
│ claude-mem × OpenClaw │
|
|
320
|
+
│ Persistent Memory Plugin Installer │
|
|
321
|
+
└─────────────────────────────────────────┘
|
|
322
|
+
BANNER
|
|
323
|
+
echo -e "${COLOR_RESET}"
|
|
324
|
+
info "Installer v${INSTALLER_VERSION}"
|
|
325
|
+
echo ""
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
PLATFORM=""
|
|
329
|
+
IS_WSL=""
|
|
330
|
+
|
|
331
|
+
detect_platform() {
|
|
332
|
+
local uname_out
|
|
333
|
+
uname_out="$(uname -s)"
|
|
334
|
+
|
|
335
|
+
case "${uname_out}" in
|
|
336
|
+
Darwin*)
|
|
337
|
+
PLATFORM="macos"
|
|
338
|
+
;;
|
|
339
|
+
Linux*)
|
|
340
|
+
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
341
|
+
PLATFORM="linux"
|
|
342
|
+
IS_WSL="true"
|
|
343
|
+
else
|
|
344
|
+
PLATFORM="linux"
|
|
345
|
+
fi
|
|
346
|
+
;;
|
|
347
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
348
|
+
PLATFORM="windows"
|
|
349
|
+
;;
|
|
350
|
+
*)
|
|
351
|
+
error "Unsupported platform: ${uname_out}"
|
|
352
|
+
exit 1
|
|
353
|
+
;;
|
|
354
|
+
esac
|
|
355
|
+
|
|
356
|
+
info "Detected platform: ${PLATFORM}${IS_WSL:+ (WSL)}"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
version_gte() {
|
|
360
|
+
local v1="$1" v2="$2"
|
|
361
|
+
local -a parts1 parts2
|
|
362
|
+
IFS='.' read -ra parts1 <<< "$v1"
|
|
363
|
+
IFS='.' read -ra parts2 <<< "$v2"
|
|
364
|
+
|
|
365
|
+
for i in 0 1 2; do
|
|
366
|
+
local p1="${parts1[$i]:-0}"
|
|
367
|
+
local p2="${parts2[$i]:-0}"
|
|
368
|
+
if (( p1 > p2 )); then return 0; fi
|
|
369
|
+
if (( p1 < p2 )); then return 1; fi
|
|
370
|
+
done
|
|
371
|
+
return 0
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
BUN_PATH=""
|
|
375
|
+
|
|
376
|
+
find_bun_path() {
|
|
377
|
+
if command -v bun &>/dev/null; then
|
|
378
|
+
BUN_PATH="$(command -v bun)"
|
|
379
|
+
return 0
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
local -a bun_paths=(
|
|
383
|
+
"${HOME}/.bun/bin/bun"
|
|
384
|
+
"/usr/local/bin/bun"
|
|
385
|
+
"/opt/homebrew/bin/bun"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
for candidate in "${bun_paths[@]}"; do
|
|
389
|
+
if [[ -x "$candidate" ]]; then
|
|
390
|
+
BUN_PATH="$candidate"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
done
|
|
394
|
+
|
|
395
|
+
BUN_PATH=""
|
|
396
|
+
return 1
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
check_bun() {
|
|
400
|
+
if ! find_bun_path; then
|
|
401
|
+
return 1
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
local bun_version
|
|
405
|
+
bun_version="$("$BUN_PATH" --version 2>/dev/null)" || return 1
|
|
406
|
+
|
|
407
|
+
if version_gte "$bun_version" "$MIN_BUN_VERSION"; then
|
|
408
|
+
success "Bun ${bun_version} found at ${BUN_PATH}"
|
|
409
|
+
return 0
|
|
410
|
+
else
|
|
411
|
+
warn "Bun ${bun_version} is below minimum required version ${MIN_BUN_VERSION}"
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
install_bun() {
|
|
417
|
+
info "Installing Bun runtime..."
|
|
418
|
+
|
|
419
|
+
if ! curl -fsSL https://bun.sh/install | bash; then
|
|
420
|
+
error "Failed to install Bun automatically"
|
|
421
|
+
error "Please install manually:"
|
|
422
|
+
error " curl -fsSL https://bun.sh/install | bash"
|
|
423
|
+
error " Or: brew install oven-sh/bun/bun (macOS)"
|
|
424
|
+
error "Then restart your terminal and re-run this installer."
|
|
425
|
+
exit 1
|
|
426
|
+
fi
|
|
427
|
+
|
|
428
|
+
if ! find_bun_path; then
|
|
429
|
+
error "Bun installation completed but binary not found in expected locations"
|
|
430
|
+
error "Please restart your terminal and re-run this installer."
|
|
431
|
+
exit 1
|
|
432
|
+
fi
|
|
433
|
+
|
|
434
|
+
local bun_version
|
|
435
|
+
bun_version="$("$BUN_PATH" --version 2>/dev/null)" || true
|
|
436
|
+
success "Bun ${bun_version} installed at ${BUN_PATH}"
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
UV_PATH=""
|
|
440
|
+
|
|
441
|
+
find_uv_path() {
|
|
442
|
+
if command -v uv &>/dev/null; then
|
|
443
|
+
UV_PATH="$(command -v uv)"
|
|
444
|
+
return 0
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
local -a uv_paths=(
|
|
448
|
+
"${HOME}/.local/bin/uv"
|
|
449
|
+
"${HOME}/.cargo/bin/uv"
|
|
450
|
+
"/usr/local/bin/uv"
|
|
451
|
+
"/opt/homebrew/bin/uv"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
for candidate in "${uv_paths[@]}"; do
|
|
455
|
+
if [[ -x "$candidate" ]]; then
|
|
456
|
+
UV_PATH="$candidate"
|
|
457
|
+
return 0
|
|
458
|
+
fi
|
|
459
|
+
done
|
|
460
|
+
|
|
461
|
+
UV_PATH=""
|
|
462
|
+
return 1
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
check_uv() {
|
|
466
|
+
if ! find_uv_path; then
|
|
467
|
+
return 1
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
local uv_version
|
|
471
|
+
uv_version="$("$UV_PATH" --version 2>/dev/null)" || return 1
|
|
472
|
+
success "uv ${uv_version} found at ${UV_PATH}"
|
|
473
|
+
return 0
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
install_uv() {
|
|
477
|
+
info "Installing uv (Python package manager for Chroma support)..."
|
|
478
|
+
|
|
479
|
+
if ! curl -LsSf https://astral.sh/uv/install.sh | sh; then
|
|
480
|
+
error "Failed to install uv automatically"
|
|
481
|
+
error "Please install manually:"
|
|
482
|
+
error " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
483
|
+
error " Or: brew install uv (macOS)"
|
|
484
|
+
error "Then restart your terminal and re-run this installer."
|
|
485
|
+
exit 1
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
if ! find_uv_path; then
|
|
489
|
+
error "uv installation completed but binary not found in expected locations"
|
|
490
|
+
error "Please restart your terminal and re-run this installer."
|
|
491
|
+
exit 1
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
local uv_version
|
|
495
|
+
uv_version="$("$UV_PATH" --version 2>/dev/null)" || true
|
|
496
|
+
success "uv ${uv_version} installed at ${UV_PATH}"
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
OPENCLAW_PATH=""
|
|
500
|
+
|
|
501
|
+
find_openclaw() {
|
|
502
|
+
for bin_name in openclaw openclaw.mjs; do
|
|
503
|
+
if command -v "$bin_name" &>/dev/null; then
|
|
504
|
+
OPENCLAW_PATH="$(command -v "$bin_name")"
|
|
505
|
+
return 0
|
|
506
|
+
fi
|
|
507
|
+
done
|
|
508
|
+
|
|
509
|
+
local -a openclaw_paths=(
|
|
510
|
+
"${HOME}/.openclaw/openclaw.mjs"
|
|
511
|
+
"/usr/local/bin/openclaw.mjs"
|
|
512
|
+
"/usr/local/bin/openclaw"
|
|
513
|
+
"/usr/local/lib/node_modules/openclaw/openclaw.mjs"
|
|
514
|
+
"${HOME}/.npm-global/lib/node_modules/openclaw/openclaw.mjs"
|
|
515
|
+
"${HOME}/.npm-global/bin/openclaw"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if [[ -n "${NODE_PATH:-}" ]]; then
|
|
519
|
+
openclaw_paths+=("${NODE_PATH}/openclaw/openclaw.mjs")
|
|
520
|
+
fi
|
|
521
|
+
|
|
522
|
+
for candidate in "${openclaw_paths[@]}"; do
|
|
523
|
+
if [[ -f "$candidate" ]]; then
|
|
524
|
+
OPENCLAW_PATH="$candidate"
|
|
525
|
+
return 0
|
|
526
|
+
fi
|
|
527
|
+
done
|
|
528
|
+
|
|
529
|
+
OPENCLAW_PATH=""
|
|
530
|
+
return 1
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
check_openclaw() {
|
|
534
|
+
if ! find_openclaw; then
|
|
535
|
+
error "OpenClaw gateway not found"
|
|
536
|
+
error ""
|
|
537
|
+
error "The claude-mem plugin requires an OpenClaw gateway to be installed."
|
|
538
|
+
error "Please install OpenClaw first:"
|
|
539
|
+
error ""
|
|
540
|
+
error " npm install -g openclaw"
|
|
541
|
+
error " # or visit: https://openclaw.dev/docs/installation"
|
|
542
|
+
error ""
|
|
543
|
+
error "Then re-run this installer."
|
|
544
|
+
exit 1
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
success "OpenClaw gateway found at ${OPENCLAW_PATH}"
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
run_openclaw() {
|
|
551
|
+
if [[ "$OPENCLAW_PATH" == *.mjs ]]; then
|
|
552
|
+
node "$OPENCLAW_PATH" "$@"
|
|
553
|
+
else
|
|
554
|
+
"$OPENCLAW_PATH" "$@"
|
|
555
|
+
fi
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
CLAUDE_MEM_REPO="https://github.com/bjlee2024/claude-mem.git"
|
|
559
|
+
CLAUDE_MEM_BRANCH="${CLI_BRANCH:-main}"
|
|
560
|
+
PLUGIN_FRESHLY_INSTALLED=""
|
|
561
|
+
|
|
562
|
+
resolve_extension_dir() {
|
|
563
|
+
local oc_config="${HOME}/.openclaw/openclaw.json"
|
|
564
|
+
if [[ -f "$oc_config" ]] && command -v node &>/dev/null; then
|
|
565
|
+
local existing_path
|
|
566
|
+
existing_path="$(node -e "
|
|
567
|
+
try {
|
|
568
|
+
const c = require('$oc_config');
|
|
569
|
+
const p = c?.plugins?.installs?.['claude-mem']?.installPath;
|
|
570
|
+
if (p) console.log(p);
|
|
571
|
+
} catch {}
|
|
572
|
+
" 2>/dev/null)" || true
|
|
573
|
+
if [[ -n "$existing_path" ]]; then
|
|
574
|
+
echo "$existing_path"
|
|
575
|
+
return
|
|
576
|
+
fi
|
|
577
|
+
local load_path
|
|
578
|
+
load_path="$(node -e "
|
|
579
|
+
try {
|
|
580
|
+
const c = require('$oc_config');
|
|
581
|
+
const paths = c?.plugins?.load?.paths || [];
|
|
582
|
+
const p = paths.find(p => p.endsWith('/claude-mem'));
|
|
583
|
+
if (p) console.log(p);
|
|
584
|
+
} catch {}
|
|
585
|
+
" 2>/dev/null)" || true
|
|
586
|
+
if [[ -n "$load_path" ]]; then
|
|
587
|
+
echo "$load_path"
|
|
588
|
+
return
|
|
589
|
+
fi
|
|
590
|
+
fi
|
|
591
|
+
echo "${HOME}/.openclaw/extensions/claude-mem"
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
CLAUDE_MEM_EXTENSION_DIR=""
|
|
595
|
+
|
|
596
|
+
install_plugin() {
|
|
597
|
+
check_git
|
|
598
|
+
|
|
599
|
+
CLAUDE_MEM_EXTENSION_DIR="$(resolve_extension_dir)"
|
|
600
|
+
|
|
601
|
+
local existing_plugin_dir="$CLAUDE_MEM_EXTENSION_DIR"
|
|
602
|
+
if [[ -d "$existing_plugin_dir" ]]; then
|
|
603
|
+
info "Removing existing claude-mem plugin at ${existing_plugin_dir}..."
|
|
604
|
+
rm -rf "$existing_plugin_dir"
|
|
605
|
+
fi
|
|
606
|
+
|
|
607
|
+
local build_dir
|
|
608
|
+
build_dir="$(mktemp -d)"
|
|
609
|
+
register_cleanup_dir "$build_dir"
|
|
610
|
+
|
|
611
|
+
info "Cloning claude-mem repository (branch: ${CLAUDE_MEM_BRANCH})..."
|
|
612
|
+
if ! git clone --depth 1 --branch "$CLAUDE_MEM_BRANCH" "$CLAUDE_MEM_REPO" "$build_dir/claude-mem" 2>&1; then
|
|
613
|
+
error "Failed to clone claude-mem repository"
|
|
614
|
+
error "Check your internet connection and try again."
|
|
615
|
+
exit 1
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
local plugin_src="${build_dir}/claude-mem/openclaw"
|
|
619
|
+
|
|
620
|
+
info "Building TypeScript plugin..."
|
|
621
|
+
if ! (cd "$plugin_src" && NODE_ENV=development npm install --ignore-scripts 2>&1 && npx tsc 2>&1); then
|
|
622
|
+
error "Failed to build the claude-mem OpenClaw plugin"
|
|
623
|
+
error "Make sure Node.js and npm are installed."
|
|
624
|
+
exit 1
|
|
625
|
+
fi
|
|
626
|
+
|
|
627
|
+
local installable_dir="${build_dir}/claude-mem-installable"
|
|
628
|
+
mkdir -p "${installable_dir}/dist"
|
|
629
|
+
|
|
630
|
+
cp "${plugin_src}/dist/index.js" "${installable_dir}/dist/"
|
|
631
|
+
cp "${plugin_src}/dist/index.d.ts" "${installable_dir}/dist/" 2>/dev/null || true
|
|
632
|
+
cp "${plugin_src}/openclaw.plugin.json" "${installable_dir}/"
|
|
633
|
+
|
|
634
|
+
INSTALLER_PACKAGE_DIR="$installable_dir" node -e "
|
|
635
|
+
const pkg = {
|
|
636
|
+
name: 'claude-mem',
|
|
637
|
+
version: '1.0.0',
|
|
638
|
+
type: 'module',
|
|
639
|
+
main: 'dist/index.js',
|
|
640
|
+
openclaw: { extensions: ['./dist/index.js'] }
|
|
641
|
+
};
|
|
642
|
+
require('fs').writeFileSync(process.env.INSTALLER_PACKAGE_DIR + '/package.json', JSON.stringify(pkg, null, 2));
|
|
643
|
+
"
|
|
644
|
+
|
|
645
|
+
local oc_config="${HOME}/.openclaw/openclaw.json"
|
|
646
|
+
local saved_plugin_config=""
|
|
647
|
+
if [[ -f "$oc_config" ]]; then
|
|
648
|
+
saved_plugin_config=$(INSTALLER_CONFIG_FILE="$oc_config" node -e "
|
|
649
|
+
const fs = require('fs');
|
|
650
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
651
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
652
|
+
const entry = config?.plugins?.entries?.['claude-mem'];
|
|
653
|
+
const allowHasClaudeMem = Array.isArray(config?.plugins?.allow) && config.plugins.allow.includes('claude-mem');
|
|
654
|
+
if (entry || config?.plugins?.slots?.memory === 'claude-mem' || allowHasClaudeMem) {
|
|
655
|
+
// Save the config block so we can restore it after install
|
|
656
|
+
process.stdout.write(JSON.stringify(entry?.config || {}));
|
|
657
|
+
// Remove the stale entry so OpenClaw CLI can run
|
|
658
|
+
if (entry) delete config.plugins.entries['claude-mem'];
|
|
659
|
+
// Also remove stale allowlist reference — this alone can block ALL CLI commands
|
|
660
|
+
if (Array.isArray(config?.plugins?.allow)) {
|
|
661
|
+
config.plugins.allow = config.plugins.allow.filter((x) => x !== 'claude-mem');
|
|
662
|
+
}
|
|
663
|
+
// Also remove the slot reference — if the slot points to a plugin
|
|
664
|
+
// that isn't in entries, OpenClaw's config validator rejects ALL commands
|
|
665
|
+
if (config?.plugins?.slots?.memory === 'claude-mem') {
|
|
666
|
+
delete config.plugins.slots.memory;
|
|
667
|
+
}
|
|
668
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
669
|
+
}
|
|
670
|
+
" 2>/dev/null) || true
|
|
671
|
+
fi
|
|
672
|
+
|
|
673
|
+
info "Installing claude-mem plugin into OpenClaw..."
|
|
674
|
+
if ! run_openclaw plugins install "$installable_dir" 2>&1; then
|
|
675
|
+
error "Failed to install claude-mem plugin"
|
|
676
|
+
error "Try manually: ${OPENCLAW_PATH} plugins install <path>"
|
|
677
|
+
exit 1
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
info "Enabling claude-mem plugin..."
|
|
681
|
+
if ! run_openclaw plugins enable claude-mem 2>&1; then
|
|
682
|
+
error "Failed to enable claude-mem plugin"
|
|
683
|
+
error "Try manually: ${OPENCLAW_PATH} plugins enable claude-mem"
|
|
684
|
+
exit 1
|
|
685
|
+
fi
|
|
686
|
+
|
|
687
|
+
if [[ -f "$oc_config" ]]; then
|
|
688
|
+
if ! INSTALLER_CONFIG_FILE="$oc_config" node -e "
|
|
689
|
+
const fs = require('fs');
|
|
690
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
691
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
692
|
+
if (!config.plugins) config.plugins = {};
|
|
693
|
+
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
694
|
+
if (!config.plugins.allow.includes('claude-mem')) {
|
|
695
|
+
config.plugins.allow.push('claude-mem');
|
|
696
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
697
|
+
console.log('Added claude-mem to plugins.allow');
|
|
698
|
+
} else {
|
|
699
|
+
console.log('claude-mem already in plugins.allow');
|
|
700
|
+
}
|
|
701
|
+
" 2>&1; then
|
|
702
|
+
warn "Failed to write plugins.allow — claude-mem may need manual allowlisting"
|
|
703
|
+
fi
|
|
704
|
+
else
|
|
705
|
+
info "OpenClaw config not yet materialized; will ensure allowlist in post-install"
|
|
706
|
+
if run_openclaw status --json >/dev/null 2>&1 && [[ -f "$oc_config" ]]; then
|
|
707
|
+
if ! INSTALLER_CONFIG_FILE="$oc_config" node -e "
|
|
708
|
+
const fs = require('fs');
|
|
709
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
710
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
711
|
+
if (!config.plugins) config.plugins = {};
|
|
712
|
+
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
713
|
+
if (!config.plugins.allow.includes('claude-mem')) {
|
|
714
|
+
config.plugins.allow.push('claude-mem');
|
|
715
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
716
|
+
console.log('Added claude-mem to plugins.allow (post-materialization)');
|
|
717
|
+
}
|
|
718
|
+
" 2>&1; then
|
|
719
|
+
warn "Failed to write plugins.allow after materialization — configure manually"
|
|
720
|
+
fi
|
|
721
|
+
fi
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
if [[ -n "$saved_plugin_config" && "$saved_plugin_config" != "{}" ]]; then
|
|
725
|
+
info "Restoring previous plugin configuration..."
|
|
726
|
+
INSTALLER_CONFIG_FILE="$oc_config" INSTALLER_SAVED_CONFIG="$saved_plugin_config" node -e "
|
|
727
|
+
const fs = require('fs');
|
|
728
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
729
|
+
const savedConfig = JSON.parse(process.env.INSTALLER_SAVED_CONFIG);
|
|
730
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
731
|
+
if (config?.plugins?.entries?.['claude-mem']) {
|
|
732
|
+
config.plugins.entries['claude-mem'].config = savedConfig;
|
|
733
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
734
|
+
}
|
|
735
|
+
" 2>/dev/null || warn "Could not restore previous plugin config — configure manually"
|
|
736
|
+
fi
|
|
737
|
+
|
|
738
|
+
success "claude-mem plugin installed and enabled"
|
|
739
|
+
|
|
740
|
+
local extension_dir="$CLAUDE_MEM_EXTENSION_DIR"
|
|
741
|
+
local repo_root="${build_dir}/claude-mem"
|
|
742
|
+
|
|
743
|
+
if [[ -d "$extension_dir" && -d "${repo_root}/plugin" ]]; then
|
|
744
|
+
info "Copying core plugin files to ${extension_dir}..."
|
|
745
|
+
|
|
746
|
+
cp -R "${repo_root}/plugin" "${extension_dir}/"
|
|
747
|
+
|
|
748
|
+
local root_version
|
|
749
|
+
root_version="$(node -e "console.log(require('${repo_root}/package.json').version)")"
|
|
750
|
+
node -e "
|
|
751
|
+
const fs = require('fs');
|
|
752
|
+
const pkgPath = '${extension_dir}/package.json';
|
|
753
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
754
|
+
pkg.version = '${root_version}';
|
|
755
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
756
|
+
"
|
|
757
|
+
|
|
758
|
+
success "Core plugin files updated at ${extension_dir}"
|
|
759
|
+
else
|
|
760
|
+
warn "Could not copy core plugin files — worker may need manual update"
|
|
761
|
+
fi
|
|
762
|
+
|
|
763
|
+
PLUGIN_FRESHLY_INSTALLED="true"
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
configure_memory_slot() {
|
|
767
|
+
local config_dir="${HOME}/.openclaw"
|
|
768
|
+
local config_file="${config_dir}/openclaw.json"
|
|
769
|
+
|
|
770
|
+
mkdir -p "$config_dir"
|
|
771
|
+
|
|
772
|
+
if [[ ! -f "$config_file" ]]; then
|
|
773
|
+
info "Creating OpenClaw configuration with claude-mem memory slot..."
|
|
774
|
+
INSTALLER_CONFIG_FILE="$config_file" node -e "
|
|
775
|
+
const config = {
|
|
776
|
+
plugins: {
|
|
777
|
+
slots: { memory: 'claude-mem' },
|
|
778
|
+
entries: {
|
|
779
|
+
'claude-mem': {
|
|
780
|
+
enabled: true,
|
|
781
|
+
config: {
|
|
782
|
+
workerPort: 37777,
|
|
783
|
+
syncMemoryFile: true
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
require('fs').writeFileSync(process.env.INSTALLER_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
790
|
+
"
|
|
791
|
+
success "Created ${config_file} with memory slot set to claude-mem"
|
|
792
|
+
return 0
|
|
793
|
+
fi
|
|
794
|
+
|
|
795
|
+
info "Updating OpenClaw configuration to use claude-mem memory slot..."
|
|
796
|
+
|
|
797
|
+
INSTALLER_CONFIG_FILE="$config_file" node -e "
|
|
798
|
+
const fs = require('fs');
|
|
799
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
800
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
801
|
+
|
|
802
|
+
// Ensure plugins structure exists
|
|
803
|
+
if (!config.plugins) config.plugins = {};
|
|
804
|
+
if (!config.plugins.slots) config.plugins.slots = {};
|
|
805
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
806
|
+
|
|
807
|
+
// Set memory slot to claude-mem
|
|
808
|
+
config.plugins.slots.memory = 'claude-mem';
|
|
809
|
+
|
|
810
|
+
// Ensure claude-mem entry exists and is enabled
|
|
811
|
+
if (!config.plugins.entries['claude-mem']) {
|
|
812
|
+
config.plugins.entries['claude-mem'] = {
|
|
813
|
+
enabled: true,
|
|
814
|
+
config: {
|
|
815
|
+
workerPort: 37777,
|
|
816
|
+
syncMemoryFile: true
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
} else {
|
|
820
|
+
config.plugins.entries['claude-mem'].enabled = true;
|
|
821
|
+
// Remove unrecognized keys that cause OpenClaw config validation errors
|
|
822
|
+
const allowedKeys = new Set(['enabled', 'config']);
|
|
823
|
+
for (const key of Object.keys(config.plugins.entries['claude-mem'])) {
|
|
824
|
+
if (!allowedKeys.has(key)) {
|
|
825
|
+
delete config.plugins.entries['claude-mem'][key];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
831
|
+
"
|
|
832
|
+
|
|
833
|
+
success "Memory slot set to claude-mem in ${config_file}"
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
AI_PROVIDER=""
|
|
837
|
+
AI_PROVIDER_API_KEY=""
|
|
838
|
+
|
|
839
|
+
mask_api_key() {
|
|
840
|
+
local key="$1"
|
|
841
|
+
local len=${#key}
|
|
842
|
+
if (( len <= 4 )); then
|
|
843
|
+
echo "****"
|
|
844
|
+
else
|
|
845
|
+
local masked_len=$((len - 4))
|
|
846
|
+
local mask=""
|
|
847
|
+
for (( i=0; i<masked_len; i++ )); do
|
|
848
|
+
mask+="*"
|
|
849
|
+
done
|
|
850
|
+
echo "${mask}${key: -4}"
|
|
851
|
+
fi
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
setup_ai_provider() {
|
|
855
|
+
echo ""
|
|
856
|
+
info "AI Provider Configuration"
|
|
857
|
+
echo ""
|
|
858
|
+
|
|
859
|
+
if [[ -n "$CLI_PROVIDER" ]]; then
|
|
860
|
+
case "$CLI_PROVIDER" in
|
|
861
|
+
claude)
|
|
862
|
+
AI_PROVIDER="claude"
|
|
863
|
+
success "Selected via --provider: Claude Max Plan (CLI authentication)"
|
|
864
|
+
;;
|
|
865
|
+
gemini)
|
|
866
|
+
AI_PROVIDER="gemini"
|
|
867
|
+
AI_PROVIDER_API_KEY="${CLI_API_KEY}"
|
|
868
|
+
if [[ -n "$AI_PROVIDER_API_KEY" ]]; then
|
|
869
|
+
success "Selected via --provider: Gemini (API key set via --api-key)"
|
|
870
|
+
else
|
|
871
|
+
warn "Selected via --provider: Gemini (no API key — add later in ~/.claude-mem/settings.json)"
|
|
872
|
+
fi
|
|
873
|
+
;;
|
|
874
|
+
openrouter)
|
|
875
|
+
AI_PROVIDER="openrouter"
|
|
876
|
+
AI_PROVIDER_API_KEY="${CLI_API_KEY}"
|
|
877
|
+
if [[ -n "$AI_PROVIDER_API_KEY" ]]; then
|
|
878
|
+
success "Selected via --provider: OpenRouter (API key set via --api-key)"
|
|
879
|
+
else
|
|
880
|
+
warn "Selected via --provider: OpenRouter (no API key — add later in ~/.claude-mem/settings.json)"
|
|
881
|
+
fi
|
|
882
|
+
;;
|
|
883
|
+
*)
|
|
884
|
+
error "Unknown provider: ${CLI_PROVIDER}"
|
|
885
|
+
error "Valid providers: claude, gemini, openrouter"
|
|
886
|
+
exit 1
|
|
887
|
+
;;
|
|
888
|
+
esac
|
|
889
|
+
return 0
|
|
890
|
+
fi
|
|
891
|
+
|
|
892
|
+
if [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
893
|
+
info "Non-interactive mode: defaulting to Claude Max Plan (no API key needed)"
|
|
894
|
+
AI_PROVIDER="claude"
|
|
895
|
+
return 0
|
|
896
|
+
fi
|
|
897
|
+
|
|
898
|
+
echo -e " Choose your AI provider for claude-mem:"
|
|
899
|
+
echo ""
|
|
900
|
+
echo -e " ${COLOR_BOLD}1)${COLOR_RESET} Claude Max Plan ${COLOR_GREEN}(recommended)${COLOR_RESET}"
|
|
901
|
+
echo -e " Uses your existing subscription, no API key needed"
|
|
902
|
+
echo ""
|
|
903
|
+
echo -e " ${COLOR_BOLD}2)${COLOR_RESET} Gemini"
|
|
904
|
+
echo -e " Free tier available — requires API key from ai.google.dev"
|
|
905
|
+
echo ""
|
|
906
|
+
echo -e " ${COLOR_BOLD}3)${COLOR_RESET} OpenRouter"
|
|
907
|
+
echo -e " Pay-per-use — requires API key from openrouter.ai"
|
|
908
|
+
echo ""
|
|
909
|
+
|
|
910
|
+
local choice
|
|
911
|
+
while true; do
|
|
912
|
+
prompt_user "Enter choice [1/2/3] (default: 1):"
|
|
913
|
+
read_tty -r choice
|
|
914
|
+
choice="${choice:-1}"
|
|
915
|
+
|
|
916
|
+
case "$choice" in
|
|
917
|
+
1)
|
|
918
|
+
AI_PROVIDER="claude"
|
|
919
|
+
success "Selected: Claude Max Plan (CLI authentication)"
|
|
920
|
+
break
|
|
921
|
+
;;
|
|
922
|
+
2)
|
|
923
|
+
AI_PROVIDER="gemini"
|
|
924
|
+
echo ""
|
|
925
|
+
prompt_user "Enter your Gemini API key (from https://ai.google.dev):"
|
|
926
|
+
read_tty -rs AI_PROVIDER_API_KEY
|
|
927
|
+
echo ""
|
|
928
|
+
if [[ -z "$AI_PROVIDER_API_KEY" ]]; then
|
|
929
|
+
warn "No API key provided — you can add it later in ~/.claude-mem/settings.json"
|
|
930
|
+
else
|
|
931
|
+
success "Gemini API key set ($(mask_api_key "$AI_PROVIDER_API_KEY"))"
|
|
932
|
+
fi
|
|
933
|
+
break
|
|
934
|
+
;;
|
|
935
|
+
3)
|
|
936
|
+
AI_PROVIDER="openrouter"
|
|
937
|
+
echo ""
|
|
938
|
+
prompt_user "Enter your OpenRouter API key (from https://openrouter.ai):"
|
|
939
|
+
read_tty -rs AI_PROVIDER_API_KEY
|
|
940
|
+
echo ""
|
|
941
|
+
if [[ -z "$AI_PROVIDER_API_KEY" ]]; then
|
|
942
|
+
warn "No API key provided — you can add it later in ~/.claude-mem/settings.json"
|
|
943
|
+
else
|
|
944
|
+
success "OpenRouter API key set ($(mask_api_key "$AI_PROVIDER_API_KEY"))"
|
|
945
|
+
fi
|
|
946
|
+
break
|
|
947
|
+
;;
|
|
948
|
+
*)
|
|
949
|
+
warn "Invalid choice. Please enter 1, 2, or 3."
|
|
950
|
+
;;
|
|
951
|
+
esac
|
|
952
|
+
done
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
write_settings() {
|
|
956
|
+
local settings_dir="${HOME}/.claude-mem"
|
|
957
|
+
local settings_file="${settings_dir}/settings.json"
|
|
958
|
+
|
|
959
|
+
mkdir -p "$settings_dir"
|
|
960
|
+
|
|
961
|
+
INSTALLER_AI_PROVIDER="$AI_PROVIDER" \
|
|
962
|
+
INSTALLER_AI_API_KEY="$AI_PROVIDER_API_KEY" \
|
|
963
|
+
INSTALLER_SETTINGS_FILE="$settings_file" \
|
|
964
|
+
node -e "
|
|
965
|
+
const fs = require('fs');
|
|
966
|
+
const path = require('path');
|
|
967
|
+
const homedir = require('os').homedir();
|
|
968
|
+
const provider = process.env.INSTALLER_AI_PROVIDER;
|
|
969
|
+
const apiKey = process.env.INSTALLER_AI_API_KEY || '';
|
|
970
|
+
const settingsPath = process.env.INSTALLER_SETTINGS_FILE;
|
|
971
|
+
|
|
972
|
+
// All defaults from SettingsDefaultsManager.ts
|
|
973
|
+
const defaults = {
|
|
974
|
+
CLAUDE_MEM_MODEL: 'claude-sonnet-4-6',
|
|
975
|
+
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
|
|
976
|
+
CLAUDE_MEM_WORKER_PORT: '37777',
|
|
977
|
+
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
|
|
978
|
+
CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion',
|
|
979
|
+
CLAUDE_MEM_PROVIDER: 'claude',
|
|
980
|
+
CLAUDE_MEM_CLAUDE_AUTH_METHOD: 'cli',
|
|
981
|
+
CLAUDE_MEM_GEMINI_API_KEY: '',
|
|
982
|
+
CLAUDE_MEM_GEMINI_MODEL: 'gemini-2.5-flash-lite',
|
|
983
|
+
CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED: 'true',
|
|
984
|
+
CLAUDE_MEM_OPENROUTER_API_KEY: '',
|
|
985
|
+
CLAUDE_MEM_OPENROUTER_MODEL: 'xiaomi/mimo-v2-flash:free',
|
|
986
|
+
CLAUDE_MEM_OPENROUTER_SITE_URL: '',
|
|
987
|
+
CLAUDE_MEM_OPENROUTER_APP_NAME: 'claude-mem',
|
|
988
|
+
CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES: '20',
|
|
989
|
+
CLAUDE_MEM_OPENROUTER_MAX_TOKENS: '100000',
|
|
990
|
+
CLAUDE_MEM_DATA_DIR: path.join(homedir, '.claude-mem'),
|
|
991
|
+
CLAUDE_MEM_LOG_LEVEL: 'INFO',
|
|
992
|
+
CLAUDE_MEM_PYTHON_VERSION: '3.13',
|
|
993
|
+
CLAUDE_CODE_PATH: '',
|
|
994
|
+
CLAUDE_MEM_MODE: 'code',
|
|
995
|
+
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'true',
|
|
996
|
+
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: 'true',
|
|
997
|
+
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: 'true',
|
|
998
|
+
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: 'true',
|
|
999
|
+
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: 'bugfix,feature,refactor,discovery,decision,change',
|
|
1000
|
+
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: 'how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off',
|
|
1001
|
+
CLAUDE_MEM_CONTEXT_FULL_COUNT: '5',
|
|
1002
|
+
CLAUDE_MEM_CONTEXT_FULL_FIELD: 'narrative',
|
|
1003
|
+
CLAUDE_MEM_CONTEXT_SESSION_COUNT: '10',
|
|
1004
|
+
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: 'true',
|
|
1005
|
+
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: 'false',
|
|
1006
|
+
CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED: 'false',
|
|
1007
|
+
CLAUDE_MEM_EXCLUDED_PROJECTS: '',
|
|
1008
|
+
CLAUDE_MEM_FOLDER_MD_EXCLUDE: '[]'
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// Build provider-specific overrides safely from environment variables
|
|
1012
|
+
const overrides = { CLAUDE_MEM_PROVIDER: provider };
|
|
1013
|
+
if (provider === 'claude') {
|
|
1014
|
+
overrides.CLAUDE_MEM_CLAUDE_AUTH_METHOD = 'cli';
|
|
1015
|
+
} else if (provider === 'gemini') {
|
|
1016
|
+
overrides.CLAUDE_MEM_GEMINI_API_KEY = apiKey;
|
|
1017
|
+
overrides.CLAUDE_MEM_GEMINI_MODEL = 'gemini-2.5-flash-lite';
|
|
1018
|
+
} else if (provider === 'openrouter') {
|
|
1019
|
+
overrides.CLAUDE_MEM_OPENROUTER_API_KEY = apiKey;
|
|
1020
|
+
overrides.CLAUDE_MEM_OPENROUTER_MODEL = 'xiaomi/mimo-v2-flash:free';
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const settings = Object.assign(defaults, overrides);
|
|
1024
|
+
|
|
1025
|
+
// If settings file already exists, merge (preserve user customizations)
|
|
1026
|
+
if (fs.existsSync(settingsPath)) {
|
|
1027
|
+
try {
|
|
1028
|
+
let existing = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
1029
|
+
// Handle old nested schema
|
|
1030
|
+
if (existing.env && typeof existing.env === 'object') {
|
|
1031
|
+
existing = existing.env;
|
|
1032
|
+
}
|
|
1033
|
+
// Existing settings take priority, except for provider settings we just set
|
|
1034
|
+
for (const key of Object.keys(existing)) {
|
|
1035
|
+
if (!(key in overrides) && key in defaults) {
|
|
1036
|
+
settings[key] = existing[key];
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} catch (e) {
|
|
1040
|
+
// Corrupted file — overwrite with fresh defaults
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
1045
|
+
"
|
|
1046
|
+
|
|
1047
|
+
success "Settings written to ${settings_file}"
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
1051
|
+
|
|
1052
|
+
find_claude_mem_install_dir() {
|
|
1053
|
+
local resolved_dir
|
|
1054
|
+
resolved_dir="$(resolve_extension_dir)"
|
|
1055
|
+
local -a search_paths=(
|
|
1056
|
+
"$resolved_dir"
|
|
1057
|
+
"${HOME}/.openclaw/extensions/claude-mem"
|
|
1058
|
+
"${HOME}/.claude/plugins/marketplaces/bjlee2024"
|
|
1059
|
+
"${HOME}/.openclaw/plugins/claude-mem"
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
for candidate in "${search_paths[@]}"; do
|
|
1063
|
+
if [[ -f "${candidate}/plugin/scripts/worker-service.cjs" ]]; then
|
|
1064
|
+
CLAUDE_MEM_INSTALL_DIR="$candidate"
|
|
1065
|
+
return 0
|
|
1066
|
+
fi
|
|
1067
|
+
done
|
|
1068
|
+
|
|
1069
|
+
local -a roots=(
|
|
1070
|
+
"${HOME}/.openclaw"
|
|
1071
|
+
"${HOME}/.claude/plugins"
|
|
1072
|
+
)
|
|
1073
|
+
for root in "${roots[@]}"; do
|
|
1074
|
+
if [[ -d "$root" ]]; then
|
|
1075
|
+
local found
|
|
1076
|
+
found="$(find "$root" -name "worker-service.cjs" -path "*/plugin/scripts/*" 2>/dev/null | head -n 1)" || true
|
|
1077
|
+
if [[ -n "$found" ]]; then
|
|
1078
|
+
CLAUDE_MEM_INSTALL_DIR="${found%/plugin/scripts/worker-service.cjs}"
|
|
1079
|
+
return 0
|
|
1080
|
+
fi
|
|
1081
|
+
fi
|
|
1082
|
+
done
|
|
1083
|
+
|
|
1084
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
1085
|
+
return 1
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
WORKER_PID=""
|
|
1089
|
+
WORKER_VERSION=""
|
|
1090
|
+
WORKER_AI_PROVIDER=""
|
|
1091
|
+
WORKER_AI_AUTH_METHOD=""
|
|
1092
|
+
WORKER_INITIALIZED=""
|
|
1093
|
+
WORKER_REPORTED_PID=""
|
|
1094
|
+
WORKER_UPTIME=""
|
|
1095
|
+
|
|
1096
|
+
start_worker() {
|
|
1097
|
+
info "Starting claude-mem worker service..."
|
|
1098
|
+
|
|
1099
|
+
if ! find_claude_mem_install_dir; then
|
|
1100
|
+
error "Cannot find claude-mem plugin installation directory"
|
|
1101
|
+
error "Expected worker-service.cjs in one of:"
|
|
1102
|
+
error " ~/.openclaw/extensions/claude-mem/plugin/scripts/"
|
|
1103
|
+
error " ~/.claude/plugins/marketplaces/bjlee2024/plugin/scripts/"
|
|
1104
|
+
error ""
|
|
1105
|
+
error "Try reinstalling the plugin and re-running this installer."
|
|
1106
|
+
return 1
|
|
1107
|
+
fi
|
|
1108
|
+
|
|
1109
|
+
local worker_script="${CLAUDE_MEM_INSTALL_DIR}/plugin/scripts/worker-service.cjs"
|
|
1110
|
+
local log_dir="${HOME}/.claude-mem/logs"
|
|
1111
|
+
local log_date
|
|
1112
|
+
log_date="$(date +%Y-%m-%d)"
|
|
1113
|
+
local log_file="${log_dir}/worker-${log_date}.log"
|
|
1114
|
+
|
|
1115
|
+
mkdir -p "$log_dir"
|
|
1116
|
+
|
|
1117
|
+
if [[ -z "$BUN_PATH" ]]; then
|
|
1118
|
+
if ! find_bun_path; then
|
|
1119
|
+
error "Bun not found — cannot start worker service"
|
|
1120
|
+
return 1
|
|
1121
|
+
fi
|
|
1122
|
+
fi
|
|
1123
|
+
|
|
1124
|
+
CLAUDE_MEM_WORKER_PORT=37777 nohup "$BUN_PATH" "$worker_script" \
|
|
1125
|
+
>> "$log_file" 2>&1 &
|
|
1126
|
+
WORKER_PID=$!
|
|
1127
|
+
|
|
1128
|
+
local pid_file="${HOME}/.claude-mem/worker.pid"
|
|
1129
|
+
mkdir -p "${HOME}/.claude-mem"
|
|
1130
|
+
INSTALLER_PID_FILE="$pid_file" INSTALLER_WORKER_PID="$WORKER_PID" node -e "
|
|
1131
|
+
const info = {
|
|
1132
|
+
pid: parseInt(process.env.INSTALLER_WORKER_PID, 10),
|
|
1133
|
+
port: 37777,
|
|
1134
|
+
startedAt: new Date().toISOString(),
|
|
1135
|
+
version: 'installer'
|
|
1136
|
+
};
|
|
1137
|
+
require('fs').writeFileSync(process.env.INSTALLER_PID_FILE, JSON.stringify(info, null, 2));
|
|
1138
|
+
"
|
|
1139
|
+
|
|
1140
|
+
success "Worker process started (PID: ${WORKER_PID})"
|
|
1141
|
+
info "Logs: ${log_file}"
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
verify_health() {
|
|
1145
|
+
local max_attempts=30
|
|
1146
|
+
local attempt=1
|
|
1147
|
+
local health_url="http://127.0.0.1:37777/api/health"
|
|
1148
|
+
local readiness_url="http://127.0.0.1:37777/api/readiness"
|
|
1149
|
+
local health_alive=false
|
|
1150
|
+
|
|
1151
|
+
info "Verifying worker health..."
|
|
1152
|
+
|
|
1153
|
+
while (( attempt <= max_attempts )); do
|
|
1154
|
+
local http_status
|
|
1155
|
+
http_status="$(curl -s -o /dev/null -w "%{http_code}" "$health_url" 2>/dev/null)" || true
|
|
1156
|
+
|
|
1157
|
+
if [[ "$http_status" == "200" ]]; then
|
|
1158
|
+
health_alive=true
|
|
1159
|
+
|
|
1160
|
+
local body
|
|
1161
|
+
body="$(curl -s "$health_url" 2>/dev/null)" || true
|
|
1162
|
+
parse_health_json "$body"
|
|
1163
|
+
|
|
1164
|
+
success "Worker is alive, waiting for initialization..."
|
|
1165
|
+
|
|
1166
|
+
break
|
|
1167
|
+
fi
|
|
1168
|
+
|
|
1169
|
+
info "Waiting for worker to start... (attempt ${attempt}/${max_attempts})"
|
|
1170
|
+
sleep 1
|
|
1171
|
+
attempt=$((attempt + 1))
|
|
1172
|
+
done
|
|
1173
|
+
|
|
1174
|
+
if [[ "$health_alive" != "true" ]]; then
|
|
1175
|
+
warn "Worker health check timed out after ${max_attempts} attempts"
|
|
1176
|
+
warn "The worker may still be starting up. Check status with:"
|
|
1177
|
+
warn " curl http://127.0.0.1:37777/api/health"
|
|
1178
|
+
warn " Or check logs: ~/.claude-mem/logs/"
|
|
1179
|
+
return 1
|
|
1180
|
+
fi
|
|
1181
|
+
|
|
1182
|
+
attempt=$((attempt + 1))
|
|
1183
|
+
while (( attempt <= max_attempts )); do
|
|
1184
|
+
local readiness_status
|
|
1185
|
+
readiness_status="$(curl -s -o /dev/null -w "%{http_code}" "$readiness_url" 2>/dev/null)" || true
|
|
1186
|
+
|
|
1187
|
+
if [[ "$readiness_status" == "200" ]]; then
|
|
1188
|
+
success "Worker is ready!"
|
|
1189
|
+
return 0
|
|
1190
|
+
fi
|
|
1191
|
+
|
|
1192
|
+
info "Waiting for worker to initialize... (attempt ${attempt}/${max_attempts})"
|
|
1193
|
+
sleep 1
|
|
1194
|
+
attempt=$((attempt + 1))
|
|
1195
|
+
done
|
|
1196
|
+
|
|
1197
|
+
warn "Worker is running but initialization is still in progress"
|
|
1198
|
+
warn "This is normal on first run — the worker will finish initializing in the background."
|
|
1199
|
+
warn "Check readiness with: curl http://127.0.0.1:37777/api/readiness"
|
|
1200
|
+
return 0
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
FEED_CHANNEL=""
|
|
1204
|
+
FEED_TARGET_ID=""
|
|
1205
|
+
FEED_CONFIGURED=false
|
|
1206
|
+
|
|
1207
|
+
setup_observation_feed() {
|
|
1208
|
+
echo ""
|
|
1209
|
+
echo -e " ${COLOR_BOLD}Real-Time Observation Feed${COLOR_RESET}"
|
|
1210
|
+
echo ""
|
|
1211
|
+
echo " claude-mem can stream AI-compressed observations to a messaging"
|
|
1212
|
+
echo " channel in real time. Every time an agent learns something,"
|
|
1213
|
+
echo " you'll see it in your chat."
|
|
1214
|
+
echo ""
|
|
1215
|
+
|
|
1216
|
+
if [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
1217
|
+
info "Non-interactive mode: skipping observation feed setup"
|
|
1218
|
+
info "Configure later in ~/.openclaw/openclaw.json under"
|
|
1219
|
+
info " plugins.entries.claude-mem.config.observationFeed"
|
|
1220
|
+
return 0
|
|
1221
|
+
fi
|
|
1222
|
+
|
|
1223
|
+
prompt_user "Would you like to set up real-time observation streaming to a messaging channel? (y/n)"
|
|
1224
|
+
local answer
|
|
1225
|
+
read_tty -r answer
|
|
1226
|
+
answer="${answer:-n}"
|
|
1227
|
+
|
|
1228
|
+
if [[ "$answer" != [yY] && "$answer" != [yY][eE][sS] ]]; then
|
|
1229
|
+
echo ""
|
|
1230
|
+
info "Skipped observation feed setup."
|
|
1231
|
+
info "You can configure it later by re-running this installer or"
|
|
1232
|
+
info "editing ~/.openclaw/openclaw.json under"
|
|
1233
|
+
info " plugins.entries.claude-mem.config.observationFeed"
|
|
1234
|
+
return 0
|
|
1235
|
+
fi
|
|
1236
|
+
|
|
1237
|
+
echo ""
|
|
1238
|
+
echo -e " ${COLOR_BOLD}Select your messaging channel:${COLOR_RESET}"
|
|
1239
|
+
echo ""
|
|
1240
|
+
echo -e " ${COLOR_BOLD}1)${COLOR_RESET} Telegram"
|
|
1241
|
+
echo -e " ${COLOR_BOLD}2)${COLOR_RESET} Discord"
|
|
1242
|
+
echo -e " ${COLOR_BOLD}3)${COLOR_RESET} Slack"
|
|
1243
|
+
echo -e " ${COLOR_BOLD}4)${COLOR_RESET} Signal"
|
|
1244
|
+
echo -e " ${COLOR_BOLD}5)${COLOR_RESET} WhatsApp"
|
|
1245
|
+
echo -e " ${COLOR_BOLD}6)${COLOR_RESET} LINE"
|
|
1246
|
+
echo ""
|
|
1247
|
+
|
|
1248
|
+
local channel_choice
|
|
1249
|
+
while true; do
|
|
1250
|
+
prompt_user "Enter choice [1-6]:"
|
|
1251
|
+
read_tty -r channel_choice
|
|
1252
|
+
|
|
1253
|
+
case "$channel_choice" in
|
|
1254
|
+
1)
|
|
1255
|
+
FEED_CHANNEL="telegram"
|
|
1256
|
+
echo ""
|
|
1257
|
+
echo -e " ${COLOR_CYAN}How to find your Telegram chat ID:${COLOR_RESET}"
|
|
1258
|
+
echo " Message @userinfobot on Telegram (https://t.me/userinfobot)"
|
|
1259
|
+
echo " — it replies with your numeric chat ID."
|
|
1260
|
+
echo " For groups, the ID is negative (e.g., -1001234567890)."
|
|
1261
|
+
break
|
|
1262
|
+
;;
|
|
1263
|
+
2)
|
|
1264
|
+
FEED_CHANNEL="discord"
|
|
1265
|
+
echo ""
|
|
1266
|
+
echo -e " ${COLOR_CYAN}How to find your Discord channel ID:${COLOR_RESET}"
|
|
1267
|
+
echo " Enable Developer Mode (Settings → Advanced → Developer Mode),"
|
|
1268
|
+
echo " right-click the target channel → Copy Channel ID"
|
|
1269
|
+
break
|
|
1270
|
+
;;
|
|
1271
|
+
3)
|
|
1272
|
+
FEED_CHANNEL="slack"
|
|
1273
|
+
echo ""
|
|
1274
|
+
echo -e " ${COLOR_CYAN}How to find your Slack channel ID:${COLOR_RESET}"
|
|
1275
|
+
echo " Open the channel, click the channel name at top,"
|
|
1276
|
+
echo " scroll to bottom — ID looks like C01ABC2DEFG"
|
|
1277
|
+
break
|
|
1278
|
+
;;
|
|
1279
|
+
4)
|
|
1280
|
+
FEED_CHANNEL="signal"
|
|
1281
|
+
echo ""
|
|
1282
|
+
echo -e " ${COLOR_CYAN}How to find your Signal target ID:${COLOR_RESET}"
|
|
1283
|
+
echo " Use the phone number or group ID from your"
|
|
1284
|
+
echo " OpenClaw Signal plugin config"
|
|
1285
|
+
break
|
|
1286
|
+
;;
|
|
1287
|
+
5)
|
|
1288
|
+
FEED_CHANNEL="whatsapp"
|
|
1289
|
+
echo ""
|
|
1290
|
+
echo -e " ${COLOR_CYAN}How to find your WhatsApp target ID:${COLOR_RESET}"
|
|
1291
|
+
echo " Use the phone number or group JID from your"
|
|
1292
|
+
echo " OpenClaw WhatsApp plugin config"
|
|
1293
|
+
break
|
|
1294
|
+
;;
|
|
1295
|
+
6)
|
|
1296
|
+
FEED_CHANNEL="line"
|
|
1297
|
+
echo ""
|
|
1298
|
+
echo -e " ${COLOR_CYAN}How to find your LINE target ID:${COLOR_RESET}"
|
|
1299
|
+
echo " Use the user ID or group ID from the"
|
|
1300
|
+
echo " LINE Developer Console"
|
|
1301
|
+
break
|
|
1302
|
+
;;
|
|
1303
|
+
*)
|
|
1304
|
+
warn "Invalid choice. Please enter a number between 1 and 6."
|
|
1305
|
+
;;
|
|
1306
|
+
esac
|
|
1307
|
+
done
|
|
1308
|
+
|
|
1309
|
+
echo ""
|
|
1310
|
+
prompt_user "Enter your ${FEED_CHANNEL} target ID:"
|
|
1311
|
+
read_tty -r FEED_TARGET_ID
|
|
1312
|
+
|
|
1313
|
+
if [[ -z "$FEED_TARGET_ID" ]]; then
|
|
1314
|
+
warn "No target ID provided — skipping observation feed setup."
|
|
1315
|
+
warn "You can configure it later in ~/.openclaw/openclaw.json"
|
|
1316
|
+
FEED_CHANNEL=""
|
|
1317
|
+
return 0
|
|
1318
|
+
fi
|
|
1319
|
+
|
|
1320
|
+
success "Observation feed: ${FEED_CHANNEL} → ${FEED_TARGET_ID}"
|
|
1321
|
+
FEED_CONFIGURED=true
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
write_observation_feed_config() {
|
|
1325
|
+
if [[ "$FEED_CONFIGURED" != "true" ]]; then
|
|
1326
|
+
return 0
|
|
1327
|
+
fi
|
|
1328
|
+
|
|
1329
|
+
local config_file="${HOME}/.openclaw/openclaw.json"
|
|
1330
|
+
|
|
1331
|
+
if [[ ! -f "$config_file" ]]; then
|
|
1332
|
+
warn "OpenClaw config file not found at ${config_file}"
|
|
1333
|
+
warn "Cannot write observation feed config."
|
|
1334
|
+
return 1
|
|
1335
|
+
fi
|
|
1336
|
+
|
|
1337
|
+
info "Writing observation feed configuration..."
|
|
1338
|
+
|
|
1339
|
+
if command -v jq &>/dev/null; then
|
|
1340
|
+
local tmp_file
|
|
1341
|
+
tmp_file="$(mktemp)"
|
|
1342
|
+
jq --arg channel "$FEED_CHANNEL" --arg target "$FEED_TARGET_ID" '
|
|
1343
|
+
.plugins //= {} |
|
|
1344
|
+
.plugins.entries //= {} |
|
|
1345
|
+
.plugins.entries["claude-mem"] //= {"enabled": true, "config": {}} |
|
|
1346
|
+
.plugins.entries["claude-mem"].config //= {} |
|
|
1347
|
+
.plugins.entries["claude-mem"].config.observationFeed = {
|
|
1348
|
+
"enabled": true,
|
|
1349
|
+
"channel": $channel,
|
|
1350
|
+
"to": $target
|
|
1351
|
+
}
|
|
1352
|
+
' "$config_file" > "$tmp_file" && mv "$tmp_file" "$config_file"
|
|
1353
|
+
elif command -v python3 &>/dev/null; then
|
|
1354
|
+
INSTALLER_FEED_CHANNEL="$FEED_CHANNEL" \
|
|
1355
|
+
INSTALLER_FEED_TARGET_ID="$FEED_TARGET_ID" \
|
|
1356
|
+
INSTALLER_CONFIG_FILE="$config_file" \
|
|
1357
|
+
python3 -c "
|
|
1358
|
+
import json, os
|
|
1359
|
+
config_path = os.environ['INSTALLER_CONFIG_FILE']
|
|
1360
|
+
channel = os.environ['INSTALLER_FEED_CHANNEL']
|
|
1361
|
+
target_id = os.environ['INSTALLER_FEED_TARGET_ID']
|
|
1362
|
+
|
|
1363
|
+
with open(config_path) as f:
|
|
1364
|
+
config = json.load(f)
|
|
1365
|
+
|
|
1366
|
+
config.setdefault('plugins', {})
|
|
1367
|
+
config['plugins'].setdefault('entries', {})
|
|
1368
|
+
config['plugins']['entries'].setdefault('claude-mem', {'enabled': True, 'config': {}})
|
|
1369
|
+
config['plugins']['entries']['claude-mem'].setdefault('config', {})
|
|
1370
|
+
config['plugins']['entries']['claude-mem']['config']['observationFeed'] = {
|
|
1371
|
+
'enabled': True,
|
|
1372
|
+
'channel': channel,
|
|
1373
|
+
'to': target_id
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
with open(config_path, 'w') as f:
|
|
1377
|
+
json.dump(config, f, indent=2)
|
|
1378
|
+
"
|
|
1379
|
+
else
|
|
1380
|
+
INSTALLER_FEED_CHANNEL="$FEED_CHANNEL" \
|
|
1381
|
+
INSTALLER_FEED_TARGET_ID="$FEED_TARGET_ID" \
|
|
1382
|
+
INSTALLER_CONFIG_FILE="$config_file" \
|
|
1383
|
+
node -e "
|
|
1384
|
+
const fs = require('fs');
|
|
1385
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
1386
|
+
const channel = process.env.INSTALLER_FEED_CHANNEL;
|
|
1387
|
+
const targetId = process.env.INSTALLER_FEED_TARGET_ID;
|
|
1388
|
+
|
|
1389
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1390
|
+
|
|
1391
|
+
if (!config.plugins) config.plugins = {};
|
|
1392
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
1393
|
+
if (!config.plugins.entries['claude-mem']) {
|
|
1394
|
+
config.plugins.entries['claude-mem'] = { enabled: true, config: {} };
|
|
1395
|
+
}
|
|
1396
|
+
if (!config.plugins.entries['claude-mem'].config) {
|
|
1397
|
+
config.plugins.entries['claude-mem'].config = {};
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
config.plugins.entries['claude-mem'].config.observationFeed = {
|
|
1401
|
+
enabled: true,
|
|
1402
|
+
channel: channel,
|
|
1403
|
+
to: targetId
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1407
|
+
"
|
|
1408
|
+
fi
|
|
1409
|
+
|
|
1410
|
+
success "Observation feed config written to ${config_file}"
|
|
1411
|
+
echo ""
|
|
1412
|
+
echo -e " ${COLOR_BOLD}Observation feed summary:${COLOR_RESET}"
|
|
1413
|
+
echo -e " Channel: ${COLOR_CYAN}${FEED_CHANNEL}${COLOR_RESET}"
|
|
1414
|
+
echo -e " Target: ${COLOR_CYAN}${FEED_TARGET_ID}${COLOR_RESET}"
|
|
1415
|
+
echo -e " Enabled: ${COLOR_GREEN}yes${COLOR_RESET}"
|
|
1416
|
+
echo ""
|
|
1417
|
+
info "Restart your OpenClaw gateway to activate the observation feed."
|
|
1418
|
+
info "You should see these log lines:"
|
|
1419
|
+
echo " [claude-mem] Observation feed starting — channel: ${FEED_CHANNEL}, target: ${FEED_TARGET_ID}"
|
|
1420
|
+
echo ""
|
|
1421
|
+
info "After restarting, run /claude-mem-feed in any OpenClaw chat to verify"
|
|
1422
|
+
info "the feed is connected."
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
print_completion_summary() {
|
|
1426
|
+
local provider_display=""
|
|
1427
|
+
case "$AI_PROVIDER" in
|
|
1428
|
+
claude) provider_display="Claude Max Plan (CLI authentication)" ;;
|
|
1429
|
+
gemini) provider_display="Gemini (gemini-2.5-flash-lite)" ;;
|
|
1430
|
+
openrouter) provider_display="OpenRouter (xiaomi/mimo-v2-flash:free)" ;;
|
|
1431
|
+
*) provider_display="$AI_PROVIDER" ;;
|
|
1432
|
+
esac
|
|
1433
|
+
|
|
1434
|
+
echo ""
|
|
1435
|
+
echo -e "${COLOR_MAGENTA}${COLOR_BOLD}"
|
|
1436
|
+
echo " ┌──────────────────────────────────────────┐"
|
|
1437
|
+
echo " │ Installation Complete! │"
|
|
1438
|
+
echo " └──────────────────────────────────────────┘"
|
|
1439
|
+
echo -e "${COLOR_RESET}"
|
|
1440
|
+
|
|
1441
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Dependencies installed (Bun, uv)"
|
|
1442
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} OpenClaw gateway detected"
|
|
1443
|
+
|
|
1444
|
+
if [[ -n "$WORKER_VERSION" ]]; then
|
|
1445
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} claude-mem v${COLOR_BOLD}${WORKER_VERSION}${COLOR_RESET} installed and running"
|
|
1446
|
+
else
|
|
1447
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} claude-mem plugin installed and enabled"
|
|
1448
|
+
fi
|
|
1449
|
+
|
|
1450
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Memory slot configured"
|
|
1451
|
+
|
|
1452
|
+
if [[ -n "$WORKER_AI_AUTH_METHOD" ]]; then
|
|
1453
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} AI provider: ${COLOR_BOLD}${WORKER_AI_PROVIDER} (${WORKER_AI_AUTH_METHOD})${COLOR_RESET}"
|
|
1454
|
+
else
|
|
1455
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} AI provider: ${COLOR_BOLD}${provider_display}${COLOR_RESET}"
|
|
1456
|
+
fi
|
|
1457
|
+
|
|
1458
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Settings written to ~/.claude-mem/settings.json"
|
|
1459
|
+
|
|
1460
|
+
if [[ -n "$WORKER_PID" ]] && kill -0 "$WORKER_PID" 2>/dev/null; then
|
|
1461
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Worker running on port ${COLOR_BOLD}37777${COLOR_RESET} (PID: ${WORKER_PID})"
|
|
1462
|
+
elif [[ -n "$WORKER_UPTIME" && "$WORKER_UPTIME" =~ ^[0-9]+$ ]] && (( WORKER_UPTIME > 0 )); then
|
|
1463
|
+
local uptime_formatted
|
|
1464
|
+
uptime_formatted="$(format_uptime_ms "$WORKER_UPTIME")"
|
|
1465
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Worker running on port ${COLOR_BOLD}37777${COLOR_RESET} (PID: ${WORKER_REPORTED_PID}, uptime: ${uptime_formatted})"
|
|
1466
|
+
else
|
|
1467
|
+
echo -e " ${COLOR_YELLOW}⚠${COLOR_RESET} Worker may not be running — check logs at ~/.claude-mem/logs/"
|
|
1468
|
+
fi
|
|
1469
|
+
|
|
1470
|
+
if [[ "$WORKER_INITIALIZED" != "true" ]] && { [[ -n "$WORKER_REPORTED_PID" ]] || { [[ -n "$WORKER_PID" ]] && kill -0 "$WORKER_PID" 2>/dev/null; }; }; then
|
|
1471
|
+
echo -e " ${COLOR_YELLOW}⚠${COLOR_RESET} Worker is starting but still initializing (this is normal on first run)"
|
|
1472
|
+
fi
|
|
1473
|
+
|
|
1474
|
+
if [[ "$FEED_CONFIGURED" == "true" ]]; then
|
|
1475
|
+
echo -e " ${COLOR_GREEN}✓${COLOR_RESET} Observation feed: ${COLOR_BOLD}${FEED_CHANNEL}${COLOR_RESET} → ${FEED_TARGET_ID}"
|
|
1476
|
+
else
|
|
1477
|
+
echo -e " ${COLOR_YELLOW}─${COLOR_RESET} Observation feed: not configured (optional)"
|
|
1478
|
+
echo -e " Configure later in ~/.openclaw/openclaw.json under"
|
|
1479
|
+
echo -e " plugins.entries.claude-mem.config.observationFeed"
|
|
1480
|
+
fi
|
|
1481
|
+
|
|
1482
|
+
echo ""
|
|
1483
|
+
echo -e " ${COLOR_BOLD}What's next?${COLOR_RESET}"
|
|
1484
|
+
echo ""
|
|
1485
|
+
echo -e " ${COLOR_CYAN}1.${COLOR_RESET} Restart your OpenClaw gateway to load the plugin"
|
|
1486
|
+
echo -e " ${COLOR_CYAN}2.${COLOR_RESET} Verify with ${COLOR_BOLD}/claude-mem-status${COLOR_RESET} in any OpenClaw chat"
|
|
1487
|
+
echo -e " ${COLOR_CYAN}3.${COLOR_RESET} Check the viewer UI at ${COLOR_BOLD}http://localhost:37777${COLOR_RESET}"
|
|
1488
|
+
if [[ "$FEED_CONFIGURED" == "true" ]]; then
|
|
1489
|
+
echo -e " ${COLOR_CYAN}4.${COLOR_RESET} Run ${COLOR_BOLD}/claude-mem-feed${COLOR_RESET} to check feed status"
|
|
1490
|
+
fi
|
|
1491
|
+
echo ""
|
|
1492
|
+
echo -e " ${COLOR_BOLD}To re-run this installer:${COLOR_RESET}"
|
|
1493
|
+
echo " bash <(curl -fsSL https://install.cmem.ai/openclaw.sh)"
|
|
1494
|
+
echo ""
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
main() {
|
|
1498
|
+
setup_tty
|
|
1499
|
+
print_banner
|
|
1500
|
+
detect_platform
|
|
1501
|
+
|
|
1502
|
+
echo ""
|
|
1503
|
+
info "${COLOR_BOLD}[1/8]${COLOR_RESET} Checking dependencies..."
|
|
1504
|
+
echo ""
|
|
1505
|
+
|
|
1506
|
+
if ! check_bun; then
|
|
1507
|
+
install_bun
|
|
1508
|
+
fi
|
|
1509
|
+
|
|
1510
|
+
if ! check_uv; then
|
|
1511
|
+
install_uv
|
|
1512
|
+
fi
|
|
1513
|
+
|
|
1514
|
+
echo ""
|
|
1515
|
+
success "All dependencies satisfied"
|
|
1516
|
+
|
|
1517
|
+
echo ""
|
|
1518
|
+
info "${COLOR_BOLD}[2/8]${COLOR_RESET} Locating OpenClaw gateway..."
|
|
1519
|
+
check_openclaw
|
|
1520
|
+
|
|
1521
|
+
echo ""
|
|
1522
|
+
info "${COLOR_BOLD}[3/8]${COLOR_RESET} Installing claude-mem plugin..."
|
|
1523
|
+
|
|
1524
|
+
if [[ "$UPGRADE_MODE" == "true" ]] && is_claude_mem_installed; then
|
|
1525
|
+
success "claude-mem already installed at ${CLAUDE_MEM_INSTALL_DIR}"
|
|
1526
|
+
info "Upgrade mode: skipping clone/build/register, updating settings only"
|
|
1527
|
+
else
|
|
1528
|
+
install_plugin
|
|
1529
|
+
fi
|
|
1530
|
+
|
|
1531
|
+
echo ""
|
|
1532
|
+
info "${COLOR_BOLD}[4/8]${COLOR_RESET} Configuring memory slot..."
|
|
1533
|
+
configure_memory_slot
|
|
1534
|
+
|
|
1535
|
+
echo ""
|
|
1536
|
+
info "${COLOR_BOLD}[5/8]${COLOR_RESET} AI provider setup..."
|
|
1537
|
+
setup_ai_provider
|
|
1538
|
+
|
|
1539
|
+
echo ""
|
|
1540
|
+
info "${COLOR_BOLD}[6/8]${COLOR_RESET} Writing settings..."
|
|
1541
|
+
write_settings
|
|
1542
|
+
|
|
1543
|
+
echo ""
|
|
1544
|
+
info "${COLOR_BOLD}[7/8]${COLOR_RESET} Starting worker service..."
|
|
1545
|
+
|
|
1546
|
+
if check_port_37777; then
|
|
1547
|
+
warn "Port 37777 is already in use (worker may already be running)"
|
|
1548
|
+
info "Checking if the existing service is healthy..."
|
|
1549
|
+
if verify_health; then
|
|
1550
|
+
local expected_version=""
|
|
1551
|
+
if [[ -n "$CLAUDE_MEM_INSTALL_DIR" ]] || find_claude_mem_install_dir; then
|
|
1552
|
+
expected_version="$(INSTALLER_PKG="${CLAUDE_MEM_INSTALL_DIR}/package.json" node -e "
|
|
1553
|
+
try { process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.INSTALLER_PKG, 'utf8')).version || ''); }
|
|
1554
|
+
catch(e) {}
|
|
1555
|
+
" 2>/dev/null)" || true
|
|
1556
|
+
fi
|
|
1557
|
+
|
|
1558
|
+
local needs_restart=""
|
|
1559
|
+
|
|
1560
|
+
if [[ "$PLUGIN_FRESHLY_INSTALLED" == "true" ]]; then
|
|
1561
|
+
if [[ -n "$WORKER_VERSION" && -n "$expected_version" && "$WORKER_VERSION" != "$expected_version" ]]; then
|
|
1562
|
+
info "Upgrading worker from v${WORKER_VERSION} to v${expected_version}..."
|
|
1563
|
+
else
|
|
1564
|
+
info "Plugin files updated — restarting worker to load new code..."
|
|
1565
|
+
fi
|
|
1566
|
+
needs_restart="true"
|
|
1567
|
+
fi
|
|
1568
|
+
|
|
1569
|
+
if [[ "$needs_restart" != "true" && -n "$WORKER_VERSION" && -n "$expected_version" && "$WORKER_VERSION" != "$expected_version" ]]; then
|
|
1570
|
+
info "Upgrading worker from v${WORKER_VERSION} to v${expected_version}..."
|
|
1571
|
+
needs_restart="true"
|
|
1572
|
+
fi
|
|
1573
|
+
|
|
1574
|
+
if [[ "$needs_restart" != "true" && -n "$WORKER_AI_PROVIDER" && -n "$AI_PROVIDER" && "$WORKER_AI_PROVIDER" != "$AI_PROVIDER" ]]; then
|
|
1575
|
+
warn "Worker is using ${WORKER_AI_PROVIDER} but you configured ${AI_PROVIDER} — restarting to apply"
|
|
1576
|
+
needs_restart="true"
|
|
1577
|
+
fi
|
|
1578
|
+
|
|
1579
|
+
if [[ "$needs_restart" == "true" ]]; then
|
|
1580
|
+
info "Stopping existing worker..."
|
|
1581
|
+
curl -s -X POST "http://127.0.0.1:37777/api/admin/shutdown" >/dev/null 2>&1 || true
|
|
1582
|
+
sleep 2
|
|
1583
|
+
|
|
1584
|
+
if check_port_37777; then
|
|
1585
|
+
if [[ -n "$WORKER_REPORTED_PID" ]]; then
|
|
1586
|
+
kill "$WORKER_REPORTED_PID" 2>/dev/null || true
|
|
1587
|
+
sleep 1
|
|
1588
|
+
fi
|
|
1589
|
+
local pid_file="${HOME}/.claude-mem/worker.pid"
|
|
1590
|
+
if [[ -f "$pid_file" ]]; then
|
|
1591
|
+
local file_pid
|
|
1592
|
+
file_pid="$(INSTALLER_PID_FILE="$pid_file" node -e "
|
|
1593
|
+
try { process.stdout.write(String(JSON.parse(require('fs').readFileSync(process.env.INSTALLER_PID_FILE, 'utf8')).pid || '')); }
|
|
1594
|
+
catch(e) {}
|
|
1595
|
+
" 2>/dev/null)" || true
|
|
1596
|
+
if [[ -n "$file_pid" ]]; then
|
|
1597
|
+
kill "$file_pid" 2>/dev/null || true
|
|
1598
|
+
sleep 1
|
|
1599
|
+
fi
|
|
1600
|
+
fi
|
|
1601
|
+
fi
|
|
1602
|
+
|
|
1603
|
+
if start_worker; then
|
|
1604
|
+
verify_health || true
|
|
1605
|
+
else
|
|
1606
|
+
warn "Worker restart failed — you can start it manually later"
|
|
1607
|
+
fi
|
|
1608
|
+
else
|
|
1609
|
+
local uptime_display=""
|
|
1610
|
+
if [[ -n "$WORKER_UPTIME" && "$WORKER_UPTIME" =~ ^[0-9]+$ && "$WORKER_UPTIME" != "0" ]]; then
|
|
1611
|
+
uptime_display="$(format_uptime_ms "$WORKER_UPTIME")"
|
|
1612
|
+
fi
|
|
1613
|
+
|
|
1614
|
+
local status_parts=""
|
|
1615
|
+
if [[ -n "$WORKER_VERSION" ]]; then
|
|
1616
|
+
status_parts="v${WORKER_VERSION}"
|
|
1617
|
+
fi
|
|
1618
|
+
if [[ -n "$WORKER_AI_PROVIDER" ]]; then
|
|
1619
|
+
status_parts="${status_parts:+${status_parts}, }${WORKER_AI_PROVIDER}"
|
|
1620
|
+
fi
|
|
1621
|
+
if [[ -n "$uptime_display" ]]; then
|
|
1622
|
+
status_parts="${status_parts:+${status_parts}, }uptime: ${uptime_display}"
|
|
1623
|
+
fi
|
|
1624
|
+
|
|
1625
|
+
if [[ -n "$status_parts" ]]; then
|
|
1626
|
+
success "Existing worker is healthy (${status_parts}) — skipping startup"
|
|
1627
|
+
else
|
|
1628
|
+
success "Existing worker is healthy — skipping startup"
|
|
1629
|
+
fi
|
|
1630
|
+
fi
|
|
1631
|
+
else
|
|
1632
|
+
warn "Port 37777 is occupied but not responding to health checks"
|
|
1633
|
+
warn "Another process may be using this port. Stop it and re-run the installer,"
|
|
1634
|
+
warn "or change CLAUDE_MEM_WORKER_PORT in ~/.claude-mem/settings.json"
|
|
1635
|
+
fi
|
|
1636
|
+
else
|
|
1637
|
+
if start_worker; then
|
|
1638
|
+
verify_health || true
|
|
1639
|
+
else
|
|
1640
|
+
warn "Worker startup failed — you can start it manually later"
|
|
1641
|
+
warn " cd ~/.openclaw/extensions/claude-mem && bun plugin/scripts/worker-service.cjs"
|
|
1642
|
+
fi
|
|
1643
|
+
fi
|
|
1644
|
+
|
|
1645
|
+
echo ""
|
|
1646
|
+
info "${COLOR_BOLD}[8/8]${COLOR_RESET} Observation feed setup..."
|
|
1647
|
+
setup_observation_feed
|
|
1648
|
+
write_observation_feed_config
|
|
1649
|
+
|
|
1650
|
+
print_completion_summary
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
main "$@"
|