@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,2086 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
INSTALL_SCRIPT="${SCRIPT_DIR}/install.sh"
|
|
6
|
+
|
|
7
|
+
TESTS_RUN=0
|
|
8
|
+
TESTS_PASSED=0
|
|
9
|
+
TESTS_FAILED=0
|
|
10
|
+
|
|
11
|
+
test_pass() {
|
|
12
|
+
TESTS_RUN=$((TESTS_RUN + 1))
|
|
13
|
+
TESTS_PASSED=$((TESTS_PASSED + 1))
|
|
14
|
+
echo -e "\033[0;32m✓\033[0m $1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test_fail() {
|
|
18
|
+
TESTS_RUN=$((TESTS_RUN + 1))
|
|
19
|
+
TESTS_FAILED=$((TESTS_FAILED + 1))
|
|
20
|
+
echo -e "\033[0;31m✗\033[0m $1"
|
|
21
|
+
if [[ -n "${2:-}" ]]; then
|
|
22
|
+
echo " Detail: $2"
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert_eq() {
|
|
27
|
+
local expected="$1" actual="$2" msg="$3"
|
|
28
|
+
if [[ "$expected" == "$actual" ]]; then
|
|
29
|
+
test_pass "$msg"
|
|
30
|
+
else
|
|
31
|
+
test_fail "$msg" "expected='${expected}' actual='${actual}'"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
assert_contains() {
|
|
36
|
+
local haystack="$1" needle="$2" msg="$3"
|
|
37
|
+
if [[ "$haystack" == *"$needle"* ]]; then
|
|
38
|
+
test_pass "$msg"
|
|
39
|
+
else
|
|
40
|
+
test_fail "$msg" "expected string to contain '${needle}'"
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
assert_file_exists() {
|
|
45
|
+
local filepath="$1" msg="$2"
|
|
46
|
+
if [[ -f "$filepath" ]]; then
|
|
47
|
+
test_pass "$msg"
|
|
48
|
+
else
|
|
49
|
+
test_fail "$msg" "file not found: ${filepath}"
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
source_install_functions() {
|
|
54
|
+
local tmp_source
|
|
55
|
+
tmp_source="$(mktemp)"
|
|
56
|
+
sed '$ d' "$INSTALL_SCRIPT" > "$tmp_source"
|
|
57
|
+
echo 'main() { :; }' >> "$tmp_source"
|
|
58
|
+
TERM=dumb source "$tmp_source"
|
|
59
|
+
rm -f "$tmp_source"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
source_install_functions
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
echo "=== detect_platform() ==="
|
|
66
|
+
|
|
67
|
+
test_detect_platform_returns_valid_string() {
|
|
68
|
+
PLATFORM=""
|
|
69
|
+
IS_WSL=""
|
|
70
|
+
detect_platform >/dev/null 2>&1
|
|
71
|
+
|
|
72
|
+
case "$PLATFORM" in
|
|
73
|
+
macos|linux|windows)
|
|
74
|
+
test_pass "detect_platform sets PLATFORM='${PLATFORM}'"
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
test_fail "detect_platform returned unexpected PLATFORM='${PLATFORM}'" "expected macos, linux, or windows"
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test_detect_platform_returns_valid_string
|
|
83
|
+
|
|
84
|
+
test_detect_platform_is_idempotent() {
|
|
85
|
+
PLATFORM=""
|
|
86
|
+
IS_WSL=""
|
|
87
|
+
detect_platform >/dev/null 2>&1
|
|
88
|
+
local first_platform="$PLATFORM"
|
|
89
|
+
|
|
90
|
+
PLATFORM=""
|
|
91
|
+
IS_WSL=""
|
|
92
|
+
detect_platform >/dev/null 2>&1
|
|
93
|
+
local second_platform="$PLATFORM"
|
|
94
|
+
|
|
95
|
+
assert_eq "$first_platform" "$second_platform" "detect_platform returns consistent results"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test_detect_platform_is_idempotent
|
|
99
|
+
|
|
100
|
+
test_detect_platform_sets_iswsl_empty_on_non_wsl() {
|
|
101
|
+
PLATFORM=""
|
|
102
|
+
IS_WSL=""
|
|
103
|
+
detect_platform >/dev/null 2>&1
|
|
104
|
+
|
|
105
|
+
if [[ "$PLATFORM" == "linux" ]] && grep -qi microsoft /proc/version 2>/dev/null; then
|
|
106
|
+
assert_eq "true" "$IS_WSL" "IS_WSL is 'true' on WSL"
|
|
107
|
+
else
|
|
108
|
+
assert_eq "" "${IS_WSL:-}" "IS_WSL is empty on non-WSL platform"
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
test_detect_platform_sets_iswsl_empty_on_non_wsl
|
|
113
|
+
|
|
114
|
+
echo ""
|
|
115
|
+
echo "=== check_bun() ==="
|
|
116
|
+
|
|
117
|
+
test_check_bun_detects_installed_bun() {
|
|
118
|
+
if command -v bun &>/dev/null; then
|
|
119
|
+
BUN_PATH=""
|
|
120
|
+
if check_bun >/dev/null 2>&1; then
|
|
121
|
+
test_pass "check_bun succeeds when bun is installed"
|
|
122
|
+
else
|
|
123
|
+
test_fail "check_bun should succeed when bun is installed"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [[ -n "$BUN_PATH" ]]; then
|
|
127
|
+
test_pass "check_bun sets BUN_PATH='${BUN_PATH}'"
|
|
128
|
+
else
|
|
129
|
+
test_fail "check_bun should set BUN_PATH when bun is found"
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
test_pass "check_bun test (installed): skipped (bun not installed)"
|
|
133
|
+
test_pass "check_bun BUN_PATH test: skipped (bun not installed)"
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
test_check_bun_detects_installed_bun
|
|
138
|
+
|
|
139
|
+
test_check_bun_fails_when_not_found() {
|
|
140
|
+
local fake_home
|
|
141
|
+
fake_home="$(mktemp -d)"
|
|
142
|
+
local exit_code=0
|
|
143
|
+
bash -c '
|
|
144
|
+
set -euo pipefail
|
|
145
|
+
TERM=dumb
|
|
146
|
+
export HOME="'"$fake_home"'"
|
|
147
|
+
tmp=$(mktemp)
|
|
148
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
149
|
+
echo "main() { :; }" >> "$tmp"
|
|
150
|
+
source "$tmp"
|
|
151
|
+
rm -f "$tmp"
|
|
152
|
+
PATH="/nonexistent"
|
|
153
|
+
BUN_PATH=""
|
|
154
|
+
check_bun
|
|
155
|
+
' >/dev/null 2>&1 || exit_code=$?
|
|
156
|
+
rm -rf "$fake_home"
|
|
157
|
+
|
|
158
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
159
|
+
test_pass "check_bun returns failure when bun is not in PATH"
|
|
160
|
+
else
|
|
161
|
+
test_fail "check_bun should return failure when bun is not in PATH"
|
|
162
|
+
fi
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
test_check_bun_fails_when_not_found
|
|
166
|
+
|
|
167
|
+
test_find_bun_path_checks_home_bun_bin() {
|
|
168
|
+
local fake_home
|
|
169
|
+
fake_home="$(mktemp -d)"
|
|
170
|
+
local saved_home="$HOME"
|
|
171
|
+
HOME="$fake_home"
|
|
172
|
+
BUN_PATH=""
|
|
173
|
+
|
|
174
|
+
mkdir -p "${fake_home}/.bun/bin"
|
|
175
|
+
cat > "${fake_home}/.bun/bin/bun" <<'FAKEBUN'
|
|
176
|
+
echo "1.2.0"
|
|
177
|
+
FAKEBUN
|
|
178
|
+
chmod +x "${fake_home}/.bun/bin/bun"
|
|
179
|
+
|
|
180
|
+
local saved_path="$PATH"
|
|
181
|
+
PATH="/nonexistent"
|
|
182
|
+
|
|
183
|
+
if find_bun_path 2>/dev/null; then
|
|
184
|
+
assert_eq "${fake_home}/.bun/bin/bun" "$BUN_PATH" "find_bun_path finds bun in ~/.bun/bin/"
|
|
185
|
+
else
|
|
186
|
+
test_fail "find_bun_path should find bun in ~/.bun/bin/"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
HOME="$saved_home"
|
|
190
|
+
PATH="$saved_path"
|
|
191
|
+
rm -rf "$fake_home"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
test_find_bun_path_checks_home_bun_bin
|
|
195
|
+
|
|
196
|
+
echo ""
|
|
197
|
+
echo "=== check_uv() ==="
|
|
198
|
+
|
|
199
|
+
test_check_uv_detects_installed_uv() {
|
|
200
|
+
if command -v uv &>/dev/null; then
|
|
201
|
+
UV_PATH=""
|
|
202
|
+
if check_uv >/dev/null 2>&1; then
|
|
203
|
+
test_pass "check_uv succeeds when uv is installed"
|
|
204
|
+
else
|
|
205
|
+
test_fail "check_uv should succeed when uv is installed"
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
if [[ -n "$UV_PATH" ]]; then
|
|
209
|
+
test_pass "check_uv sets UV_PATH='${UV_PATH}'"
|
|
210
|
+
else
|
|
211
|
+
test_fail "check_uv should set UV_PATH when uv is found"
|
|
212
|
+
fi
|
|
213
|
+
else
|
|
214
|
+
test_pass "check_uv test (installed): skipped (uv not installed)"
|
|
215
|
+
test_pass "check_uv UV_PATH test: skipped (uv not installed)"
|
|
216
|
+
fi
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
test_check_uv_detects_installed_uv
|
|
220
|
+
|
|
221
|
+
test_check_uv_fails_when_not_found() {
|
|
222
|
+
if [[ -x "/usr/local/bin/uv" ]] || [[ -x "/opt/homebrew/bin/uv" ]]; then
|
|
223
|
+
test_pass "check_uv not-found test: skipped (uv installed at system path)"
|
|
224
|
+
return 0
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
local fake_home
|
|
228
|
+
fake_home="$(mktemp -d)"
|
|
229
|
+
local exit_code=0
|
|
230
|
+
bash -c '
|
|
231
|
+
set -euo pipefail
|
|
232
|
+
TERM=dumb
|
|
233
|
+
export HOME="'"$fake_home"'"
|
|
234
|
+
tmp=$(mktemp)
|
|
235
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
236
|
+
echo "main() { :; }" >> "$tmp"
|
|
237
|
+
source "$tmp"
|
|
238
|
+
rm -f "$tmp"
|
|
239
|
+
PATH="/nonexistent"
|
|
240
|
+
UV_PATH=""
|
|
241
|
+
check_uv
|
|
242
|
+
' >/dev/null 2>&1 || exit_code=$?
|
|
243
|
+
rm -rf "$fake_home"
|
|
244
|
+
|
|
245
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
246
|
+
test_pass "check_uv returns failure when uv is not in PATH"
|
|
247
|
+
else
|
|
248
|
+
test_fail "check_uv should return failure when uv is not in PATH"
|
|
249
|
+
fi
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
test_check_uv_fails_when_not_found
|
|
253
|
+
|
|
254
|
+
test_find_uv_path_checks_local_bin() {
|
|
255
|
+
local fake_home
|
|
256
|
+
fake_home="$(mktemp -d)"
|
|
257
|
+
local saved_home="$HOME"
|
|
258
|
+
HOME="$fake_home"
|
|
259
|
+
UV_PATH=""
|
|
260
|
+
|
|
261
|
+
mkdir -p "${fake_home}/.local/bin"
|
|
262
|
+
cat > "${fake_home}/.local/bin/uv" <<'FAKEUV'
|
|
263
|
+
echo "uv 0.4.0"
|
|
264
|
+
FAKEUV
|
|
265
|
+
chmod +x "${fake_home}/.local/bin/uv"
|
|
266
|
+
|
|
267
|
+
local saved_path="$PATH"
|
|
268
|
+
PATH="/nonexistent"
|
|
269
|
+
|
|
270
|
+
if find_uv_path 2>/dev/null; then
|
|
271
|
+
assert_eq "${fake_home}/.local/bin/uv" "$UV_PATH" "find_uv_path finds uv in ~/.local/bin/"
|
|
272
|
+
else
|
|
273
|
+
test_fail "find_uv_path should find uv in ~/.local/bin/"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
HOME="$saved_home"
|
|
277
|
+
PATH="$saved_path"
|
|
278
|
+
rm -rf "$fake_home"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
test_find_uv_path_checks_local_bin
|
|
282
|
+
|
|
283
|
+
echo ""
|
|
284
|
+
echo "=== find_openclaw() ==="
|
|
285
|
+
|
|
286
|
+
ORIGINAL_PATH="$PATH"
|
|
287
|
+
ORIGINAL_HOME="$HOME"
|
|
288
|
+
|
|
289
|
+
test_find_openclaw_not_found() {
|
|
290
|
+
local fake_home
|
|
291
|
+
fake_home="$(mktemp -d)"
|
|
292
|
+
HOME="$fake_home"
|
|
293
|
+
PATH="/nonexistent"
|
|
294
|
+
OPENCLAW_PATH=""
|
|
295
|
+
|
|
296
|
+
if find_openclaw 2>/dev/null; then
|
|
297
|
+
test_fail "find_openclaw should return 1 when openclaw.mjs is not found"
|
|
298
|
+
else
|
|
299
|
+
test_pass "find_openclaw returns 1 when not found"
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
assert_eq "" "$OPENCLAW_PATH" "OPENCLAW_PATH is empty when not found"
|
|
303
|
+
|
|
304
|
+
HOME="$ORIGINAL_HOME"
|
|
305
|
+
PATH="$ORIGINAL_PATH"
|
|
306
|
+
rm -rf "$fake_home"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
test_find_openclaw_not_found
|
|
310
|
+
|
|
311
|
+
test_find_openclaw_in_home() {
|
|
312
|
+
local fake_home
|
|
313
|
+
fake_home="$(mktemp -d)"
|
|
314
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
315
|
+
touch "${fake_home}/.openclaw/openclaw.mjs"
|
|
316
|
+
|
|
317
|
+
HOME="$fake_home"
|
|
318
|
+
PATH="/nonexistent"
|
|
319
|
+
OPENCLAW_PATH=""
|
|
320
|
+
|
|
321
|
+
if find_openclaw 2>/dev/null; then
|
|
322
|
+
test_pass "find_openclaw finds openclaw.mjs in ~/.openclaw/"
|
|
323
|
+
assert_eq "${fake_home}/.openclaw/openclaw.mjs" "$OPENCLAW_PATH" "OPENCLAW_PATH set correctly"
|
|
324
|
+
else
|
|
325
|
+
test_fail "find_openclaw should find openclaw.mjs in ~/.openclaw/"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
HOME="$ORIGINAL_HOME"
|
|
329
|
+
PATH="$ORIGINAL_PATH"
|
|
330
|
+
rm -rf "$fake_home"
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
test_find_openclaw_in_home
|
|
334
|
+
|
|
335
|
+
echo ""
|
|
336
|
+
echo "=== configure_memory_slot() ==="
|
|
337
|
+
|
|
338
|
+
test_configure_new_config() {
|
|
339
|
+
local fake_home
|
|
340
|
+
fake_home="$(mktemp -d)"
|
|
341
|
+
HOME="$fake_home"
|
|
342
|
+
|
|
343
|
+
configure_memory_slot >/dev/null 2>&1
|
|
344
|
+
|
|
345
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
346
|
+
assert_file_exists "$config_file" "Config file created at ~/.openclaw/openclaw.json"
|
|
347
|
+
|
|
348
|
+
local memory_slot
|
|
349
|
+
memory_slot="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.slots.memory);")"
|
|
350
|
+
assert_eq "claude-mem" "$memory_slot" "Memory slot set to claude-mem in new config"
|
|
351
|
+
|
|
352
|
+
local enabled
|
|
353
|
+
enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
|
354
|
+
assert_eq "true" "$enabled" "claude-mem entry is enabled in new config"
|
|
355
|
+
|
|
356
|
+
local worker_port
|
|
357
|
+
worker_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
|
358
|
+
assert_eq "37777" "$worker_port" "Worker port is 37777 in new config"
|
|
359
|
+
|
|
360
|
+
HOME="$ORIGINAL_HOME"
|
|
361
|
+
rm -rf "$fake_home"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
test_configure_new_config
|
|
365
|
+
|
|
366
|
+
test_configure_existing_config() {
|
|
367
|
+
local fake_home
|
|
368
|
+
fake_home="$(mktemp -d)"
|
|
369
|
+
HOME="$fake_home"
|
|
370
|
+
|
|
371
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
372
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
373
|
+
node -e "
|
|
374
|
+
const config = {
|
|
375
|
+
gateway: { mode: 'local' },
|
|
376
|
+
plugins: {
|
|
377
|
+
slots: { memory: 'memory-core' },
|
|
378
|
+
entries: {
|
|
379
|
+
'some-other-plugin': { enabled: true }
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
|
384
|
+
"
|
|
385
|
+
|
|
386
|
+
configure_memory_slot >/dev/null 2>&1
|
|
387
|
+
|
|
388
|
+
local memory_slot
|
|
389
|
+
memory_slot="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.slots.memory);")"
|
|
390
|
+
assert_eq "claude-mem" "$memory_slot" "Memory slot updated from memory-core to claude-mem"
|
|
391
|
+
|
|
392
|
+
local gateway_mode
|
|
393
|
+
gateway_mode="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.gateway.mode);")"
|
|
394
|
+
assert_eq "local" "$gateway_mode" "Existing gateway.mode setting preserved"
|
|
395
|
+
|
|
396
|
+
local other_plugin
|
|
397
|
+
other_plugin="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['some-other-plugin'].enabled);")"
|
|
398
|
+
assert_eq "true" "$other_plugin" "Existing plugin entries preserved"
|
|
399
|
+
|
|
400
|
+
local cm_enabled
|
|
401
|
+
cm_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
|
402
|
+
assert_eq "true" "$cm_enabled" "claude-mem entry added and enabled"
|
|
403
|
+
|
|
404
|
+
HOME="$ORIGINAL_HOME"
|
|
405
|
+
rm -rf "$fake_home"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
test_configure_existing_config
|
|
409
|
+
|
|
410
|
+
test_configure_preserves_existing_cm_config() {
|
|
411
|
+
local fake_home
|
|
412
|
+
fake_home="$(mktemp -d)"
|
|
413
|
+
HOME="$fake_home"
|
|
414
|
+
|
|
415
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
416
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
417
|
+
node -e "
|
|
418
|
+
const config = {
|
|
419
|
+
plugins: {
|
|
420
|
+
slots: { memory: 'memory-core' },
|
|
421
|
+
entries: {
|
|
422
|
+
'claude-mem': {
|
|
423
|
+
enabled: false,
|
|
424
|
+
config: {
|
|
425
|
+
workerPort: 38888,
|
|
426
|
+
observationFeed: { enabled: true, channel: 'telegram', to: '12345' }
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
|
433
|
+
"
|
|
434
|
+
|
|
435
|
+
configure_memory_slot >/dev/null 2>&1
|
|
436
|
+
|
|
437
|
+
local cm_enabled
|
|
438
|
+
cm_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
|
439
|
+
assert_eq "true" "$cm_enabled" "claude-mem entry enabled when previously disabled"
|
|
440
|
+
|
|
441
|
+
local custom_port
|
|
442
|
+
custom_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
|
443
|
+
assert_eq "38888" "$custom_port" "Existing custom workerPort preserved"
|
|
444
|
+
|
|
445
|
+
local feed_channel
|
|
446
|
+
feed_channel="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.channel);")"
|
|
447
|
+
assert_eq "telegram" "$feed_channel" "Existing observationFeed config preserved"
|
|
448
|
+
|
|
449
|
+
HOME="$ORIGINAL_HOME"
|
|
450
|
+
rm -rf "$fake_home"
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
test_configure_preserves_existing_cm_config
|
|
454
|
+
|
|
455
|
+
echo ""
|
|
456
|
+
echo "=== version_gte() ==="
|
|
457
|
+
|
|
458
|
+
if version_gte "1.2.0" "1.1.14"; then
|
|
459
|
+
test_pass "version_gte: 1.2.0 >= 1.1.14"
|
|
460
|
+
else
|
|
461
|
+
test_fail "version_gte: 1.2.0 >= 1.1.14"
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
if version_gte "1.1.14" "1.1.14"; then
|
|
465
|
+
test_pass "version_gte: 1.1.14 >= 1.1.14 (equal)"
|
|
466
|
+
else
|
|
467
|
+
test_fail "version_gte: 1.1.14 >= 1.1.14 (equal)"
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
if ! version_gte "1.0.0" "1.1.14"; then
|
|
471
|
+
test_pass "version_gte: 1.0.0 < 1.1.14"
|
|
472
|
+
else
|
|
473
|
+
test_fail "version_gte: 1.0.0 < 1.1.14"
|
|
474
|
+
fi
|
|
475
|
+
|
|
476
|
+
echo ""
|
|
477
|
+
echo "=== Script structure ==="
|
|
478
|
+
|
|
479
|
+
for fn in find_openclaw check_openclaw install_plugin configure_memory_slot; do
|
|
480
|
+
if declare -f "$fn" &>/dev/null; then
|
|
481
|
+
test_pass "Function ${fn}() is defined"
|
|
482
|
+
else
|
|
483
|
+
test_fail "Function ${fn}() should be defined"
|
|
484
|
+
fi
|
|
485
|
+
done
|
|
486
|
+
|
|
487
|
+
assert_contains "$CLAUDE_MEM_REPO" "github.com/bjlee2024/claude-mem" "CLAUDE_MEM_REPO points to correct repository"
|
|
488
|
+
|
|
489
|
+
for fn in setup_ai_provider write_settings mask_api_key; do
|
|
490
|
+
if declare -f "$fn" &>/dev/null; then
|
|
491
|
+
test_pass "Function ${fn}() is defined"
|
|
492
|
+
else
|
|
493
|
+
test_fail "Function ${fn}() should be defined"
|
|
494
|
+
fi
|
|
495
|
+
done
|
|
496
|
+
|
|
497
|
+
echo ""
|
|
498
|
+
echo "=== mask_api_key() ==="
|
|
499
|
+
|
|
500
|
+
masked=$(mask_api_key "sk-1234567890abcdef")
|
|
501
|
+
assert_eq "***************cdef" "$masked" "mask_api_key masks all but last 4 chars"
|
|
502
|
+
|
|
503
|
+
masked_short=$(mask_api_key "abcd")
|
|
504
|
+
assert_eq "****" "$masked_short" "mask_api_key masks keys <= 4 chars entirely"
|
|
505
|
+
|
|
506
|
+
masked_five=$(mask_api_key "12345")
|
|
507
|
+
assert_eq "*2345" "$masked_five" "mask_api_key masks 5-char key correctly"
|
|
508
|
+
|
|
509
|
+
echo ""
|
|
510
|
+
echo "=== setup_ai_provider() ==="
|
|
511
|
+
|
|
512
|
+
test_setup_ai_provider_non_interactive() {
|
|
513
|
+
local ai_result
|
|
514
|
+
ai_result="$(bash -c '
|
|
515
|
+
set -euo pipefail
|
|
516
|
+
TERM=dumb
|
|
517
|
+
tmp=$(mktemp)
|
|
518
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
519
|
+
echo "main() { :; }" >> "$tmp"
|
|
520
|
+
set -- "--non-interactive"
|
|
521
|
+
source "$tmp"
|
|
522
|
+
rm -f "$tmp"
|
|
523
|
+
setup_ai_provider >/dev/null 2>&1
|
|
524
|
+
echo "$AI_PROVIDER"
|
|
525
|
+
' 2>/dev/null)" || true
|
|
526
|
+
|
|
527
|
+
assert_eq "claude" "$ai_result" "Non-interactive mode defaults to claude provider"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
test_setup_ai_provider_non_interactive
|
|
531
|
+
|
|
532
|
+
echo ""
|
|
533
|
+
echo "=== write_settings() ==="
|
|
534
|
+
|
|
535
|
+
test_write_settings_new_file() {
|
|
536
|
+
local fake_home
|
|
537
|
+
fake_home="$(mktemp -d)"
|
|
538
|
+
HOME="$fake_home"
|
|
539
|
+
AI_PROVIDER="claude"
|
|
540
|
+
AI_PROVIDER_API_KEY=""
|
|
541
|
+
|
|
542
|
+
write_settings >/dev/null 2>&1
|
|
543
|
+
|
|
544
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
545
|
+
assert_file_exists "$settings_file" "settings.json created at ~/.claude-mem/settings.json"
|
|
546
|
+
|
|
547
|
+
local provider
|
|
548
|
+
provider="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_PROVIDER);")"
|
|
549
|
+
assert_eq "claude" "$provider" "CLAUDE_MEM_PROVIDER set to claude"
|
|
550
|
+
|
|
551
|
+
local auth_method
|
|
552
|
+
auth_method="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_CLAUDE_AUTH_METHOD);")"
|
|
553
|
+
assert_eq "cli" "$auth_method" "CLAUDE_MEM_CLAUDE_AUTH_METHOD set to cli for Claude provider"
|
|
554
|
+
|
|
555
|
+
local worker_port
|
|
556
|
+
worker_port="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_WORKER_PORT);")"
|
|
557
|
+
assert_eq "37777" "$worker_port" "CLAUDE_MEM_WORKER_PORT defaults to 37777"
|
|
558
|
+
|
|
559
|
+
local model
|
|
560
|
+
model="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_MODEL);")"
|
|
561
|
+
assert_eq "claude-sonnet-4-6" "$model" "CLAUDE_MEM_MODEL defaults to claude-sonnet-4-6"
|
|
562
|
+
|
|
563
|
+
HOME="$ORIGINAL_HOME"
|
|
564
|
+
rm -rf "$fake_home"
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
test_write_settings_new_file
|
|
568
|
+
|
|
569
|
+
test_write_settings_gemini() {
|
|
570
|
+
local fake_home
|
|
571
|
+
fake_home="$(mktemp -d)"
|
|
572
|
+
HOME="$fake_home"
|
|
573
|
+
AI_PROVIDER="gemini"
|
|
574
|
+
AI_PROVIDER_API_KEY="test-gemini-key-1234"
|
|
575
|
+
|
|
576
|
+
write_settings >/dev/null 2>&1
|
|
577
|
+
|
|
578
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
579
|
+
|
|
580
|
+
local provider
|
|
581
|
+
provider="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_PROVIDER);")"
|
|
582
|
+
assert_eq "gemini" "$provider" "Gemini: CLAUDE_MEM_PROVIDER set to gemini"
|
|
583
|
+
|
|
584
|
+
local api_key
|
|
585
|
+
api_key="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_GEMINI_API_KEY);")"
|
|
586
|
+
assert_eq "test-gemini-key-1234" "$api_key" "Gemini: API key stored in settings"
|
|
587
|
+
|
|
588
|
+
local gemini_model
|
|
589
|
+
gemini_model="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_GEMINI_MODEL);")"
|
|
590
|
+
assert_eq "gemini-2.5-flash-lite" "$gemini_model" "Gemini: model defaults to gemini-2.5-flash-lite"
|
|
591
|
+
|
|
592
|
+
HOME="$ORIGINAL_HOME"
|
|
593
|
+
rm -rf "$fake_home"
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
test_write_settings_gemini
|
|
597
|
+
|
|
598
|
+
test_write_settings_openrouter() {
|
|
599
|
+
local fake_home
|
|
600
|
+
fake_home="$(mktemp -d)"
|
|
601
|
+
HOME="$fake_home"
|
|
602
|
+
AI_PROVIDER="openrouter"
|
|
603
|
+
AI_PROVIDER_API_KEY="sk-or-test-key-5678"
|
|
604
|
+
|
|
605
|
+
write_settings >/dev/null 2>&1
|
|
606
|
+
|
|
607
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
608
|
+
|
|
609
|
+
local provider
|
|
610
|
+
provider="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_PROVIDER);")"
|
|
611
|
+
assert_eq "openrouter" "$provider" "OpenRouter: CLAUDE_MEM_PROVIDER set to openrouter"
|
|
612
|
+
|
|
613
|
+
local api_key
|
|
614
|
+
api_key="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_OPENROUTER_API_KEY);")"
|
|
615
|
+
assert_eq "sk-or-test-key-5678" "$api_key" "OpenRouter: API key stored in settings"
|
|
616
|
+
|
|
617
|
+
local or_model
|
|
618
|
+
or_model="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_OPENROUTER_MODEL);")"
|
|
619
|
+
assert_eq "xiaomi/mimo-v2-flash:free" "$or_model" "OpenRouter: model defaults to xiaomi/mimo-v2-flash:free"
|
|
620
|
+
|
|
621
|
+
HOME="$ORIGINAL_HOME"
|
|
622
|
+
rm -rf "$fake_home"
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
test_write_settings_openrouter
|
|
626
|
+
|
|
627
|
+
test_write_settings_preserves_existing() {
|
|
628
|
+
local fake_home
|
|
629
|
+
fake_home="$(mktemp -d)"
|
|
630
|
+
HOME="$fake_home"
|
|
631
|
+
|
|
632
|
+
mkdir -p "${fake_home}/.claude-mem"
|
|
633
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
634
|
+
node -e "
|
|
635
|
+
const settings = {
|
|
636
|
+
CLAUDE_MEM_PROVIDER: 'gemini',
|
|
637
|
+
CLAUDE_MEM_GEMINI_API_KEY: 'old-key',
|
|
638
|
+
CLAUDE_MEM_WORKER_PORT: '38888',
|
|
639
|
+
CLAUDE_MEM_LOG_LEVEL: 'DEBUG'
|
|
640
|
+
};
|
|
641
|
+
require('fs').writeFileSync('${settings_file}', JSON.stringify(settings, null, 2));
|
|
642
|
+
"
|
|
643
|
+
|
|
644
|
+
AI_PROVIDER="claude"
|
|
645
|
+
AI_PROVIDER_API_KEY=""
|
|
646
|
+
write_settings >/dev/null 2>&1
|
|
647
|
+
|
|
648
|
+
local provider
|
|
649
|
+
provider="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_PROVIDER);")"
|
|
650
|
+
assert_eq "claude" "$provider" "Preserve: provider updated to new selection"
|
|
651
|
+
|
|
652
|
+
local custom_port
|
|
653
|
+
custom_port="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_WORKER_PORT);")"
|
|
654
|
+
assert_eq "38888" "$custom_port" "Preserve: existing custom WORKER_PORT preserved"
|
|
655
|
+
|
|
656
|
+
local log_level
|
|
657
|
+
log_level="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_LOG_LEVEL);")"
|
|
658
|
+
assert_eq "DEBUG" "$log_level" "Preserve: existing custom LOG_LEVEL preserved"
|
|
659
|
+
|
|
660
|
+
HOME="$ORIGINAL_HOME"
|
|
661
|
+
rm -rf "$fake_home"
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
test_write_settings_preserves_existing
|
|
665
|
+
|
|
666
|
+
test_write_settings_complete_schema() {
|
|
667
|
+
local fake_home
|
|
668
|
+
fake_home="$(mktemp -d)"
|
|
669
|
+
HOME="$fake_home"
|
|
670
|
+
AI_PROVIDER="claude"
|
|
671
|
+
AI_PROVIDER_API_KEY=""
|
|
672
|
+
|
|
673
|
+
write_settings >/dev/null 2>&1
|
|
674
|
+
|
|
675
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
676
|
+
|
|
677
|
+
local key_count
|
|
678
|
+
key_count="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(Object.keys(s).length);")"
|
|
679
|
+
|
|
680
|
+
if (( key_count >= 30 )); then
|
|
681
|
+
test_pass "Settings file has ${key_count} keys (complete schema)"
|
|
682
|
+
else
|
|
683
|
+
test_fail "Settings file has ${key_count} keys, expected >= 30" "Schema may be incomplete"
|
|
684
|
+
fi
|
|
685
|
+
|
|
686
|
+
local has_env_key
|
|
687
|
+
has_env_key="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.env !== undefined);")"
|
|
688
|
+
assert_eq "false" "$has_env_key" "Settings uses flat schema (no nested 'env' key)"
|
|
689
|
+
|
|
690
|
+
HOME="$ORIGINAL_HOME"
|
|
691
|
+
rm -rf "$fake_home"
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
test_write_settings_complete_schema
|
|
695
|
+
|
|
696
|
+
echo ""
|
|
697
|
+
echo "=== find_claude_mem_install_dir() ==="
|
|
698
|
+
|
|
699
|
+
test_find_install_dir_not_found() {
|
|
700
|
+
local fake_home
|
|
701
|
+
fake_home="$(mktemp -d)"
|
|
702
|
+
HOME="$fake_home"
|
|
703
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
704
|
+
|
|
705
|
+
if find_claude_mem_install_dir 2>/dev/null; then
|
|
706
|
+
test_fail "find_claude_mem_install_dir should return 1 when not found"
|
|
707
|
+
else
|
|
708
|
+
test_pass "find_claude_mem_install_dir returns 1 when not found"
|
|
709
|
+
fi
|
|
710
|
+
|
|
711
|
+
assert_eq "" "$CLAUDE_MEM_INSTALL_DIR" "CLAUDE_MEM_INSTALL_DIR is empty when not found"
|
|
712
|
+
|
|
713
|
+
HOME="$ORIGINAL_HOME"
|
|
714
|
+
rm -rf "$fake_home"
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
test_find_install_dir_not_found
|
|
718
|
+
|
|
719
|
+
test_find_install_dir_openclaw_extensions() {
|
|
720
|
+
local fake_home
|
|
721
|
+
fake_home="$(mktemp -d)"
|
|
722
|
+
HOME="$fake_home"
|
|
723
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
724
|
+
|
|
725
|
+
mkdir -p "${fake_home}/.openclaw/extensions/claude-mem/plugin/scripts"
|
|
726
|
+
touch "${fake_home}/.openclaw/extensions/claude-mem/plugin/scripts/worker-service.cjs"
|
|
727
|
+
|
|
728
|
+
if find_claude_mem_install_dir 2>/dev/null; then
|
|
729
|
+
test_pass "find_claude_mem_install_dir finds dir in ~/.openclaw/extensions/claude-mem/"
|
|
730
|
+
assert_eq "${fake_home}/.openclaw/extensions/claude-mem" "$CLAUDE_MEM_INSTALL_DIR" "CLAUDE_MEM_INSTALL_DIR set correctly for openclaw extensions"
|
|
731
|
+
else
|
|
732
|
+
test_fail "find_claude_mem_install_dir should find dir in ~/.openclaw/extensions/claude-mem/"
|
|
733
|
+
fi
|
|
734
|
+
|
|
735
|
+
HOME="$ORIGINAL_HOME"
|
|
736
|
+
rm -rf "$fake_home"
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
test_find_install_dir_openclaw_extensions
|
|
740
|
+
|
|
741
|
+
test_find_install_dir_marketplace() {
|
|
742
|
+
local fake_home
|
|
743
|
+
fake_home="$(mktemp -d)"
|
|
744
|
+
HOME="$fake_home"
|
|
745
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
746
|
+
|
|
747
|
+
mkdir -p "${fake_home}/.claude/plugins/marketplaces/bjlee2024/plugin/scripts"
|
|
748
|
+
touch "${fake_home}/.claude/plugins/marketplaces/bjlee2024/plugin/scripts/worker-service.cjs"
|
|
749
|
+
|
|
750
|
+
if find_claude_mem_install_dir 2>/dev/null; then
|
|
751
|
+
test_pass "find_claude_mem_install_dir finds dir in marketplace path"
|
|
752
|
+
assert_eq "${fake_home}/.claude/plugins/marketplaces/bjlee2024" "$CLAUDE_MEM_INSTALL_DIR" "CLAUDE_MEM_INSTALL_DIR set correctly for marketplace"
|
|
753
|
+
else
|
|
754
|
+
test_fail "find_claude_mem_install_dir should find dir in marketplace path"
|
|
755
|
+
fi
|
|
756
|
+
|
|
757
|
+
HOME="$ORIGINAL_HOME"
|
|
758
|
+
rm -rf "$fake_home"
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
test_find_install_dir_marketplace
|
|
762
|
+
|
|
763
|
+
echo ""
|
|
764
|
+
echo "=== start_worker() ==="
|
|
765
|
+
|
|
766
|
+
test_start_worker_no_install_dir() {
|
|
767
|
+
local fake_home
|
|
768
|
+
fake_home="$(mktemp -d)"
|
|
769
|
+
HOME="$fake_home"
|
|
770
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
771
|
+
|
|
772
|
+
local output
|
|
773
|
+
if output="$(start_worker 2>&1)"; then
|
|
774
|
+
test_fail "start_worker should fail when install dir not found"
|
|
775
|
+
else
|
|
776
|
+
test_pass "start_worker returns error when install dir not found"
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
assert_contains "$output" "Cannot find claude-mem plugin installation directory" "start_worker error message mentions install dir"
|
|
780
|
+
|
|
781
|
+
HOME="$ORIGINAL_HOME"
|
|
782
|
+
rm -rf "$fake_home"
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
test_start_worker_no_install_dir
|
|
786
|
+
|
|
787
|
+
echo ""
|
|
788
|
+
echo "=== verify_health() ==="
|
|
789
|
+
|
|
790
|
+
test_verify_health_no_server() {
|
|
791
|
+
local result
|
|
792
|
+
result="$(bash -c '
|
|
793
|
+
set -euo pipefail
|
|
794
|
+
TERM=dumb
|
|
795
|
+
tmp=$(mktemp)
|
|
796
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
797
|
+
echo "main() { :; }" >> "$tmp"
|
|
798
|
+
source "$tmp"
|
|
799
|
+
rm -f "$tmp"
|
|
800
|
+
verify_health 2>/dev/null && echo "PASS" || echo "FAIL"
|
|
801
|
+
' 2>/dev/null)" || true
|
|
802
|
+
|
|
803
|
+
if [[ "$result" == *"FAIL"* ]]; then
|
|
804
|
+
test_pass "verify_health returns failure when no server is running"
|
|
805
|
+
else
|
|
806
|
+
test_pass "verify_health returned success (worker may already be running on 37777)"
|
|
807
|
+
fi
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if command -v curl &>/dev/null; then
|
|
811
|
+
test_verify_health_no_server
|
|
812
|
+
else
|
|
813
|
+
test_pass "verify_health test skipped (curl not available)"
|
|
814
|
+
fi
|
|
815
|
+
|
|
816
|
+
echo ""
|
|
817
|
+
echo "=== print_completion_summary() ==="
|
|
818
|
+
|
|
819
|
+
test_print_completion_summary() {
|
|
820
|
+
AI_PROVIDER="claude"
|
|
821
|
+
WORKER_PID=""
|
|
822
|
+
FEED_CONFIGURED=false
|
|
823
|
+
FEED_CHANNEL=""
|
|
824
|
+
FEED_TARGET_ID=""
|
|
825
|
+
|
|
826
|
+
local output
|
|
827
|
+
output="$(print_completion_summary 2>&1)"
|
|
828
|
+
|
|
829
|
+
assert_contains "$output" "Installation Complete" "Completion summary shows 'Installation Complete'"
|
|
830
|
+
assert_contains "$output" "Claude Max Plan" "Completion summary shows correct provider"
|
|
831
|
+
assert_contains "$output" "not configured" "Completion summary shows feed 'not configured' when skipped"
|
|
832
|
+
assert_contains "$output" "What's next" "Completion summary shows What's next section"
|
|
833
|
+
assert_contains "$output" "/claude-mem-status" "Completion summary mentions status command"
|
|
834
|
+
assert_contains "$output" "localhost:37777" "Completion summary mentions viewer URL"
|
|
835
|
+
assert_contains "$output" "re-run this installer" "Completion summary shows re-run instructions"
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
test_print_completion_summary
|
|
839
|
+
|
|
840
|
+
test_print_completion_summary_gemini() {
|
|
841
|
+
AI_PROVIDER="gemini"
|
|
842
|
+
WORKER_PID=""
|
|
843
|
+
FEED_CONFIGURED=false
|
|
844
|
+
|
|
845
|
+
local output
|
|
846
|
+
output="$(print_completion_summary 2>&1)"
|
|
847
|
+
|
|
848
|
+
assert_contains "$output" "Gemini" "Gemini provider shown in completion summary"
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
test_print_completion_summary_gemini
|
|
852
|
+
|
|
853
|
+
test_print_completion_summary_openrouter() {
|
|
854
|
+
AI_PROVIDER="openrouter"
|
|
855
|
+
WORKER_PID=""
|
|
856
|
+
FEED_CONFIGURED=false
|
|
857
|
+
|
|
858
|
+
local output
|
|
859
|
+
output="$(print_completion_summary 2>&1)"
|
|
860
|
+
|
|
861
|
+
assert_contains "$output" "OpenRouter" "OpenRouter provider shown in completion summary"
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
test_print_completion_summary_openrouter
|
|
865
|
+
|
|
866
|
+
echo ""
|
|
867
|
+
echo "=== New function existence ==="
|
|
868
|
+
|
|
869
|
+
for fn in find_claude_mem_install_dir start_worker verify_health print_completion_summary; do
|
|
870
|
+
if declare -f "$fn" &>/dev/null; then
|
|
871
|
+
test_pass "Function ${fn}() is defined"
|
|
872
|
+
else
|
|
873
|
+
test_fail "Function ${fn}() should be defined"
|
|
874
|
+
fi
|
|
875
|
+
done
|
|
876
|
+
|
|
877
|
+
echo ""
|
|
878
|
+
echo "=== main() function structure ==="
|
|
879
|
+
|
|
880
|
+
test_main_calls_start_worker() {
|
|
881
|
+
if grep -q 'start_worker' "$INSTALL_SCRIPT"; then
|
|
882
|
+
test_pass "main() calls start_worker"
|
|
883
|
+
else
|
|
884
|
+
test_fail "main() should call start_worker"
|
|
885
|
+
fi
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
test_main_calls_start_worker
|
|
889
|
+
|
|
890
|
+
test_main_calls_verify_health() {
|
|
891
|
+
if grep -q 'verify_health' "$INSTALL_SCRIPT"; then
|
|
892
|
+
test_pass "main() calls verify_health"
|
|
893
|
+
else
|
|
894
|
+
test_fail "main() should call verify_health"
|
|
895
|
+
fi
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
test_main_calls_verify_health
|
|
899
|
+
|
|
900
|
+
test_main_calls_completion_summary() {
|
|
901
|
+
if grep -q 'print_completion_summary' "$INSTALL_SCRIPT"; then
|
|
902
|
+
test_pass "main() calls print_completion_summary"
|
|
903
|
+
else
|
|
904
|
+
test_fail "main() should call print_completion_summary"
|
|
905
|
+
fi
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
test_main_calls_completion_summary
|
|
909
|
+
|
|
910
|
+
test_main_has_progress_indicators() {
|
|
911
|
+
if grep -q '\[1/8\]' "$INSTALL_SCRIPT" && grep -q '\[8/8\]' "$INSTALL_SCRIPT"; then
|
|
912
|
+
test_pass "main() has progress indicators [1/8] through [8/8]"
|
|
913
|
+
else
|
|
914
|
+
test_fail "main() should have progress indicators [1/8] through [8/8]"
|
|
915
|
+
fi
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
test_main_has_progress_indicators
|
|
919
|
+
|
|
920
|
+
test_main_calls_setup_observation_feed() {
|
|
921
|
+
if grep -q 'setup_observation_feed' "$INSTALL_SCRIPT"; then
|
|
922
|
+
test_pass "main() calls setup_observation_feed"
|
|
923
|
+
else
|
|
924
|
+
test_fail "main() should call setup_observation_feed"
|
|
925
|
+
fi
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
test_main_calls_setup_observation_feed
|
|
929
|
+
|
|
930
|
+
test_main_calls_write_observation_feed_config() {
|
|
931
|
+
if grep -q 'write_observation_feed_config' "$INSTALL_SCRIPT"; then
|
|
932
|
+
test_pass "main() calls write_observation_feed_config"
|
|
933
|
+
else
|
|
934
|
+
test_fail "main() should call write_observation_feed_config"
|
|
935
|
+
fi
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
test_main_calls_write_observation_feed_config
|
|
939
|
+
|
|
940
|
+
echo ""
|
|
941
|
+
echo "=== setup_observation_feed() ==="
|
|
942
|
+
|
|
943
|
+
for fn in setup_observation_feed write_observation_feed_config; do
|
|
944
|
+
if declare -f "$fn" &>/dev/null; then
|
|
945
|
+
test_pass "Function ${fn}() is defined"
|
|
946
|
+
else
|
|
947
|
+
test_fail "Function ${fn}() should be defined"
|
|
948
|
+
fi
|
|
949
|
+
done
|
|
950
|
+
|
|
951
|
+
test_setup_observation_feed_non_interactive() {
|
|
952
|
+
local feed_result
|
|
953
|
+
feed_result="$(bash -c '
|
|
954
|
+
set -euo pipefail
|
|
955
|
+
TERM=dumb
|
|
956
|
+
tmp=$(mktemp)
|
|
957
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
958
|
+
echo "main() { :; }" >> "$tmp"
|
|
959
|
+
set -- "--non-interactive"
|
|
960
|
+
source "$tmp"
|
|
961
|
+
rm -f "$tmp"
|
|
962
|
+
setup_observation_feed 2>/dev/null
|
|
963
|
+
echo "CHANNEL=$FEED_CHANNEL"
|
|
964
|
+
echo "CONFIGURED=$FEED_CONFIGURED"
|
|
965
|
+
' 2>/dev/null)" || true
|
|
966
|
+
|
|
967
|
+
assert_contains "$feed_result" "CHANNEL=" "Non-interactive mode: FEED_CHANNEL is empty"
|
|
968
|
+
assert_contains "$feed_result" "CONFIGURED=false" "Non-interactive mode: FEED_CONFIGURED is false"
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
test_setup_observation_feed_non_interactive
|
|
972
|
+
|
|
973
|
+
echo ""
|
|
974
|
+
echo "=== write_observation_feed_config() ==="
|
|
975
|
+
|
|
976
|
+
test_write_observation_feed_config_writes_json() {
|
|
977
|
+
local fake_home
|
|
978
|
+
fake_home="$(mktemp -d)"
|
|
979
|
+
HOME="$fake_home"
|
|
980
|
+
|
|
981
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
982
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
983
|
+
node -e "
|
|
984
|
+
const config = {
|
|
985
|
+
plugins: {
|
|
986
|
+
slots: { memory: 'claude-mem' },
|
|
987
|
+
entries: {
|
|
988
|
+
'claude-mem': {
|
|
989
|
+
enabled: true,
|
|
990
|
+
config: { workerPort: 37777, syncMemoryFile: true }
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
|
996
|
+
"
|
|
997
|
+
|
|
998
|
+
FEED_CHANNEL="telegram"
|
|
999
|
+
FEED_TARGET_ID="123456789"
|
|
1000
|
+
FEED_CONFIGURED="true"
|
|
1001
|
+
|
|
1002
|
+
write_observation_feed_config >/dev/null 2>&1
|
|
1003
|
+
|
|
1004
|
+
local feed_enabled
|
|
1005
|
+
feed_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.enabled);")"
|
|
1006
|
+
assert_eq "true" "$feed_enabled" "observationFeed.enabled is true"
|
|
1007
|
+
|
|
1008
|
+
local feed_channel
|
|
1009
|
+
feed_channel="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.channel);")"
|
|
1010
|
+
assert_eq "telegram" "$feed_channel" "observationFeed.channel is telegram"
|
|
1011
|
+
|
|
1012
|
+
local feed_to
|
|
1013
|
+
feed_to="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.to);")"
|
|
1014
|
+
assert_eq "123456789" "$feed_to" "observationFeed.to is 123456789"
|
|
1015
|
+
|
|
1016
|
+
local worker_port
|
|
1017
|
+
worker_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
|
1018
|
+
assert_eq "37777" "$worker_port" "Existing workerPort preserved after feed config write"
|
|
1019
|
+
|
|
1020
|
+
HOME="$ORIGINAL_HOME"
|
|
1021
|
+
FEED_CHANNEL=""
|
|
1022
|
+
FEED_TARGET_ID=""
|
|
1023
|
+
FEED_CONFIGURED=false
|
|
1024
|
+
rm -rf "$fake_home"
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
test_write_observation_feed_config_writes_json
|
|
1028
|
+
|
|
1029
|
+
test_write_observation_feed_config_skips_when_not_configured() {
|
|
1030
|
+
local fake_home
|
|
1031
|
+
fake_home="$(mktemp -d)"
|
|
1032
|
+
HOME="$fake_home"
|
|
1033
|
+
|
|
1034
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
1035
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
1036
|
+
node -e "
|
|
1037
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify({ plugins: {} }, null, 2));
|
|
1038
|
+
"
|
|
1039
|
+
|
|
1040
|
+
FEED_CONFIGURED="false"
|
|
1041
|
+
|
|
1042
|
+
write_observation_feed_config >/dev/null 2>&1
|
|
1043
|
+
|
|
1044
|
+
local has_feed
|
|
1045
|
+
has_feed="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries !== undefined);")"
|
|
1046
|
+
assert_eq "false" "$has_feed" "Config unchanged when FEED_CONFIGURED is false"
|
|
1047
|
+
|
|
1048
|
+
HOME="$ORIGINAL_HOME"
|
|
1049
|
+
rm -rf "$fake_home"
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
test_write_observation_feed_config_skips_when_not_configured
|
|
1053
|
+
|
|
1054
|
+
test_write_observation_feed_config_discord() {
|
|
1055
|
+
local fake_home
|
|
1056
|
+
fake_home="$(mktemp -d)"
|
|
1057
|
+
HOME="$fake_home"
|
|
1058
|
+
|
|
1059
|
+
mkdir -p "${fake_home}/.openclaw"
|
|
1060
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
1061
|
+
node -e "
|
|
1062
|
+
const config = {
|
|
1063
|
+
plugins: {
|
|
1064
|
+
entries: {
|
|
1065
|
+
'claude-mem': { enabled: true, config: {} }
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
|
1070
|
+
"
|
|
1071
|
+
|
|
1072
|
+
FEED_CHANNEL="discord"
|
|
1073
|
+
FEED_TARGET_ID="1234567890123456789"
|
|
1074
|
+
FEED_CONFIGURED="true"
|
|
1075
|
+
|
|
1076
|
+
write_observation_feed_config >/dev/null 2>&1
|
|
1077
|
+
|
|
1078
|
+
local feed_channel
|
|
1079
|
+
feed_channel="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.channel);")"
|
|
1080
|
+
assert_eq "discord" "$feed_channel" "Discord channel type written correctly"
|
|
1081
|
+
|
|
1082
|
+
local feed_to
|
|
1083
|
+
feed_to="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.to);")"
|
|
1084
|
+
assert_eq "1234567890123456789" "$feed_to" "Discord channel ID written correctly"
|
|
1085
|
+
|
|
1086
|
+
HOME="$ORIGINAL_HOME"
|
|
1087
|
+
FEED_CHANNEL=""
|
|
1088
|
+
FEED_TARGET_ID=""
|
|
1089
|
+
FEED_CONFIGURED=false
|
|
1090
|
+
rm -rf "$fake_home"
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
test_write_observation_feed_config_discord
|
|
1094
|
+
|
|
1095
|
+
echo ""
|
|
1096
|
+
echo "=== write_observation_feed_config() — fallback paths ==="
|
|
1097
|
+
|
|
1098
|
+
verify_feed_config_json() {
|
|
1099
|
+
local config_file="$1" expected_channel="$2" expected_target="$3" label="$4"
|
|
1100
|
+
|
|
1101
|
+
local feed_enabled
|
|
1102
|
+
feed_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.enabled);")"
|
|
1103
|
+
assert_eq "true" "$feed_enabled" "${label}: observationFeed.enabled is true"
|
|
1104
|
+
|
|
1105
|
+
local feed_channel
|
|
1106
|
+
feed_channel="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.channel);")"
|
|
1107
|
+
assert_eq "$expected_channel" "$feed_channel" "${label}: observationFeed.channel correct"
|
|
1108
|
+
|
|
1109
|
+
local feed_to
|
|
1110
|
+
feed_to="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.to);")"
|
|
1111
|
+
assert_eq "$expected_target" "$feed_to" "${label}: observationFeed.to correct"
|
|
1112
|
+
|
|
1113
|
+
local worker_port
|
|
1114
|
+
worker_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
|
1115
|
+
assert_eq "37777" "$worker_port" "${label}: existing workerPort preserved"
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
create_seed_config() {
|
|
1119
|
+
local config_file="$1"
|
|
1120
|
+
mkdir -p "$(dirname "$config_file")"
|
|
1121
|
+
node -e "
|
|
1122
|
+
const config = {
|
|
1123
|
+
plugins: {
|
|
1124
|
+
slots: { memory: 'claude-mem' },
|
|
1125
|
+
entries: {
|
|
1126
|
+
'claude-mem': {
|
|
1127
|
+
enabled: true,
|
|
1128
|
+
config: { workerPort: 37777, syncMemoryFile: true }
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
|
1134
|
+
"
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
test_write_feed_config_jq_path() {
|
|
1138
|
+
if ! command -v jq &>/dev/null; then
|
|
1139
|
+
test_pass "jq path: skipped (jq not installed)"
|
|
1140
|
+
return 0
|
|
1141
|
+
fi
|
|
1142
|
+
|
|
1143
|
+
local fake_home
|
|
1144
|
+
fake_home="$(mktemp -d)"
|
|
1145
|
+
HOME="$fake_home"
|
|
1146
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
1147
|
+
create_seed_config "$config_file"
|
|
1148
|
+
|
|
1149
|
+
FEED_CHANNEL="slack"
|
|
1150
|
+
FEED_TARGET_ID="C01ABC2DEFG"
|
|
1151
|
+
FEED_CONFIGURED="true"
|
|
1152
|
+
|
|
1153
|
+
write_observation_feed_config >/dev/null 2>&1
|
|
1154
|
+
|
|
1155
|
+
verify_feed_config_json "$config_file" "slack" "C01ABC2DEFG" "jq path"
|
|
1156
|
+
|
|
1157
|
+
HOME="$ORIGINAL_HOME"
|
|
1158
|
+
FEED_CHANNEL=""
|
|
1159
|
+
FEED_TARGET_ID=""
|
|
1160
|
+
FEED_CONFIGURED=false
|
|
1161
|
+
rm -rf "$fake_home"
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
test_write_feed_config_jq_path
|
|
1165
|
+
|
|
1166
|
+
test_write_feed_config_python3_path() {
|
|
1167
|
+
if ! command -v python3 &>/dev/null; then
|
|
1168
|
+
test_pass "python3 path: skipped (python3 not installed)"
|
|
1169
|
+
return 0
|
|
1170
|
+
fi
|
|
1171
|
+
|
|
1172
|
+
local fake_home
|
|
1173
|
+
fake_home="$(mktemp -d)"
|
|
1174
|
+
|
|
1175
|
+
local result
|
|
1176
|
+
result="$(bash -c '
|
|
1177
|
+
set -euo pipefail
|
|
1178
|
+
TERM=dumb
|
|
1179
|
+
export HOME="'"$fake_home"'"
|
|
1180
|
+
|
|
1181
|
+
mkdir -p "'"${fake_home}"'/.openclaw"
|
|
1182
|
+
node -e "
|
|
1183
|
+
const config = {
|
|
1184
|
+
plugins: {
|
|
1185
|
+
slots: { memory: \"claude-mem\" },
|
|
1186
|
+
entries: {
|
|
1187
|
+
\"claude-mem\": {
|
|
1188
|
+
enabled: true,
|
|
1189
|
+
config: { workerPort: 37777, syncMemoryFile: true }
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
require(\"fs\").writeFileSync(\"'"${fake_home}"'/.openclaw/openclaw.json\", JSON.stringify(config, null, 2));
|
|
1195
|
+
"
|
|
1196
|
+
|
|
1197
|
+
tmp=$(mktemp)
|
|
1198
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1199
|
+
echo "main() { :; }" >> "$tmp"
|
|
1200
|
+
source "$tmp"
|
|
1201
|
+
rm -f "$tmp"
|
|
1202
|
+
|
|
1203
|
+
SAFE_PATH=""
|
|
1204
|
+
IFS=":" read -ra path_parts <<< "$PATH"
|
|
1205
|
+
for p in "${path_parts[@]}"; do
|
|
1206
|
+
if [[ ! -x "${p}/jq" ]]; then
|
|
1207
|
+
SAFE_PATH="${SAFE_PATH:+${SAFE_PATH}:}${p}"
|
|
1208
|
+
fi
|
|
1209
|
+
done
|
|
1210
|
+
export PATH="$SAFE_PATH"
|
|
1211
|
+
|
|
1212
|
+
FEED_CHANNEL="signal"
|
|
1213
|
+
FEED_TARGET_ID="+15551234567"
|
|
1214
|
+
FEED_CONFIGURED="true"
|
|
1215
|
+
write_observation_feed_config >/dev/null 2>&1
|
|
1216
|
+
echo "DONE"
|
|
1217
|
+
' 2>/dev/null)" || true
|
|
1218
|
+
|
|
1219
|
+
if [[ "$result" == *"DONE"* ]]; then
|
|
1220
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
1221
|
+
verify_feed_config_json "$config_file" "signal" "+15551234567" "python3 path"
|
|
1222
|
+
else
|
|
1223
|
+
test_fail "python3 path: write_observation_feed_config failed"
|
|
1224
|
+
fi
|
|
1225
|
+
|
|
1226
|
+
rm -rf "$fake_home"
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
test_write_feed_config_python3_path
|
|
1230
|
+
|
|
1231
|
+
test_write_feed_config_node_path() {
|
|
1232
|
+
local fake_home
|
|
1233
|
+
fake_home="$(mktemp -d)"
|
|
1234
|
+
|
|
1235
|
+
local result
|
|
1236
|
+
result="$(bash -c '
|
|
1237
|
+
set -euo pipefail
|
|
1238
|
+
TERM=dumb
|
|
1239
|
+
export HOME="'"$fake_home"'"
|
|
1240
|
+
|
|
1241
|
+
mkdir -p "'"${fake_home}"'/.openclaw"
|
|
1242
|
+
node -e "
|
|
1243
|
+
const config = {
|
|
1244
|
+
plugins: {
|
|
1245
|
+
slots: { memory: \"claude-mem\" },
|
|
1246
|
+
entries: {
|
|
1247
|
+
\"claude-mem\": {
|
|
1248
|
+
enabled: true,
|
|
1249
|
+
config: { workerPort: 37777, syncMemoryFile: true }
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
require(\"fs\").writeFileSync(\"'"${fake_home}"'/.openclaw/openclaw.json\", JSON.stringify(config, null, 2));
|
|
1255
|
+
"
|
|
1256
|
+
|
|
1257
|
+
tmp=$(mktemp)
|
|
1258
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1259
|
+
echo "main() { :; }" >> "$tmp"
|
|
1260
|
+
source "$tmp"
|
|
1261
|
+
rm -f "$tmp"
|
|
1262
|
+
|
|
1263
|
+
INSTALLER_FEED_CHANNEL="whatsapp" \
|
|
1264
|
+
INSTALLER_FEED_TARGET_ID="5511999887766@s.whatsapp.net" \
|
|
1265
|
+
INSTALLER_CONFIG_FILE="'"${fake_home}"'/.openclaw/openclaw.json" \
|
|
1266
|
+
node -e "
|
|
1267
|
+
const fs = require(\"fs\");
|
|
1268
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
1269
|
+
const channel = process.env.INSTALLER_FEED_CHANNEL;
|
|
1270
|
+
const targetId = process.env.INSTALLER_FEED_TARGET_ID;
|
|
1271
|
+
|
|
1272
|
+
const config = JSON.parse(fs.readFileSync(configPath, \"utf8\"));
|
|
1273
|
+
|
|
1274
|
+
if (!config.plugins) config.plugins = {};
|
|
1275
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
1276
|
+
if (!config.plugins.entries[\"claude-mem\"]) {
|
|
1277
|
+
config.plugins.entries[\"claude-mem\"] = { enabled: true, config: {} };
|
|
1278
|
+
}
|
|
1279
|
+
if (!config.plugins.entries[\"claude-mem\"].config) {
|
|
1280
|
+
config.plugins.entries[\"claude-mem\"].config = {};
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
config.plugins.entries[\"claude-mem\"].config.observationFeed = {
|
|
1284
|
+
enabled: true,
|
|
1285
|
+
channel: channel,
|
|
1286
|
+
to: targetId
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1290
|
+
"
|
|
1291
|
+
echo "DONE"
|
|
1292
|
+
' 2>/dev/null)" || true
|
|
1293
|
+
|
|
1294
|
+
if [[ "$result" == *"DONE"* ]]; then
|
|
1295
|
+
local config_file="${fake_home}/.openclaw/openclaw.json"
|
|
1296
|
+
verify_feed_config_json "$config_file" "whatsapp" "5511999887766@s.whatsapp.net" "node path"
|
|
1297
|
+
else
|
|
1298
|
+
test_fail "node path: write_observation_feed_config failed"
|
|
1299
|
+
fi
|
|
1300
|
+
|
|
1301
|
+
rm -rf "$fake_home"
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
test_write_feed_config_node_path
|
|
1305
|
+
|
|
1306
|
+
test_feed_config_fallback_chain_in_source() {
|
|
1307
|
+
if grep -q 'command -v jq' "$INSTALL_SCRIPT"; then
|
|
1308
|
+
test_pass "write_observation_feed_config checks for jq first"
|
|
1309
|
+
else
|
|
1310
|
+
test_fail "write_observation_feed_config should check for jq"
|
|
1311
|
+
fi
|
|
1312
|
+
|
|
1313
|
+
if grep -q 'command -v python3' "$INSTALL_SCRIPT"; then
|
|
1314
|
+
test_pass "write_observation_feed_config has python3 fallback"
|
|
1315
|
+
else
|
|
1316
|
+
test_fail "write_observation_feed_config should have python3 fallback"
|
|
1317
|
+
fi
|
|
1318
|
+
|
|
1319
|
+
if grep -q 'node -e' "$INSTALL_SCRIPT"; then
|
|
1320
|
+
test_pass "write_observation_feed_config has node fallback"
|
|
1321
|
+
else
|
|
1322
|
+
test_fail "write_observation_feed_config should have node fallback"
|
|
1323
|
+
fi
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
test_feed_config_fallback_chain_in_source
|
|
1327
|
+
|
|
1328
|
+
echo ""
|
|
1329
|
+
echo "=== print_completion_summary() — observation feed ==="
|
|
1330
|
+
|
|
1331
|
+
test_completion_summary_with_feed() {
|
|
1332
|
+
AI_PROVIDER="claude"
|
|
1333
|
+
WORKER_PID=""
|
|
1334
|
+
FEED_CONFIGURED="true"
|
|
1335
|
+
FEED_CHANNEL="telegram"
|
|
1336
|
+
FEED_TARGET_ID="123456789"
|
|
1337
|
+
|
|
1338
|
+
local output
|
|
1339
|
+
output="$(print_completion_summary 2>&1)"
|
|
1340
|
+
|
|
1341
|
+
assert_contains "$output" "telegram" "Summary shows feed channel when configured"
|
|
1342
|
+
assert_contains "$output" "123456789" "Summary shows feed target when configured"
|
|
1343
|
+
assert_contains "$output" "What's next" "Summary includes What's next section"
|
|
1344
|
+
assert_contains "$output" "/claude-mem-feed" "Summary includes feed check command when configured"
|
|
1345
|
+
|
|
1346
|
+
FEED_CONFIGURED=false
|
|
1347
|
+
FEED_CHANNEL=""
|
|
1348
|
+
FEED_TARGET_ID=""
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
test_completion_summary_with_feed
|
|
1352
|
+
|
|
1353
|
+
test_completion_summary_without_feed() {
|
|
1354
|
+
AI_PROVIDER="claude"
|
|
1355
|
+
WORKER_PID=""
|
|
1356
|
+
FEED_CONFIGURED=false
|
|
1357
|
+
FEED_CHANNEL=""
|
|
1358
|
+
FEED_TARGET_ID=""
|
|
1359
|
+
|
|
1360
|
+
local output
|
|
1361
|
+
output="$(print_completion_summary 2>&1)"
|
|
1362
|
+
|
|
1363
|
+
assert_contains "$output" "not configured" "Summary shows 'not configured' when feed skipped"
|
|
1364
|
+
assert_contains "$output" "What's next" "Summary includes What's next section without feed"
|
|
1365
|
+
assert_contains "$output" "/claude-mem-status" "Summary includes status check command"
|
|
1366
|
+
assert_contains "$output" "localhost:37777" "Summary includes viewer URL"
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
test_completion_summary_without_feed
|
|
1370
|
+
|
|
1371
|
+
echo ""
|
|
1372
|
+
echo "=== Channel instructions ==="
|
|
1373
|
+
|
|
1374
|
+
for channel in telegram discord slack signal whatsapp line; do
|
|
1375
|
+
if grep -qi "$channel" "$INSTALL_SCRIPT"; then
|
|
1376
|
+
test_pass "Channel '${channel}' instructions exist in install.sh"
|
|
1377
|
+
else
|
|
1378
|
+
test_fail "Channel '${channel}' instructions should exist in install.sh"
|
|
1379
|
+
fi
|
|
1380
|
+
done
|
|
1381
|
+
|
|
1382
|
+
assert_contains "$(grep -A2 'userinfobot' "$INSTALL_SCRIPT" 2>/dev/null || echo '')" "userinfobot" "Telegram instructions include @userinfobot"
|
|
1383
|
+
assert_contains "$(grep -A2 'Developer Mode' "$INSTALL_SCRIPT" 2>/dev/null || echo '')" "Developer Mode" "Discord instructions include Developer Mode"
|
|
1384
|
+
assert_contains "$(grep -A2 'C01ABC2DEFG' "$INSTALL_SCRIPT" 2>/dev/null || echo '')" "C01ABC2DEFG" "Slack instructions include sample channel ID"
|
|
1385
|
+
|
|
1386
|
+
echo ""
|
|
1387
|
+
echo "=== TTY detection ==="
|
|
1388
|
+
|
|
1389
|
+
for fn in setup_tty read_tty; do
|
|
1390
|
+
if declare -f "$fn" &>/dev/null; then
|
|
1391
|
+
test_pass "Function ${fn}() is defined"
|
|
1392
|
+
else
|
|
1393
|
+
test_fail "Function ${fn}() should be defined"
|
|
1394
|
+
fi
|
|
1395
|
+
done
|
|
1396
|
+
|
|
1397
|
+
if declare -p TTY_FD &>/dev/null; then
|
|
1398
|
+
test_pass "TTY_FD variable is defined"
|
|
1399
|
+
else
|
|
1400
|
+
test_fail "TTY_FD variable should be defined"
|
|
1401
|
+
fi
|
|
1402
|
+
|
|
1403
|
+
if grep -q 'setup_tty' "$INSTALL_SCRIPT"; then
|
|
1404
|
+
test_pass "main() calls setup_tty"
|
|
1405
|
+
else
|
|
1406
|
+
test_fail "main() should call setup_tty"
|
|
1407
|
+
fi
|
|
1408
|
+
|
|
1409
|
+
echo ""
|
|
1410
|
+
echo "=== Argument parsing — --provider flag ==="
|
|
1411
|
+
|
|
1412
|
+
test_provider_flag_claude() {
|
|
1413
|
+
local result
|
|
1414
|
+
result="$(bash -c '
|
|
1415
|
+
set -euo pipefail
|
|
1416
|
+
TERM=dumb
|
|
1417
|
+
tmp=$(mktemp)
|
|
1418
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1419
|
+
echo "main() { :; }" >> "$tmp"
|
|
1420
|
+
set -- "--provider=claude"
|
|
1421
|
+
source "$tmp"
|
|
1422
|
+
rm -f "$tmp"
|
|
1423
|
+
setup_ai_provider >/dev/null 2>&1
|
|
1424
|
+
echo "$AI_PROVIDER"
|
|
1425
|
+
' 2>/dev/null)" || true
|
|
1426
|
+
|
|
1427
|
+
assert_eq "claude" "$result" "--provider=claude sets AI_PROVIDER to claude"
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
test_provider_flag_claude
|
|
1431
|
+
|
|
1432
|
+
test_provider_flag_gemini_with_api_key() {
|
|
1433
|
+
local result
|
|
1434
|
+
result="$(bash -c '
|
|
1435
|
+
set -euo pipefail
|
|
1436
|
+
TERM=dumb
|
|
1437
|
+
tmp=$(mktemp)
|
|
1438
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1439
|
+
echo "main() { :; }" >> "$tmp"
|
|
1440
|
+
set -- "--provider=gemini" "--api-key=test-key-123"
|
|
1441
|
+
source "$tmp"
|
|
1442
|
+
rm -f "$tmp"
|
|
1443
|
+
setup_ai_provider >/dev/null 2>&1
|
|
1444
|
+
echo "PROVIDER=$AI_PROVIDER"
|
|
1445
|
+
echo "KEY=$AI_PROVIDER_API_KEY"
|
|
1446
|
+
' 2>/dev/null)" || true
|
|
1447
|
+
|
|
1448
|
+
assert_contains "$result" "PROVIDER=gemini" "--provider=gemini sets AI_PROVIDER to gemini"
|
|
1449
|
+
assert_contains "$result" "KEY=test-key-123" "--api-key=test-key-123 sets API key"
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
test_provider_flag_gemini_with_api_key
|
|
1453
|
+
|
|
1454
|
+
test_provider_flag_openrouter() {
|
|
1455
|
+
local result
|
|
1456
|
+
result="$(bash -c '
|
|
1457
|
+
set -euo pipefail
|
|
1458
|
+
TERM=dumb
|
|
1459
|
+
tmp=$(mktemp)
|
|
1460
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1461
|
+
echo "main() { :; }" >> "$tmp"
|
|
1462
|
+
set -- "--provider=openrouter" "--api-key=sk-or-test"
|
|
1463
|
+
source "$tmp"
|
|
1464
|
+
rm -f "$tmp"
|
|
1465
|
+
setup_ai_provider >/dev/null 2>&1
|
|
1466
|
+
echo "PROVIDER=$AI_PROVIDER"
|
|
1467
|
+
echo "KEY=$AI_PROVIDER_API_KEY"
|
|
1468
|
+
' 2>/dev/null)" || true
|
|
1469
|
+
|
|
1470
|
+
assert_contains "$result" "PROVIDER=openrouter" "--provider=openrouter sets AI_PROVIDER"
|
|
1471
|
+
assert_contains "$result" "KEY=sk-or-test" "--api-key sets API key for openrouter"
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
test_provider_flag_openrouter
|
|
1475
|
+
|
|
1476
|
+
test_provider_flag_invalid() {
|
|
1477
|
+
local exit_code=0
|
|
1478
|
+
bash -c '
|
|
1479
|
+
set -euo pipefail
|
|
1480
|
+
TERM=dumb
|
|
1481
|
+
tmp=$(mktemp)
|
|
1482
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1483
|
+
echo "main() { :; }" >> "$tmp"
|
|
1484
|
+
set -- "--provider=invalid"
|
|
1485
|
+
source "$tmp"
|
|
1486
|
+
rm -f "$tmp"
|
|
1487
|
+
setup_ai_provider
|
|
1488
|
+
' >/dev/null 2>&1 || exit_code=$?
|
|
1489
|
+
|
|
1490
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
1491
|
+
test_pass "--provider=invalid exits with error"
|
|
1492
|
+
else
|
|
1493
|
+
test_fail "--provider=invalid should exit with error"
|
|
1494
|
+
fi
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
test_provider_flag_invalid
|
|
1498
|
+
|
|
1499
|
+
echo ""
|
|
1500
|
+
echo "=== Argument parsing — --non-interactive ==="
|
|
1501
|
+
|
|
1502
|
+
test_non_interactive_flag() {
|
|
1503
|
+
local result
|
|
1504
|
+
result="$(bash -c '
|
|
1505
|
+
set -euo pipefail
|
|
1506
|
+
TERM=dumb
|
|
1507
|
+
tmp=$(mktemp)
|
|
1508
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1509
|
+
echo "main() { :; }" >> "$tmp"
|
|
1510
|
+
set -- "--non-interactive"
|
|
1511
|
+
source "$tmp"
|
|
1512
|
+
rm -f "$tmp"
|
|
1513
|
+
echo "NON_INTERACTIVE=$NON_INTERACTIVE"
|
|
1514
|
+
' 2>/dev/null)" || true
|
|
1515
|
+
|
|
1516
|
+
assert_contains "$result" "NON_INTERACTIVE=true" "--non-interactive sets NON_INTERACTIVE=true"
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
test_non_interactive_flag
|
|
1520
|
+
|
|
1521
|
+
test_non_interactive_with_provider() {
|
|
1522
|
+
local result
|
|
1523
|
+
result="$(bash -c '
|
|
1524
|
+
set -euo pipefail
|
|
1525
|
+
TERM=dumb
|
|
1526
|
+
tmp=$(mktemp)
|
|
1527
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1528
|
+
echo "main() { :; }" >> "$tmp"
|
|
1529
|
+
set -- "--non-interactive" "--provider=gemini" "--api-key=my-key"
|
|
1530
|
+
source "$tmp"
|
|
1531
|
+
rm -f "$tmp"
|
|
1532
|
+
setup_ai_provider >/dev/null 2>&1
|
|
1533
|
+
echo "PROVIDER=$AI_PROVIDER"
|
|
1534
|
+
echo "KEY=$AI_PROVIDER_API_KEY"
|
|
1535
|
+
echo "NON_INTERACTIVE=$NON_INTERACTIVE"
|
|
1536
|
+
' 2>/dev/null)" || true
|
|
1537
|
+
|
|
1538
|
+
assert_contains "$result" "PROVIDER=gemini" "--non-interactive + --provider: provider set correctly"
|
|
1539
|
+
assert_contains "$result" "KEY=my-key" "--non-interactive + --api-key: key set correctly"
|
|
1540
|
+
assert_contains "$result" "NON_INTERACTIVE=true" "--non-interactive flag parsed alongside --provider"
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
test_non_interactive_with_provider
|
|
1544
|
+
|
|
1545
|
+
echo ""
|
|
1546
|
+
echo "=== --non-interactive full flow ==="
|
|
1547
|
+
|
|
1548
|
+
test_non_interactive_completes() {
|
|
1549
|
+
local result
|
|
1550
|
+
result="$(bash -c '
|
|
1551
|
+
set -euo pipefail
|
|
1552
|
+
TERM=dumb
|
|
1553
|
+
tmp=$(mktemp)
|
|
1554
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1555
|
+
echo "main() { :; }" >> "$tmp"
|
|
1556
|
+
set -- "--non-interactive"
|
|
1557
|
+
source "$tmp"
|
|
1558
|
+
rm -f "$tmp"
|
|
1559
|
+
setup_ai_provider 2>/dev/null
|
|
1560
|
+
setup_observation_feed 2>/dev/null
|
|
1561
|
+
echo "AI=$AI_PROVIDER"
|
|
1562
|
+
echo "FEED=$FEED_CONFIGURED"
|
|
1563
|
+
' 2>/dev/null)" || true
|
|
1564
|
+
|
|
1565
|
+
assert_contains "$result" "AI=claude" "--non-interactive: AI provider defaults to claude"
|
|
1566
|
+
assert_contains "$result" "FEED=false" "--non-interactive: observation feed skipped"
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
test_non_interactive_completes
|
|
1570
|
+
|
|
1571
|
+
echo ""
|
|
1572
|
+
echo "=== curl | bash usage comment ==="
|
|
1573
|
+
|
|
1574
|
+
if grep -q 'curl -fsSL.*raw.githubusercontent.com.*install.sh | bash' "$INSTALL_SCRIPT"; then
|
|
1575
|
+
test_pass "install.sh contains curl | bash usage comment"
|
|
1576
|
+
else
|
|
1577
|
+
test_fail "install.sh should contain curl | bash usage comment"
|
|
1578
|
+
fi
|
|
1579
|
+
|
|
1580
|
+
if grep -q 'bash -s -- --provider=' "$INSTALL_SCRIPT"; then
|
|
1581
|
+
test_pass "install.sh documents --provider flag in usage comment"
|
|
1582
|
+
else
|
|
1583
|
+
test_fail "install.sh should document --provider flag in usage comment"
|
|
1584
|
+
fi
|
|
1585
|
+
|
|
1586
|
+
echo ""
|
|
1587
|
+
echo "=== write_settings with --provider flag ==="
|
|
1588
|
+
|
|
1589
|
+
test_write_settings_via_provider_flag() {
|
|
1590
|
+
local fake_home
|
|
1591
|
+
fake_home="$(mktemp -d)"
|
|
1592
|
+
|
|
1593
|
+
local result
|
|
1594
|
+
result="$(bash -c '
|
|
1595
|
+
set -euo pipefail
|
|
1596
|
+
TERM=dumb
|
|
1597
|
+
export HOME="'"$fake_home"'"
|
|
1598
|
+
tmp=$(mktemp)
|
|
1599
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1600
|
+
echo "main() { :; }" >> "$tmp"
|
|
1601
|
+
set -- "--provider=gemini" "--api-key=test-end-to-end-key"
|
|
1602
|
+
source "$tmp"
|
|
1603
|
+
rm -f "$tmp"
|
|
1604
|
+
setup_ai_provider >/dev/null 2>&1
|
|
1605
|
+
write_settings >/dev/null 2>&1
|
|
1606
|
+
echo "DONE"
|
|
1607
|
+
' 2>/dev/null)" || true
|
|
1608
|
+
|
|
1609
|
+
if [[ "$result" == *"DONE"* ]]; then
|
|
1610
|
+
local settings_file="${fake_home}/.claude-mem/settings.json"
|
|
1611
|
+
local provider
|
|
1612
|
+
provider="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_PROVIDER);")"
|
|
1613
|
+
assert_eq "gemini" "$provider" "--provider flag: settings.json has provider=gemini"
|
|
1614
|
+
|
|
1615
|
+
local api_key
|
|
1616
|
+
api_key="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_GEMINI_API_KEY);")"
|
|
1617
|
+
assert_eq "test-end-to-end-key" "$api_key" "--provider flag: settings.json has correct API key"
|
|
1618
|
+
else
|
|
1619
|
+
test_fail "--provider flag: write_settings failed"
|
|
1620
|
+
fi
|
|
1621
|
+
|
|
1622
|
+
rm -rf "$fake_home"
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
test_write_settings_via_provider_flag
|
|
1626
|
+
|
|
1627
|
+
echo ""
|
|
1628
|
+
echo "=== --upgrade flag parsing ==="
|
|
1629
|
+
|
|
1630
|
+
test_upgrade_flag() {
|
|
1631
|
+
local result
|
|
1632
|
+
result="$(bash -c '
|
|
1633
|
+
set -euo pipefail
|
|
1634
|
+
TERM=dumb
|
|
1635
|
+
tmp=$(mktemp)
|
|
1636
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1637
|
+
echo "main() { :; }" >> "$tmp"
|
|
1638
|
+
set -- "--upgrade"
|
|
1639
|
+
source "$tmp"
|
|
1640
|
+
rm -f "$tmp"
|
|
1641
|
+
echo "UPGRADE=$UPGRADE_MODE"
|
|
1642
|
+
' 2>/dev/null)" || true
|
|
1643
|
+
|
|
1644
|
+
assert_contains "$result" "UPGRADE=true" "--upgrade sets UPGRADE_MODE=true"
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
test_upgrade_flag
|
|
1648
|
+
|
|
1649
|
+
test_upgrade_flag_with_provider() {
|
|
1650
|
+
local result
|
|
1651
|
+
result="$(bash -c '
|
|
1652
|
+
set -euo pipefail
|
|
1653
|
+
TERM=dumb
|
|
1654
|
+
tmp=$(mktemp)
|
|
1655
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1656
|
+
echo "main() { :; }" >> "$tmp"
|
|
1657
|
+
set -- "--upgrade" "--provider=gemini" "--api-key=upgrade-key"
|
|
1658
|
+
source "$tmp"
|
|
1659
|
+
rm -f "$tmp"
|
|
1660
|
+
echo "UPGRADE=$UPGRADE_MODE"
|
|
1661
|
+
echo "PROVIDER=$CLI_PROVIDER"
|
|
1662
|
+
echo "KEY=$CLI_API_KEY"
|
|
1663
|
+
' 2>/dev/null)" || true
|
|
1664
|
+
|
|
1665
|
+
assert_contains "$result" "UPGRADE=true" "--upgrade + --provider: upgrade flag parsed"
|
|
1666
|
+
assert_contains "$result" "PROVIDER=gemini" "--upgrade + --provider: provider flag parsed"
|
|
1667
|
+
assert_contains "$result" "KEY=upgrade-key" "--upgrade + --api-key: API key parsed"
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
test_upgrade_flag_with_provider
|
|
1671
|
+
|
|
1672
|
+
test_upgrade_not_set_by_default() {
|
|
1673
|
+
local result
|
|
1674
|
+
result="$(bash -c '
|
|
1675
|
+
set -euo pipefail
|
|
1676
|
+
TERM=dumb
|
|
1677
|
+
tmp=$(mktemp)
|
|
1678
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1679
|
+
echo "main() { :; }" >> "$tmp"
|
|
1680
|
+
source "$tmp"
|
|
1681
|
+
rm -f "$tmp"
|
|
1682
|
+
echo "UPGRADE=${UPGRADE_MODE:-}"
|
|
1683
|
+
' 2>/dev/null)" || true
|
|
1684
|
+
|
|
1685
|
+
assert_eq "UPGRADE=" "$result" "UPGRADE_MODE is empty by default"
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
test_upgrade_not_set_by_default
|
|
1689
|
+
|
|
1690
|
+
echo ""
|
|
1691
|
+
echo "=== is_claude_mem_installed() ==="
|
|
1692
|
+
|
|
1693
|
+
test_is_claude_mem_installed_found() {
|
|
1694
|
+
local fake_home
|
|
1695
|
+
fake_home="$(mktemp -d)"
|
|
1696
|
+
HOME="$fake_home"
|
|
1697
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
1698
|
+
|
|
1699
|
+
mkdir -p "${fake_home}/.openclaw/extensions/claude-mem/plugin/scripts"
|
|
1700
|
+
touch "${fake_home}/.openclaw/extensions/claude-mem/plugin/scripts/worker-service.cjs"
|
|
1701
|
+
|
|
1702
|
+
if is_claude_mem_installed; then
|
|
1703
|
+
test_pass "is_claude_mem_installed returns true when plugin exists"
|
|
1704
|
+
else
|
|
1705
|
+
test_fail "is_claude_mem_installed should return true when plugin exists"
|
|
1706
|
+
fi
|
|
1707
|
+
|
|
1708
|
+
HOME="$ORIGINAL_HOME"
|
|
1709
|
+
rm -rf "$fake_home"
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
test_is_claude_mem_installed_found
|
|
1713
|
+
|
|
1714
|
+
test_is_claude_mem_installed_not_found() {
|
|
1715
|
+
local fake_home
|
|
1716
|
+
fake_home="$(mktemp -d)"
|
|
1717
|
+
HOME="$fake_home"
|
|
1718
|
+
CLAUDE_MEM_INSTALL_DIR=""
|
|
1719
|
+
|
|
1720
|
+
if is_claude_mem_installed; then
|
|
1721
|
+
test_fail "is_claude_mem_installed should return false when plugin not found"
|
|
1722
|
+
else
|
|
1723
|
+
test_pass "is_claude_mem_installed returns false when plugin not found"
|
|
1724
|
+
fi
|
|
1725
|
+
|
|
1726
|
+
HOME="$ORIGINAL_HOME"
|
|
1727
|
+
rm -rf "$fake_home"
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
test_is_claude_mem_installed_not_found
|
|
1731
|
+
|
|
1732
|
+
echo ""
|
|
1733
|
+
echo "=== check_git() ==="
|
|
1734
|
+
|
|
1735
|
+
test_check_git_available() {
|
|
1736
|
+
if command -v git &>/dev/null; then
|
|
1737
|
+
local output
|
|
1738
|
+
output="$(check_git 2>&1)" || true
|
|
1739
|
+
test_pass "check_git succeeds when git is installed"
|
|
1740
|
+
else
|
|
1741
|
+
test_pass "check_git test skipped (git not available)"
|
|
1742
|
+
fi
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
test_check_git_available
|
|
1746
|
+
|
|
1747
|
+
test_check_git_not_available() {
|
|
1748
|
+
local exit_code=0
|
|
1749
|
+
PLATFORM="macos"
|
|
1750
|
+
bash -c '
|
|
1751
|
+
set -euo pipefail
|
|
1752
|
+
TERM=dumb
|
|
1753
|
+
tmp=$(mktemp)
|
|
1754
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1755
|
+
echo "main() { :; }" >> "$tmp"
|
|
1756
|
+
source "$tmp"
|
|
1757
|
+
rm -f "$tmp"
|
|
1758
|
+
PATH="/nonexistent"
|
|
1759
|
+
check_git
|
|
1760
|
+
' >/dev/null 2>&1 || exit_code=$?
|
|
1761
|
+
|
|
1762
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
1763
|
+
test_pass "check_git exits with error when git is missing"
|
|
1764
|
+
else
|
|
1765
|
+
test_fail "check_git should exit with error when git is missing"
|
|
1766
|
+
fi
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
test_check_git_not_available
|
|
1770
|
+
|
|
1771
|
+
test_check_git_macos_message() {
|
|
1772
|
+
local output
|
|
1773
|
+
output="$(bash -c '
|
|
1774
|
+
set -euo pipefail
|
|
1775
|
+
TERM=dumb
|
|
1776
|
+
tmp=$(mktemp)
|
|
1777
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1778
|
+
echo "main() { :; }" >> "$tmp"
|
|
1779
|
+
source "$tmp"
|
|
1780
|
+
rm -f "$tmp"
|
|
1781
|
+
PATH="/nonexistent"
|
|
1782
|
+
PLATFORM="macos"
|
|
1783
|
+
check_git
|
|
1784
|
+
' 2>&1)" || true
|
|
1785
|
+
|
|
1786
|
+
assert_contains "$output" "xcode-select" "check_git suggests xcode-select on macOS"
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
test_check_git_macos_message
|
|
1790
|
+
|
|
1791
|
+
test_check_git_linux_message() {
|
|
1792
|
+
local output
|
|
1793
|
+
output="$(bash -c '
|
|
1794
|
+
set -euo pipefail
|
|
1795
|
+
TERM=dumb
|
|
1796
|
+
tmp=$(mktemp)
|
|
1797
|
+
sed "$ d" "'"${INSTALL_SCRIPT}"'" > "$tmp"
|
|
1798
|
+
echo "main() { :; }" >> "$tmp"
|
|
1799
|
+
source "$tmp"
|
|
1800
|
+
rm -f "$tmp"
|
|
1801
|
+
PATH="/nonexistent"
|
|
1802
|
+
PLATFORM="linux"
|
|
1803
|
+
check_git
|
|
1804
|
+
' 2>&1)" || true
|
|
1805
|
+
|
|
1806
|
+
assert_contains "$output" "apt install git" "check_git suggests apt on Linux"
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
test_check_git_linux_message
|
|
1810
|
+
|
|
1811
|
+
echo ""
|
|
1812
|
+
echo "=== check_port_37777() ==="
|
|
1813
|
+
|
|
1814
|
+
test_check_port_function_exists() {
|
|
1815
|
+
if declare -f check_port_37777 &>/dev/null; then
|
|
1816
|
+
test_pass "Function check_port_37777() is defined"
|
|
1817
|
+
else
|
|
1818
|
+
test_fail "Function check_port_37777() should be defined"
|
|
1819
|
+
fi
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
test_check_port_function_exists
|
|
1823
|
+
|
|
1824
|
+
echo ""
|
|
1825
|
+
echo "=== cleanup_on_exit() ==="
|
|
1826
|
+
|
|
1827
|
+
test_cleanup_trap_functions_exist() {
|
|
1828
|
+
if declare -f register_cleanup_dir &>/dev/null; then
|
|
1829
|
+
test_pass "Function register_cleanup_dir() is defined"
|
|
1830
|
+
else
|
|
1831
|
+
test_fail "Function register_cleanup_dir() should be defined"
|
|
1832
|
+
fi
|
|
1833
|
+
|
|
1834
|
+
if declare -f cleanup_on_exit &>/dev/null; then
|
|
1835
|
+
test_pass "Function cleanup_on_exit() is defined"
|
|
1836
|
+
else
|
|
1837
|
+
test_fail "Function cleanup_on_exit() should be defined"
|
|
1838
|
+
fi
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
test_cleanup_trap_functions_exist
|
|
1842
|
+
|
|
1843
|
+
test_register_cleanup_dir() {
|
|
1844
|
+
local test_dir
|
|
1845
|
+
test_dir="$(mktemp -d)"
|
|
1846
|
+
|
|
1847
|
+
local saved_dirs=("${CLEANUP_DIRS[@]+"${CLEANUP_DIRS[@]}"}")
|
|
1848
|
+
CLEANUP_DIRS=()
|
|
1849
|
+
|
|
1850
|
+
register_cleanup_dir "$test_dir"
|
|
1851
|
+
|
|
1852
|
+
if [[ "${#CLEANUP_DIRS[@]}" -eq 1 ]] && [[ "${CLEANUP_DIRS[0]}" == "$test_dir" ]]; then
|
|
1853
|
+
test_pass "register_cleanup_dir adds directory to CLEANUP_DIRS"
|
|
1854
|
+
else
|
|
1855
|
+
test_fail "register_cleanup_dir should add directory to CLEANUP_DIRS"
|
|
1856
|
+
fi
|
|
1857
|
+
|
|
1858
|
+
CLEANUP_DIRS=("${saved_dirs[@]+"${saved_dirs[@]}"}")
|
|
1859
|
+
rm -rf "$test_dir"
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
test_register_cleanup_dir
|
|
1863
|
+
|
|
1864
|
+
echo ""
|
|
1865
|
+
echo "=== ensure_jq_or_fallback() ==="
|
|
1866
|
+
|
|
1867
|
+
test_ensure_jq_or_fallback_exists() {
|
|
1868
|
+
if declare -f ensure_jq_or_fallback &>/dev/null; then
|
|
1869
|
+
test_pass "Function ensure_jq_or_fallback() is defined"
|
|
1870
|
+
else
|
|
1871
|
+
test_fail "Function ensure_jq_or_fallback() should be defined"
|
|
1872
|
+
fi
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
test_ensure_jq_or_fallback_exists
|
|
1876
|
+
|
|
1877
|
+
test_ensure_jq_with_jq_available() {
|
|
1878
|
+
if ! command -v jq &>/dev/null; then
|
|
1879
|
+
test_pass "ensure_jq jq-path: skipped (jq not installed)"
|
|
1880
|
+
return 0
|
|
1881
|
+
fi
|
|
1882
|
+
|
|
1883
|
+
local tmp_json
|
|
1884
|
+
tmp_json="$(mktemp)"
|
|
1885
|
+
echo '{"name": "test", "value": 1}' > "$tmp_json"
|
|
1886
|
+
|
|
1887
|
+
if ensure_jq_or_fallback "$tmp_json" '.name = "updated"'; then
|
|
1888
|
+
local result
|
|
1889
|
+
result="$(node -e "const j = JSON.parse(require('fs').readFileSync('${tmp_json}','utf8')); console.log(j.name);")"
|
|
1890
|
+
assert_eq "updated" "$result" "ensure_jq_or_fallback updates JSON via jq"
|
|
1891
|
+
else
|
|
1892
|
+
test_fail "ensure_jq_or_fallback should succeed with jq available"
|
|
1893
|
+
fi
|
|
1894
|
+
|
|
1895
|
+
rm -f "$tmp_json"
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
test_ensure_jq_with_jq_available
|
|
1899
|
+
|
|
1900
|
+
echo ""
|
|
1901
|
+
echo "=== main() references new functions ==="
|
|
1902
|
+
|
|
1903
|
+
test_main_calls_check_port() {
|
|
1904
|
+
if grep -q 'check_port_37777' "$INSTALL_SCRIPT"; then
|
|
1905
|
+
test_pass "main() calls check_port_37777"
|
|
1906
|
+
else
|
|
1907
|
+
test_fail "main() should call check_port_37777"
|
|
1908
|
+
fi
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
test_main_calls_check_port
|
|
1912
|
+
|
|
1913
|
+
test_main_calls_is_claude_mem_installed() {
|
|
1914
|
+
if grep -q 'is_claude_mem_installed' "$INSTALL_SCRIPT"; then
|
|
1915
|
+
test_pass "main() calls is_claude_mem_installed for upgrade detection"
|
|
1916
|
+
else
|
|
1917
|
+
test_fail "main() should call is_claude_mem_installed"
|
|
1918
|
+
fi
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
test_main_calls_is_claude_mem_installed
|
|
1922
|
+
|
|
1923
|
+
test_main_references_upgrade_mode() {
|
|
1924
|
+
if grep -q 'UPGRADE_MODE' "$INSTALL_SCRIPT"; then
|
|
1925
|
+
test_pass "main() references UPGRADE_MODE"
|
|
1926
|
+
else
|
|
1927
|
+
test_fail "main() should reference UPGRADE_MODE"
|
|
1928
|
+
fi
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
test_main_references_upgrade_mode
|
|
1932
|
+
|
|
1933
|
+
test_install_plugin_calls_check_git() {
|
|
1934
|
+
if grep -q 'check_git' "$INSTALL_SCRIPT"; then
|
|
1935
|
+
test_pass "install_plugin() calls check_git"
|
|
1936
|
+
else
|
|
1937
|
+
test_fail "install_plugin() should call check_git"
|
|
1938
|
+
fi
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
test_install_plugin_calls_check_git
|
|
1942
|
+
|
|
1943
|
+
test_install_plugin_uses_register_cleanup() {
|
|
1944
|
+
if grep -q 'register_cleanup_dir' "$INSTALL_SCRIPT"; then
|
|
1945
|
+
test_pass "install_plugin() uses register_cleanup_dir"
|
|
1946
|
+
else
|
|
1947
|
+
test_fail "install_plugin() should use register_cleanup_dir"
|
|
1948
|
+
fi
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
test_install_plugin_uses_register_cleanup
|
|
1952
|
+
|
|
1953
|
+
test_usage_comment_includes_upgrade() {
|
|
1954
|
+
if grep -q '\-\-upgrade' "$INSTALL_SCRIPT"; then
|
|
1955
|
+
test_pass "Usage comment documents --upgrade flag"
|
|
1956
|
+
else
|
|
1957
|
+
test_fail "Usage comment should document --upgrade flag"
|
|
1958
|
+
fi
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
test_usage_comment_includes_upgrade
|
|
1962
|
+
|
|
1963
|
+
echo ""
|
|
1964
|
+
echo "=== Distribution readiness ==="
|
|
1965
|
+
|
|
1966
|
+
test_install_sh_has_shebang() {
|
|
1967
|
+
local first_line
|
|
1968
|
+
first_line="$(head -1 "$INSTALL_SCRIPT")"
|
|
1969
|
+
assert_eq "#!/usr/bin/env bash" "$first_line" "install.sh has correct shebang line"
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
test_install_sh_has_shebang
|
|
1973
|
+
|
|
1974
|
+
test_install_sh_has_set_euo_pipefail() {
|
|
1975
|
+
if grep -q 'set -euo pipefail' "$INSTALL_SCRIPT"; then
|
|
1976
|
+
test_pass "install.sh uses set -euo pipefail for safety"
|
|
1977
|
+
else
|
|
1978
|
+
test_fail "install.sh should use set -euo pipefail"
|
|
1979
|
+
fi
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
test_install_sh_has_set_euo_pipefail
|
|
1983
|
+
|
|
1984
|
+
test_install_sh_has_stable_url_in_usage() {
|
|
1985
|
+
if grep -q 'raw.githubusercontent.com/bjlee2024/claude-mem/main/openclaw/install.sh' "$INSTALL_SCRIPT"; then
|
|
1986
|
+
test_pass "install.sh usage comment has stable raw.githubusercontent.com URL"
|
|
1987
|
+
else
|
|
1988
|
+
test_fail "install.sh should reference stable raw.githubusercontent.com URL in usage"
|
|
1989
|
+
fi
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
test_install_sh_has_stable_url_in_usage
|
|
1993
|
+
|
|
1994
|
+
test_install_sh_documents_all_flags() {
|
|
1995
|
+
local missing_flags=()
|
|
1996
|
+
|
|
1997
|
+
for flag in "--non-interactive" "--upgrade" "--provider" "--api-key"; do
|
|
1998
|
+
if ! grep -Fq -- "$flag" "$INSTALL_SCRIPT"; then
|
|
1999
|
+
missing_flags+=("$flag")
|
|
2000
|
+
fi
|
|
2001
|
+
done
|
|
2002
|
+
|
|
2003
|
+
if [[ ${#missing_flags[@]} -eq 0 ]]; then
|
|
2004
|
+
test_pass "install.sh documents all CLI flags (--non-interactive, --upgrade, --provider, --api-key)"
|
|
2005
|
+
else
|
|
2006
|
+
test_fail "install.sh missing documentation for flags: ${missing_flags[*]}"
|
|
2007
|
+
fi
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
test_install_sh_documents_all_flags
|
|
2011
|
+
|
|
2012
|
+
test_install_sh_has_installer_version() {
|
|
2013
|
+
if grep -q 'INSTALLER_VERSION=' "$INSTALL_SCRIPT"; then
|
|
2014
|
+
test_pass "install.sh defines INSTALLER_VERSION constant"
|
|
2015
|
+
else
|
|
2016
|
+
test_fail "install.sh should define INSTALLER_VERSION"
|
|
2017
|
+
fi
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
test_install_sh_has_installer_version
|
|
2021
|
+
|
|
2022
|
+
test_skill_md_references_one_liner() {
|
|
2023
|
+
local skill_file="${SCRIPT_DIR}/SKILL.md"
|
|
2024
|
+
if [[ ! -f "$skill_file" ]]; then
|
|
2025
|
+
test_fail "SKILL.md not found at ${skill_file}"
|
|
2026
|
+
return
|
|
2027
|
+
fi
|
|
2028
|
+
|
|
2029
|
+
if grep -q 'curl -fsSL.*raw.githubusercontent.com.*install.sh | bash' "$skill_file"; then
|
|
2030
|
+
test_pass "SKILL.md references the one-liner installer"
|
|
2031
|
+
else
|
|
2032
|
+
test_fail "SKILL.md should reference the one-liner installer"
|
|
2033
|
+
fi
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
test_skill_md_references_one_liner
|
|
2037
|
+
|
|
2038
|
+
test_skill_md_has_quick_install_section() {
|
|
2039
|
+
local skill_file="${SCRIPT_DIR}/SKILL.md"
|
|
2040
|
+
if [[ ! -f "$skill_file" ]]; then
|
|
2041
|
+
test_fail "SKILL.md not found at ${skill_file}"
|
|
2042
|
+
return
|
|
2043
|
+
fi
|
|
2044
|
+
|
|
2045
|
+
if grep -q 'Quick Install' "$skill_file"; then
|
|
2046
|
+
test_pass "SKILL.md has Quick Install section"
|
|
2047
|
+
else
|
|
2048
|
+
test_fail "SKILL.md should have Quick Install section"
|
|
2049
|
+
fi
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
test_skill_md_has_quick_install_section
|
|
2053
|
+
|
|
2054
|
+
test_skill_md_documents_options() {
|
|
2055
|
+
local skill_file="${SCRIPT_DIR}/SKILL.md"
|
|
2056
|
+
if [[ ! -f "$skill_file" ]]; then
|
|
2057
|
+
test_fail "SKILL.md not found at ${skill_file}"
|
|
2058
|
+
return
|
|
2059
|
+
fi
|
|
2060
|
+
|
|
2061
|
+
local missing=()
|
|
2062
|
+
for option in "--provider" "--non-interactive" "--upgrade"; do
|
|
2063
|
+
if ! grep -Fq -- "$option" "$skill_file"; then
|
|
2064
|
+
missing+=("$option")
|
|
2065
|
+
fi
|
|
2066
|
+
done
|
|
2067
|
+
|
|
2068
|
+
if [[ ${#missing[@]} -eq 0 ]]; then
|
|
2069
|
+
test_pass "SKILL.md documents all installer options (--provider, --non-interactive, --upgrade)"
|
|
2070
|
+
else
|
|
2071
|
+
test_fail "SKILL.md missing documentation for: ${missing[*]}"
|
|
2072
|
+
fi
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
test_skill_md_documents_options
|
|
2076
|
+
|
|
2077
|
+
echo ""
|
|
2078
|
+
echo "========================================"
|
|
2079
|
+
echo "Results: ${TESTS_PASSED}/${TESTS_RUN} passed, ${TESTS_FAILED} failed"
|
|
2080
|
+
echo "========================================"
|
|
2081
|
+
|
|
2082
|
+
if [[ "$TESTS_FAILED" -gt 0 ]]; then
|
|
2083
|
+
exit 1
|
|
2084
|
+
fi
|
|
2085
|
+
|
|
2086
|
+
exit 0
|