@hitechclaw/clawspark 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +378 -0
  4. package/clawspark +2715 -0
  5. package/configs/models.yaml +108 -0
  6. package/configs/skill-packs.yaml +44 -0
  7. package/configs/skills.yaml +37 -0
  8. package/install.sh +387 -0
  9. package/lib/common.sh +249 -0
  10. package/lib/detect-hardware.sh +156 -0
  11. package/lib/diagnose.sh +636 -0
  12. package/lib/render-diagram.sh +47 -0
  13. package/lib/sandbox-commands.sh +415 -0
  14. package/lib/secure.sh +244 -0
  15. package/lib/select-model.sh +442 -0
  16. package/lib/setup-browser.sh +138 -0
  17. package/lib/setup-dashboard.sh +228 -0
  18. package/lib/setup-inference.sh +128 -0
  19. package/lib/setup-mcp.sh +142 -0
  20. package/lib/setup-messaging.sh +242 -0
  21. package/lib/setup-models.sh +121 -0
  22. package/lib/setup-openclaw.sh +808 -0
  23. package/lib/setup-sandbox.sh +188 -0
  24. package/lib/setup-skills.sh +113 -0
  25. package/lib/setup-systemd.sh +224 -0
  26. package/lib/setup-tailscale.sh +188 -0
  27. package/lib/setup-voice.sh +101 -0
  28. package/lib/skill-audit.sh +449 -0
  29. package/lib/verify.sh +177 -0
  30. package/package.json +57 -0
  31. package/scripts/release.sh +133 -0
  32. package/uninstall.sh +161 -0
  33. package/v2/README.md +50 -0
  34. package/v2/configs/providers.yaml +79 -0
  35. package/v2/configs/skills.yaml +36 -0
  36. package/v2/install.sh +116 -0
  37. package/v2/lib/common.sh +285 -0
  38. package/v2/lib/detect-hardware.sh +119 -0
  39. package/v2/lib/select-runtime.sh +273 -0
  40. package/v2/lib/setup-extras.sh +95 -0
  41. package/v2/lib/setup-openclaw.sh +187 -0
  42. package/v2/lib/setup-provider.sh +131 -0
  43. package/v2/lib/verify.sh +133 -0
  44. package/web/index.html +1835 -0
  45. package/web/install.sh +387 -0
  46. package/web/logo-hero.svg +11 -0
  47. package/web/logo-icon.svg +12 -0
  48. package/web/logo.svg +17 -0
  49. package/web/vercel.json +8 -0
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SKILLS_DIR="${HOME}/.openclaw/skills"
5
+ CLAWSPARK_DIR="${CLAWSPARK_DIR:-${HOME}/.clawspark}"
6
+ HASH_FILE="${CLAWSPARK_DIR}/skill-hashes.json"
7
+ AUDIT_REPORT="${CLAWSPARK_DIR}/audit-report.txt"
8
+
9
+ ALLOWLIST=(
10
+ "local-whisper"
11
+ "self-improvement"
12
+ "memory-setup"
13
+ "whatsapp-voice-chat-integration-open-source"
14
+ "deep-research-pro"
15
+ "agent-browser"
16
+ "second-brain"
17
+ "proactive-agent"
18
+ "ddg-web-search"
19
+ "local-web-search-skill"
20
+ )
21
+
22
+ _NETWORK_PATTERNS=(
23
+ 'fetch('
24
+ 'axios'
25
+ 'http\.request'
26
+ 'net\.connect'
27
+ 'XMLHttpRequest'
28
+ 'WebSocket('
29
+ )
30
+
31
+ _CREDENTIAL_PATTERNS=(
32
+ '~/\.ssh'
33
+ '\$HOME/\.ssh'
34
+ '~/\.aws'
35
+ '\$HOME/\.aws'
36
+ '~/\.env'
37
+ '/etc/passwd'
38
+ '/etc/shadow'
39
+ 'process\.env\.'
40
+ 'os\.environ'
41
+ 'keychain'
42
+ 'credential'
43
+ )
44
+
45
+ _OBFUSCATION_PATTERNS=(
46
+ 'eval('
47
+ 'Function('
48
+ 'atob('
49
+ 'Buffer\.from.*base64'
50
+ 'exec('
51
+ '__import__'
52
+ 'compile('
53
+ )
54
+
55
+ _EXFILTRATION_PATTERNS=(
56
+ 'FormData'
57
+ 'encodeURIComponent.*token'
58
+ 'btoa('
59
+ )
60
+
61
+ _FILESYSTEM_PATTERNS=(
62
+ '\.\./\.\.'
63
+ '\.\.\\/\.\.\\'
64
+ )
65
+
66
+ _PROCESS_PATTERNS=(
67
+ 'child_process'
68
+ 'subprocess'
69
+ 'os\.system'
70
+ 'os\.popen'
71
+ )
72
+
73
+ _is_allowlisted() {
74
+ local name="$1"
75
+ for allowed in "${ALLOWLIST[@]}"; do
76
+ if [[ "${name}" == "${allowed}" ]]; then
77
+ return 0
78
+ fi
79
+ done
80
+ return 1
81
+ }
82
+
83
+ _compute_skill_hash() {
84
+ local skill_dir="$1"
85
+ if check_command shasum; then
86
+ find "${skill_dir}" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.py' -o -name '*.sh' -o -name '*.json' \) \
87
+ -exec shasum -a 256 {} \; 2>/dev/null | sort | shasum -a 256 | cut -d' ' -f1
88
+ elif check_command sha256sum; then
89
+ find "${skill_dir}" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.py' -o -name '*.sh' -o -name '*.json' \) \
90
+ -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | cut -d' ' -f1
91
+ else
92
+ echo "no-hash-tool"
93
+ fi
94
+ }
95
+
96
+ _load_hashes() {
97
+ if [[ -f "${HASH_FILE}" ]]; then
98
+ cat "${HASH_FILE}"
99
+ else
100
+ echo "{}"
101
+ fi
102
+ }
103
+
104
+ _save_hashes() {
105
+ local json="$1"
106
+ echo "${json}" > "${HASH_FILE}"
107
+ chmod 600 "${HASH_FILE}"
108
+ }
109
+
110
+ _escape_sed() {
111
+ printf '%s' "$1" | sed 's/[&/\]/\\&/g'
112
+ }
113
+
114
+ _get_stored_hash() {
115
+ local json="$1"
116
+ local skill="$2"
117
+ if check_command jq; then
118
+ echo "${json}" | jq -r --arg k "${skill}" '.[$k] // empty' 2>/dev/null || echo ""
119
+ else
120
+ local escaped
121
+ escaped=$(_escape_sed "${skill}")
122
+ echo "${json}" | grep -o "\"${escaped}\":\"[a-f0-9]*\"" | cut -d'"' -f4 || echo ""
123
+ fi
124
+ }
125
+
126
+ _set_hash_entry() {
127
+ local json="$1"
128
+ local skill="$2"
129
+ local hash="$3"
130
+
131
+ if check_command jq; then
132
+ echo "${json}" | jq --arg k "${skill}" --arg v "${hash}" '.[$k] = $v' 2>/dev/null
133
+ return
134
+ fi
135
+
136
+ local escaped_skill
137
+ escaped_skill=$(_escape_sed "${skill}")
138
+
139
+ if [[ "${json}" == "{}" ]]; then
140
+ echo "{\"${skill}\":\"${hash}\"}"
141
+ return
142
+ fi
143
+
144
+ if echo "${json}" | grep -q "\"${escaped_skill}\":"; then
145
+ echo "${json}" | sed "s/\"${escaped_skill}\":\"[a-f0-9]*\"/\"${escaped_skill}\":\"${hash}\"/"
146
+ else
147
+ echo "${json}" | sed "s/}$/,\"${escaped_skill}\":\"${hash}\"}/"
148
+ fi
149
+ }
150
+
151
+ _scan_patterns() {
152
+ local skill_dir="$1"
153
+ local -a findings=()
154
+
155
+ local source_file_list
156
+ source_file_list=$(mktemp)
157
+ find "${skill_dir}" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.py' -o -name '*.sh' \) -print0 2>/dev/null > "${source_file_list}" || true
158
+
159
+ if [[ ! -s "${source_file_list}" ]]; then
160
+ rm -f "${source_file_list}"
161
+ echo ""
162
+ return
163
+ fi
164
+
165
+ local -a _categories=("network" "credential" "obfuscation" "exfiltration" "path_traversal" "process_spawn")
166
+ local -a _arrays=("_NETWORK_PATTERNS" "_CREDENTIAL_PATTERNS" "_OBFUSCATION_PATTERNS" "_EXFILTRATION_PATTERNS" "_FILESYSTEM_PATTERNS" "_PROCESS_PATTERNS")
167
+
168
+ local i pattern category arr_name
169
+ for i in $(seq 0 $(( ${#_categories[@]} - 1 ))); do
170
+ category="${_categories[$i]}"
171
+ arr_name="${_arrays[$i]}"
172
+ eval "local -a _pats=(\"\${${arr_name}[@]}\")"
173
+ for pattern in "${_pats[@]}"; do
174
+ if xargs -0 grep -l "${pattern}" < "${source_file_list}" 2>/dev/null | head -1 | grep -q .; then
175
+ findings+=("${category}:${pattern}")
176
+ fi
177
+ done
178
+ done
179
+
180
+ rm -f "${source_file_list}"
181
+
182
+ local IFS="|"
183
+ echo "${findings[*]:-}"
184
+ }
185
+
186
+ _dir_size_human() {
187
+ du -sh "$1" 2>/dev/null | cut -f1 | tr -d ' '
188
+ }
189
+
190
+ audit_skills() {
191
+ log_info "Running skill security audit..."
192
+ hr
193
+
194
+ mkdir -p "${CLAWSPARK_DIR}"
195
+
196
+ if [[ ! -d "${SKILLS_DIR}" ]]; then
197
+ log_warn "Skills directory not found: ${SKILLS_DIR}"
198
+ log_info "No skills installed. Nothing to audit."
199
+ return 0
200
+ fi
201
+
202
+ local -a skill_dirs=()
203
+ for d in "${SKILLS_DIR}"/*/; do
204
+ [[ -d "${d}" ]] && skill_dirs+=("${d}")
205
+ done
206
+
207
+ if [[ ${#skill_dirs[@]} -eq 0 ]]; then
208
+ log_info "No skills installed. Nothing to audit."
209
+ return 0
210
+ fi
211
+
212
+ local total_pass=0
213
+ local total_warn=0
214
+ local total_fail=0
215
+ local timestamp
216
+ timestamp=$(date '+%Y-%m-%d %H:%M:%S')
217
+
218
+ local hashes
219
+ hashes=$(_load_hashes)
220
+
221
+ local col_name=30
222
+ local col_status=8
223
+ local separator
224
+ separator=$(printf '%90s' '' | tr ' ' '─')
225
+ local header_line
226
+ header_line=$(printf '%-*s %-*s %s' ${col_name} "SKILL NAME" ${col_status} "STATUS" "FINDINGS")
227
+
228
+ printf '\n %s%s%s\n' "${BOLD}" "${header_line}" "${RESET}"
229
+ printf ' %s%s%s\n' "${BLUE}" "${separator}" "${RESET}"
230
+
231
+ local report_lines=()
232
+ report_lines+=("ClawSpark Skill Audit Report")
233
+ report_lines+=("Timestamp: ${timestamp}")
234
+ report_lines+=("Skills directory: ${SKILLS_DIR}")
235
+ report_lines+=("${separator}")
236
+ report_lines+=("${header_line}")
237
+ report_lines+=("${separator}")
238
+
239
+ for skill_path in "${skill_dirs[@]}"; do
240
+ local skill_name
241
+ skill_name=$(basename "${skill_path}")
242
+ local size
243
+ size=$(_dir_size_human "${skill_path}")
244
+
245
+ local -a issues=()
246
+ local status="PASS"
247
+ local status_color="${GREEN}"
248
+
249
+ if [[ ! -f "${skill_path}/SKILL.md" ]] && [[ ! -f "${skill_path}/skill.md" ]]; then
250
+ issues+=("missing SKILL.md")
251
+ if [[ "${status}" != "FAIL" ]]; then
252
+ status="WARN"
253
+ status_color="${YELLOW}"
254
+ fi
255
+ fi
256
+
257
+ if ! _is_allowlisted "${skill_name}"; then
258
+ issues+=("unverified (not in allowlist)")
259
+ if [[ "${status}" != "FAIL" ]]; then
260
+ status="WARN"
261
+ status_color="${YELLOW}"
262
+ fi
263
+ fi
264
+
265
+ local scan_result
266
+ scan_result=$(_scan_patterns "${skill_path}")
267
+
268
+ if [[ -n "${scan_result}" ]]; then
269
+ local IFS='|'
270
+ local -a pattern_hits=()
271
+ read -ra pattern_hits <<< "${scan_result}"
272
+
273
+ local has_critical=false
274
+ local finding
275
+ for finding in "${pattern_hits[@]}"; do
276
+ local category="${finding%%:*}"
277
+ local detail="${finding#*:}"
278
+ issues+=("${category}: ${detail}")
279
+ case "${category}" in
280
+ credential|exfiltration|obfuscation|process_spawn)
281
+ has_critical=true
282
+ ;;
283
+ esac
284
+ done
285
+
286
+ if ${has_critical}; then
287
+ status="FAIL"
288
+ status_color="${RED}"
289
+ elif [[ "${status}" != "FAIL" ]]; then
290
+ status="WARN"
291
+ status_color="${YELLOW}"
292
+ fi
293
+ fi
294
+
295
+ local current_hash
296
+ current_hash=$(_compute_skill_hash "${skill_path}")
297
+ local stored_hash
298
+ stored_hash=$(_get_stored_hash "${hashes}" "${skill_name}")
299
+
300
+ if [[ -n "${stored_hash}" && "${stored_hash}" != "${current_hash}" && "${stored_hash}" != "no-hash-tool" && "${current_hash}" != "no-hash-tool" ]]; then
301
+ issues+=("files changed since last audit")
302
+ if [[ "${status}" == "PASS" ]]; then
303
+ status="WARN"
304
+ status_color="${YELLOW}"
305
+ fi
306
+ fi
307
+
308
+ hashes=$(_set_hash_entry "${hashes}" "${skill_name}" "${current_hash}")
309
+
310
+ local findings_str
311
+ if [[ ${#issues[@]} -eq 0 ]]; then
312
+ findings_str="clean (${size})"
313
+ else
314
+ local IFS="; "
315
+ findings_str="${issues[*]} (${size})"
316
+ fi
317
+
318
+ printf ' %-*s %s%-*s%s %s\n' \
319
+ ${col_name} "${skill_name}" \
320
+ "${status_color}" ${col_status} "${status}" "${RESET}" \
321
+ "${findings_str}"
322
+
323
+ report_lines+=("$(printf '%-*s %-*s %s' ${col_name} "${skill_name}" ${col_status} "${status}" "${findings_str}")")
324
+
325
+ case "${status}" in
326
+ PASS) total_pass=$(( total_pass + 1 )) ;;
327
+ WARN) total_warn=$(( total_warn + 1 )) ;;
328
+ FAIL) total_fail=$(( total_fail + 1 )) ;;
329
+ esac
330
+ done
331
+
332
+ _save_hashes "${hashes}"
333
+
334
+ printf ' %s%s%s\n\n' "${BLUE}" "${separator}" "${RESET}"
335
+
336
+ report_lines+=("${separator}")
337
+ report_lines+=("")
338
+
339
+ local summary
340
+ summary="Total: ${GREEN}${total_pass} PASS${RESET}, ${YELLOW}${total_warn} WARN${RESET}, ${RED}${total_fail} FAIL${RESET}"
341
+ printf ' %s\n\n' "${summary}"
342
+
343
+ local summary_plain="Total: ${total_pass} PASS, ${total_warn} WARN, ${total_fail} FAIL"
344
+ report_lines+=("${summary_plain}")
345
+
346
+ {
347
+ for line in "${report_lines[@]}"; do
348
+ echo "${line}"
349
+ done
350
+ } > "${AUDIT_REPORT}"
351
+ chmod 600 "${AUDIT_REPORT}"
352
+
353
+ if (( total_fail > 0 )); then
354
+ log_error "${total_fail} skill(s) flagged as FAIL — review immediately."
355
+ print_box \
356
+ "${RED}${BOLD}Security Alert${RESET}" \
357
+ "" \
358
+ "${total_fail} skill(s) contain suspicious patterns." \
359
+ "Run 'clawspark skills remove <name>' for any untrusted skill." \
360
+ "Report: ${AUDIT_REPORT}"
361
+ elif (( total_warn > 0 )); then
362
+ log_warn "${total_warn} skill(s) flagged as WARN — review recommended."
363
+ else
364
+ log_success "All ${total_pass} skill(s) passed security audit."
365
+ fi
366
+
367
+ log_info "Audit report saved to ${AUDIT_REPORT}"
368
+ log_info "Hash database saved to ${HASH_FILE}"
369
+ }
370
+
371
+ _quick_skill_check() {
372
+ local skill_path="$1"
373
+ local skill_name
374
+ skill_name=$(basename "${skill_path}")
375
+
376
+ if [[ ! -d "${skill_path}" ]]; then
377
+ log_error "Skill directory not found: ${skill_path}"
378
+ return 1
379
+ fi
380
+
381
+ log_info "Quick security check: ${skill_name}"
382
+
383
+ local -a issues=()
384
+ local status="PASS"
385
+
386
+ if [[ ! -f "${skill_path}/SKILL.md" ]] && [[ ! -f "${skill_path}/skill.md" ]]; then
387
+ issues+=("missing SKILL.md")
388
+ status="WARN"
389
+ fi
390
+
391
+ if ! _is_allowlisted "${skill_name}"; then
392
+ issues+=("not in curated allowlist")
393
+ status="WARN"
394
+ fi
395
+
396
+ local scan_result
397
+ scan_result=$(_scan_patterns "${skill_path}")
398
+
399
+ if [[ -n "${scan_result}" ]]; then
400
+ local IFS='|'
401
+ local -a pattern_hits=()
402
+ read -ra pattern_hits <<< "${scan_result}"
403
+
404
+ local finding
405
+ for finding in "${pattern_hits[@]}"; do
406
+ local category="${finding%%:*}"
407
+ local detail="${finding#*:}"
408
+ issues+=("${category}: ${detail}")
409
+ case "${category}" in
410
+ credential|exfiltration|obfuscation|process_spawn)
411
+ status="FAIL"
412
+ ;;
413
+ *)
414
+ if [[ "${status}" != "FAIL" ]]; then
415
+ status="WARN"
416
+ fi
417
+ ;;
418
+ esac
419
+ done
420
+ fi
421
+
422
+ case "${status}" in
423
+ PASS)
424
+ log_success "Skill '${skill_name}' passed pre-install check."
425
+ return 0
426
+ ;;
427
+ WARN)
428
+ log_warn "Skill '${skill_name}' has warnings:"
429
+ for issue in "${issues[@]}"; do
430
+ printf ' %s⚠%s %s\n' "${YELLOW}" "${RESET}" "${issue}"
431
+ done
432
+ return 0
433
+ ;;
434
+ FAIL)
435
+ log_error "Skill '${skill_name}' BLOCKED — suspicious patterns detected:"
436
+ for issue in "${issues[@]}"; do
437
+ printf ' %s✗%s %s\n' "${RED}" "${RESET}" "${issue}"
438
+ done
439
+ printf '\n'
440
+ print_box \
441
+ "${RED}${BOLD}Installation Blocked${RESET}" \
442
+ "" \
443
+ "Skill '${skill_name}' contains patterns associated with malware." \
444
+ "341 malicious skills were found on ClawHub (Atomic Stealer, data theft)." \
445
+ "Use --force to override this check at your own risk."
446
+ return 1
447
+ ;;
448
+ esac
449
+ }
package/lib/verify.sh ADDED
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env bash
2
+ # lib/verify.sh — Post-installation health checks and performance benchmark.
3
+ set -euo pipefail
4
+
5
+ verify_installation() {
6
+ log_info "Running verification checks..."
7
+ hr
8
+
9
+ local pass=0
10
+ local fail=0
11
+
12
+ # ── Helper to print check results ───────────────────────────────────────
13
+ _check_pass() {
14
+ printf ' %s✓%s %s\n' "${GREEN}" "${RESET}" "$1"
15
+ pass=$(( pass + 1 ))
16
+ }
17
+ _check_fail() {
18
+ printf ' %s✗%s %s\n' "${RED}" "${RESET}" "$1"
19
+ fail=$(( fail + 1 ))
20
+ }
21
+
22
+ printf '\n'
23
+
24
+ # ── 1. Hardware ─────────────────────────────────────────────────────────
25
+ local ram_gb=$(( ${HW_TOTAL_RAM_MB:-0} / 1024 ))
26
+ local platform_label
27
+ case "${HW_PLATFORM:-generic}" in
28
+ dgx-spark) platform_label="DGX Spark (${ram_gb}GB unified memory)" ;;
29
+ jetson) platform_label="Jetson (${ram_gb}GB)" ;;
30
+ rtx) platform_label="RTX (${HW_GPU_VRAM_MB:-0}MB VRAM)" ;;
31
+ mac) platform_label="macOS Apple Silicon (${ram_gb}GB unified)" ;;
32
+ *) platform_label="Generic (${ram_gb}GB RAM)" ;;
33
+ esac
34
+ _check_pass "Hardware: ${platform_label}"
35
+
36
+ # ── 2. Model ────────────────────────────────────────────────────────────
37
+ _check_pass "Model: ${SELECTED_MODEL_NAME:-unknown} (${SELECTED_MODEL_ID:-unknown})"
38
+
39
+ # ── 3. Ollama ───────────────────────────────────────────────────────────
40
+ if curl -sf http://127.0.0.1:11434/ &>/dev/null; then
41
+ _check_pass "Inference: Ollama (http://127.0.0.1:11434)"
42
+ else
43
+ _check_fail "Inference: Ollama is not responding"
44
+ fi
45
+
46
+ # ── 4. Model loaded ────────────────────────────────────────────────────
47
+ if ollama list 2>/dev/null | grep -q "${SELECTED_MODEL_ID}"; then
48
+ _check_pass "Model available in Ollama"
49
+ else
50
+ _check_fail "Model ${SELECTED_MODEL_ID} not found in Ollama"
51
+ fi
52
+
53
+ # ── 5. OpenClaw ─────────────────────────────────────────────────────────
54
+ if check_command openclaw; then
55
+ local oc_ver
56
+ oc_ver=$(openclaw --version 2>/dev/null || echo "unknown")
57
+ local gw_status="gateway not running"
58
+ if check_command systemctl && systemctl is-active --quiet clawspark-gateway.service 2>/dev/null; then
59
+ gw_status="gateway running (systemd)"
60
+ elif [[ -f "${CLAWSPARK_DIR}/gateway.pid" ]]; then
61
+ local gw_pid
62
+ gw_pid=$(cat "${CLAWSPARK_DIR}/gateway.pid")
63
+ if [[ -n "${gw_pid}" ]] && kill -0 "${gw_pid}" 2>/dev/null; then
64
+ gw_status="gateway running"
65
+ fi
66
+ fi
67
+ _check_pass "OpenClaw: ${oc_ver} (${gw_status})"
68
+ else
69
+ _check_fail "OpenClaw: not installed"
70
+ fi
71
+
72
+ # ── 6. Tools profile ──────────────────────────────────────────────────
73
+ local tools_profile
74
+ tools_profile=$(openclaw config get tools.profile 2>/dev/null || echo "unknown")
75
+ if [[ "${tools_profile}" == *"full"* ]]; then
76
+ _check_pass "Tools: full profile (web, exec, browser, filesystem)"
77
+ else
78
+ _check_fail "Tools: profile is '${tools_profile}' (should be 'full')"
79
+ fi
80
+
81
+ # ── 7. Node host ──────────────────────────────────────────────────────
82
+ if check_command systemctl && systemctl is-active --quiet clawspark-nodehost.service 2>/dev/null; then
83
+ _check_pass "Node host: running (systemd)"
84
+ elif [[ -f "${CLAWSPARK_DIR}/node.pid" ]]; then
85
+ local node_pid
86
+ node_pid=$(cat "${CLAWSPARK_DIR}/node.pid")
87
+ if [[ -n "${node_pid}" ]] && kill -0 "${node_pid}" 2>/dev/null; then
88
+ _check_pass "Node host: running (PID ${node_pid})"
89
+ else
90
+ _check_fail "Node host: not running"
91
+ fi
92
+ else
93
+ _check_fail "Node host: not started"
94
+ fi
95
+
96
+ # ── 8. Skills ──────────────────────────────────────────────────────────
97
+ local skill_count=0
98
+ if [[ -f "${CLAWSPARK_DIR}/skills.yaml" ]]; then
99
+ skill_count=$(grep -c '^ *- ' "${CLAWSPARK_DIR}/skills.yaml" 2>/dev/null || echo 0)
100
+ fi
101
+ _check_pass "Skills: ${skill_count} configured"
102
+
103
+ # ── 9. Voice ────────────────────────────────────────────────────────────
104
+ local whisper_model="${WHISPER_MODEL:-unknown}"
105
+ if [[ -f "${HOME}/.openclaw/skills/local-whisper/config.json" ]]; then
106
+ _check_pass "Voice: Whisper ${whisper_model} ready"
107
+ else
108
+ _check_fail "Voice: Whisper config not found"
109
+ fi
110
+
111
+ # ── 10. Security ────────────────────────────────────────────────────────
112
+ if [[ -f "${CLAWSPARK_DIR}/token" ]]; then
113
+ _check_pass "Security: localhost-only, token auth"
114
+ else
115
+ _check_fail "Security: token not generated"
116
+ fi
117
+
118
+ # ── 11. Messaging ───────────────────────────────────────────────────────
119
+ local msg_status="${MESSAGING_CHOICE:-skip}"
120
+ if [[ "${msg_status}" != "skip" ]]; then
121
+ _check_pass "Messaging: ${msg_status} configured"
122
+ else
123
+ _check_pass "Messaging: skipped"
124
+ fi
125
+
126
+ # ── 12. Quick benchmark ────────────────────────────────────────────────
127
+ local toks_str="n/a"
128
+ if curl -sf http://127.0.0.1:11434/ &>/dev/null; then
129
+ toks_str=$(_run_benchmark)
130
+ fi
131
+ _check_pass "Performance: ${toks_str}"
132
+
133
+ # ── Summary ─────────────────────────────────────────────────────────────
134
+ printf '\n'
135
+ if (( fail == 0 )); then
136
+ log_success "All ${pass} checks passed."
137
+ else
138
+ log_warn "${pass} passed, ${fail} failed. Review the items marked with ✗ above."
139
+ fi
140
+ }
141
+
142
+ # ── Benchmark ───────────────────────────────────────────────────────────────
143
+ # Sends a short prompt and measures tokens/second from the Ollama API.
144
+ _run_benchmark() {
145
+ local start_ms end_ms elapsed_ms
146
+ local response tok_count tps
147
+
148
+ start_ms=$(date +%s%3N 2>/dev/null || python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null || echo 0)
149
+
150
+ response=$(curl -sf --max-time 30 http://127.0.0.1:11434/api/generate \
151
+ -d "{\"model\":\"${SELECTED_MODEL_ID}\",\"prompt\":\"Count from 1 to 10.\",\"stream\":false,\"options\":{\"num_predict\":10}}" 2>/dev/null || echo "")
152
+
153
+ end_ms=$(date +%s%3N 2>/dev/null || python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null || echo 0)
154
+
155
+ if [[ -z "${response}" ]]; then
156
+ echo "benchmark skipped (API timeout)"
157
+ return 0
158
+ fi
159
+
160
+ # Try to extract eval_count and eval_duration from Ollama response
161
+ local eval_count eval_duration_ns
162
+ eval_count=$(echo "${response}" | grep -o '"eval_count":[0-9]*' | cut -d: -f2 || echo "")
163
+ eval_duration_ns=$(echo "${response}" | grep -o '"eval_duration":[0-9]*' | cut -d: -f2 || echo "")
164
+
165
+ if [[ -n "${eval_count}" && -n "${eval_duration_ns}" && "${eval_duration_ns}" -gt 0 ]]; then
166
+ # eval_duration is in nanoseconds
167
+ tps=$(awk "BEGIN {printf \"%.1f\", ${eval_count} / (${eval_duration_ns} / 1000000000)}")
168
+ echo "~${tps} tok/s"
169
+ else
170
+ elapsed_ms=$(( end_ms - start_ms ))
171
+ if (( elapsed_ms > 0 )); then
172
+ echo "~$(( 10 * 1000 / elapsed_ms )) tok/s (estimated)"
173
+ else
174
+ echo "benchmark unavailable"
175
+ fi
176
+ fi
177
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@hitechclaw/clawspark",
3
+ "version": "2.0.0",
4
+ "description": "One-command OpenClaw installer and CLI for local, CPU-first, and API-backed AI agent deployments.",
5
+ "author": "hitechclaw",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/thanhan92-f1/clawspark#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/thanhan92-f1/clawspark.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/thanhan92-f1/clawspark/issues"
14
+ },
15
+ "keywords": [
16
+ "openclaw",
17
+ "ai-agent",
18
+ "cli",
19
+ "ollama",
20
+ "installer",
21
+ "local-ai",
22
+ "cpu-ai",
23
+ "api-gateway"
24
+ ],
25
+ "bin": {
26
+ "clawspark": "clawspark"
27
+ },
28
+ "files": [
29
+ "clawspark",
30
+ "install.sh",
31
+ "uninstall.sh",
32
+ "scripts",
33
+ "lib",
34
+ "configs",
35
+ "v2",
36
+ "web",
37
+ "README.md",
38
+ "LICENSE",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "scripts": {
42
+ "lint": "bash -n ./clawspark",
43
+ "test": "bash ./tests/run.sh",
44
+ "pack:check": "npm pack --dry-run",
45
+ "release:patch": "bash ./scripts/release.sh patch",
46
+ "release:minor": "bash ./scripts/release.sh minor",
47
+ "release:major": "bash ./scripts/release.sh major"
48
+ },
49
+ "preferGlobal": true,
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "provenance": true
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ }
57
+ }