@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,285 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CLAWSPARK_DIR="${CLAWSPARK_DIR:-${HOME}/.clawspark-v2}"
5
+ CLAWSPARK_LOG="${CLAWSPARK_LOG:-${CLAWSPARK_DIR}/install.log}"
6
+ CLAWSPARK_V2_DIR="${CLAWSPARK_DIR}"
7
+ CLAWSPARK_V2_LOG="${CLAWSPARK_LOG}"
8
+ CLAWSPARK_DEFAULTS="${CLAWSPARK_DEFAULTS:-false}"
9
+
10
+ if [[ -t 1 ]] && command -v tput &>/dev/null && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
11
+ RED=$(tput setaf 1)
12
+ GREEN=$(tput setaf 2)
13
+ YELLOW=$(tput setaf 3)
14
+ BLUE=$(tput setaf 4)
15
+ CYAN=$(tput setaf 6)
16
+ BOLD=$(tput bold)
17
+ RESET=$(tput sgr0)
18
+ else
19
+ RED=$'\033[0;31m'
20
+ GREEN=$'\033[0;32m'
21
+ YELLOW=$'\033[0;33m'
22
+ BLUE=$'\033[0;34m'
23
+ CYAN=$'\033[0;36m'
24
+ BOLD=$'\033[1m'
25
+ RESET=$'\033[0m'
26
+ fi
27
+
28
+ to_lower() { echo "$1" | tr '[:upper:]' '[:lower:]'; }
29
+
30
+ _ts() { date '+%H:%M:%S'; }
31
+
32
+ _log_to_file() {
33
+ local level="$1"; shift
34
+ mkdir -p "${CLAWSPARK_DIR}"
35
+ printf '[%s] [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${level}" "$*" >> "${CLAWSPARK_LOG}" 2>/dev/null || true
36
+ }
37
+
38
+ log_info() {
39
+ printf '%s[%s]%s %s\n' "${BLUE}" "$(_ts)" "${RESET}" "$*"
40
+ _log_to_file "INFO" "$*"
41
+ }
42
+
43
+ log_warn() {
44
+ printf '%s[%s] WARNING:%s %s\n' "${YELLOW}" "$(_ts)" "${RESET}" "$*" >&2
45
+ _log_to_file "WARN" "$*"
46
+ }
47
+
48
+ log_error() {
49
+ printf '%s[%s] ERROR:%s %s\n' "${RED}" "$(_ts)" "${RESET}" "$*" >&2
50
+ _log_to_file "ERROR" "$*"
51
+ }
52
+
53
+ log_success() {
54
+ printf '%s[%s] OK:%s %s\n' "${GREEN}" "$(_ts)" "${RESET}" "$*"
55
+ _log_to_file "OK" "$*"
56
+ }
57
+
58
+ check_command() {
59
+ command -v "$1" &>/dev/null
60
+ }
61
+
62
+ prompt_choice() {
63
+ local question="$1"
64
+ local options_name="$2"
65
+ local default_idx="${3:-0}"
66
+ local count
67
+ eval "count=\${#${options_name}[@]}"
68
+
69
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
70
+ eval "printf '%s' \"\${${options_name}[${default_idx}]}\""
71
+ return 0
72
+ fi
73
+
74
+ printf '\n%s%s%s\n' "${BOLD}" "${question}" "${RESET}" >/dev/tty
75
+ local i opt marker
76
+ for i in $(seq 0 $(( count - 1 ))); do
77
+ marker=""
78
+ if [[ "${i}" -eq "${default_idx}" ]]; then
79
+ marker=" ${CYAN}(default)${RESET}"
80
+ fi
81
+ eval "opt=\${${options_name}[${i}]}"
82
+ printf ' %s%d)%s %s%s\n' "${GREEN}" $(( i + 1 )) "${RESET}" "${opt}" "${marker}" >/dev/tty
83
+ done
84
+
85
+ local selection
86
+ while true; do
87
+ printf '%s> %s' "${BOLD}" "${RESET}" >/dev/tty
88
+ read -r selection </dev/tty || selection=""
89
+ if [[ -z "${selection}" ]]; then
90
+ eval "printf '%s' \"\${${options_name}[${default_idx}]}\""
91
+ return 0
92
+ fi
93
+ if [[ "${selection}" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= count )); then
94
+ eval "printf '%s' \"\${${options_name}[$(( selection - 1 ))]}\""
95
+ return 0
96
+ fi
97
+ printf ' %sPlease enter a number between 1 and %d%s\n' "${YELLOW}" "${count}" "${RESET}" >/dev/tty
98
+ done
99
+ }
100
+
101
+ prompt_input() {
102
+ local question="$1"
103
+ local default_value="${2:-}"
104
+
105
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
106
+ printf '%s' "${default_value}"
107
+ return 0
108
+ fi
109
+
110
+ if [[ -n "${default_value}" ]]; then
111
+ printf '\n%s%s%s [%s]: ' "${BOLD}" "${question}" "${RESET}" "${default_value}" >/dev/tty
112
+ else
113
+ printf '\n%s%s%s: ' "${BOLD}" "${question}" "${RESET}" >/dev/tty
114
+ fi
115
+
116
+ local value
117
+ read -r value </dev/tty || value=""
118
+ if [[ -z "${value}" ]]; then
119
+ value="${default_value}"
120
+ fi
121
+ printf '%s' "${value}"
122
+ }
123
+
124
+ prompt_secret() {
125
+ local question="$1"
126
+
127
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
128
+ printf '%s' "${FLAG_API_KEY:-}"
129
+ return 0
130
+ fi
131
+
132
+ printf '\n%s%s%s: ' "${BOLD}" "${question}" "${RESET}" >/dev/tty
133
+ local value=""
134
+ stty -echo </dev/tty
135
+ read -r value </dev/tty || value=""
136
+ stty echo </dev/tty
137
+ printf '\n' >/dev/tty
138
+ printf '%s' "${value}"
139
+ }
140
+
141
+ prompt_yn() {
142
+ local question="$1"
143
+ local default="${2:-y}"
144
+
145
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
146
+ [[ "${default}" == "y" ]] && return 0 || return 1
147
+ fi
148
+
149
+ local hint="[y/N]"
150
+ [[ "${default}" == "y" ]] && hint="[Y/n]"
151
+ printf '\n%s%s %s%s ' "${BOLD}" "${question}" "${hint}" "${RESET}" >/dev/tty
152
+
153
+ local answer
154
+ read -r answer </dev/tty || answer=""
155
+ answer=$(to_lower "${answer}")
156
+ if [[ -z "${answer}" ]]; then
157
+ [[ "${default}" == "y" ]] && return 0 || return 1
158
+ fi
159
+ [[ "${answer}" =~ ^y(es)?$ ]]
160
+ }
161
+
162
+ spinner() {
163
+ local pid="$1"
164
+ local msg="${2:-Working...}"
165
+ local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
166
+ local frame_count=${#frames[@]}
167
+ local i=0
168
+
169
+ if [[ ! -t 1 ]]; then
170
+ wait "${pid}" 2>/dev/null || true
171
+ return 0
172
+ fi
173
+
174
+ printf ' '
175
+ while kill -0 "${pid}" 2>/dev/null; do
176
+ printf '\r %s%s%s %s' "${CYAN}" "${frames[i % frame_count]}" "${RESET}" "${msg}"
177
+ i=$(( i + 1 ))
178
+ sleep 0.08
179
+ done
180
+
181
+ wait "${pid}" 2>/dev/null
182
+ local exit_code=$?
183
+ if [[ ${exit_code} -eq 0 ]]; then
184
+ printf '\r %s✓%s %s\n' "${GREEN}" "${RESET}" "${msg}"
185
+ else
186
+ printf '\r %s✗%s %s\n' "${RED}" "${RESET}" "${msg}"
187
+ fi
188
+ return 0
189
+ }
190
+
191
+ hr() {
192
+ local cols
193
+ cols=$(tput cols 2>/dev/null || echo 60)
194
+ printf '%s%*s%s\n' "${BLUE}" "${cols}" '' "${RESET}" | tr ' ' '─'
195
+ }
196
+
197
+ print_box() {
198
+ local lines=("$@")
199
+ local max_len=0
200
+ local line
201
+
202
+ for line in "${lines[@]}"; do
203
+ local stripped
204
+ stripped=$(printf '%s' "${line}" | sed 's/\x1b\[[0-9;]*m//g')
205
+ (( ${#stripped} > max_len )) && max_len=${#stripped}
206
+ done
207
+
208
+ local pad=$(( max_len + 2 ))
209
+ printf '%s┌%s┐%s\n' "${BLUE}" "$(printf '─%.0s' $(seq 1 "${pad}"))" "${RESET}"
210
+ for line in "${lines[@]}"; do
211
+ local stripped
212
+ stripped=$(printf '%s' "${line}" | sed 's/\x1b\[[0-9;]*m//g')
213
+ local spaces=$(( max_len - ${#stripped} ))
214
+ printf '%s│%s %s%*s %s│%s\n' "${BLUE}" "${RESET}" "${line}" "${spaces}" '' "${BLUE}" "${RESET}"
215
+ done
216
+ printf '%s└%s┘%s\n' "${BLUE}" "$(printf '─%.0s' $(seq 1 "${pad}"))" "${RESET}"
217
+ }
218
+
219
+ mask_value() {
220
+ local value="$1"
221
+ if [[ -z "${value}" ]]; then
222
+ printf '(empty)'
223
+ elif [[ ${#value} -le 8 ]]; then
224
+ printf '********'
225
+ else
226
+ printf '%s****%s' "${value:0:4}" "${value: -4}"
227
+ fi
228
+ }
229
+
230
+ write_key_value() {
231
+ local file="$1"
232
+ local key="$2"
233
+ local value="$3"
234
+ touch "${file}"
235
+ if grep -q "^${key}=" "${file}" 2>/dev/null; then
236
+ python3 - "$file" "$key" "$value" <<'PY'
237
+ import sys
238
+ path, key, value = sys.argv[1:4]
239
+ with open(path, 'r', encoding='utf-8') as fh:
240
+ lines = fh.readlines()
241
+ with open(path, 'w', encoding='utf-8') as fh:
242
+ for line in lines:
243
+ if line.startswith(f"{key}="):
244
+ fh.write(f"{key}={value}\n")
245
+ else:
246
+ fh.write(line)
247
+ PY
248
+ else
249
+ printf '%s=%s\n' "${key}" "${value}" >> "${file}"
250
+ fi
251
+ }
252
+
253
+ _parse_enabled_skills() {
254
+ local skills_file="$1"
255
+ [[ -f "${skills_file}" && -r "${skills_file}" ]] || return 1
256
+ local in_enabled=false
257
+
258
+ while IFS= read -r line; do
259
+ [[ "${line}" =~ ^[[:space:]]*# ]] && continue
260
+ [[ -z "${line// }" ]] && continue
261
+
262
+ if [[ "${line}" =~ enabled:[[:space:]]*$ ]]; then
263
+ in_enabled=true
264
+ continue
265
+ fi
266
+ if ${in_enabled} && [[ "${line}" =~ ^[[:space:]]{0,3}[a-zA-Z] ]] && [[ ! "${line}" =~ ^[[:space:]]*- ]]; then
267
+ in_enabled=false
268
+ continue
269
+ fi
270
+ if ${in_enabled} && [[ "${line}" =~ ^[[:space:]]*-[[:space:]]+name:[[:space:]]+(.*) ]]; then
271
+ local slug="${BASH_REMATCH[1]}"
272
+ slug="${slug## }"
273
+ slug="${slug%% }"
274
+ echo "${slug}"
275
+ continue
276
+ fi
277
+ if ${in_enabled} && [[ "${line}" =~ ^[[:space:]]*-[[:space:]]+(.*) ]]; then
278
+ local slug="${BASH_REMATCH[1]}"
279
+ slug="${slug## }"
280
+ slug="${slug%% }"
281
+ [[ "${slug}" =~ ^[a-zA-Z]+: ]] && continue
282
+ echo "${slug}"
283
+ fi
284
+ done < "${skills_file}"
285
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ detect_hardware_v2() {
5
+ log_info "Detecting hardware profile for v2..."
6
+
7
+ HW_CPU_ARCH=$(uname -m)
8
+ if [[ -f /proc/cpuinfo ]]; then
9
+ HW_CPU_CORES=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo 1)
10
+ elif check_command sysctl; then
11
+ HW_CPU_CORES=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
12
+ else
13
+ HW_CPU_CORES=1
14
+ fi
15
+
16
+ if [[ -f /proc/meminfo ]]; then
17
+ local mem_kb
18
+ mem_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo 2>/dev/null || echo 0)
19
+ HW_TOTAL_RAM_MB=$(( mem_kb / 1024 ))
20
+ elif check_command sysctl; then
21
+ local mem_bytes
22
+ mem_bytes=$(sysctl -n hw.memsize 2>/dev/null || echo 0)
23
+ HW_TOTAL_RAM_MB=$(( mem_bytes / 1024 / 1024 ))
24
+ else
25
+ HW_TOTAL_RAM_MB=0
26
+ fi
27
+
28
+ HW_GPU_NAME="none"
29
+ HW_GPU_VRAM_MB=0
30
+ HW_DRIVER_VERSION="n/a"
31
+ HW_ACCELERATION="cpu"
32
+ HW_PLATFORM="cpu-generic"
33
+
34
+ if [[ -f /etc/nv_tegra_release ]] || uname -r 2>/dev/null | grep -qi tegra; then
35
+ HW_PLATFORM="jetson"
36
+ HW_GPU_NAME="NVIDIA Jetson (Tegra)"
37
+ HW_GPU_VRAM_MB="${HW_TOTAL_RAM_MB}"
38
+ HW_ACCELERATION="gpu"
39
+ fi
40
+
41
+ if [[ -f /sys/devices/virtual/dmi/id/product_name ]]; then
42
+ local product_name
43
+ product_name=$(cat /sys/devices/virtual/dmi/id/product_name 2>/dev/null || echo "")
44
+ if echo "${product_name}" | grep -qiE "DGX.Spark|DGX_Spark"; then
45
+ HW_PLATFORM="dgx-spark"
46
+ HW_GPU_NAME="NVIDIA GB10 / DGX Spark"
47
+ HW_GPU_VRAM_MB=131072
48
+ HW_TOTAL_RAM_MB=131072
49
+ HW_ACCELERATION="gpu"
50
+ fi
51
+ fi
52
+
53
+ if check_command nvidia-smi; then
54
+ local gpu_info
55
+ gpu_info=$(nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader,nounits 2>/dev/null || echo "")
56
+ if [[ -n "${gpu_info}" ]]; then
57
+ local first_gpu
58
+ first_gpu=$(echo "${gpu_info}" | head -n1)
59
+ HW_GPU_NAME=$(echo "${first_gpu}" | cut -d',' -f1 | xargs)
60
+ local vram_str
61
+ vram_str=$(echo "${first_gpu}" | cut -d',' -f2 | xargs)
62
+ if [[ "${vram_str}" =~ ^[0-9]+ ]]; then
63
+ HW_GPU_VRAM_MB="${vram_str%%.*}"
64
+ fi
65
+ HW_DRIVER_VERSION=$(echo "${first_gpu}" | cut -d',' -f3 | xargs)
66
+ HW_ACCELERATION="gpu"
67
+ if [[ "${HW_PLATFORM}" == "cpu-generic" ]]; then
68
+ if echo "${HW_GPU_NAME}" | grep -qiE "RTX|GeForce|Quadro"; then
69
+ HW_PLATFORM="rtx"
70
+ else
71
+ HW_PLATFORM="gpu-generic"
72
+ fi
73
+ fi
74
+ fi
75
+ fi
76
+
77
+ if [[ "${HW_PLATFORM}" == "cpu-generic" && "$(uname)" == "Darwin" ]]; then
78
+ local chip_info
79
+ chip_info=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo "")
80
+ if echo "${chip_info}" | grep -qi "Apple"; then
81
+ HW_PLATFORM="mac"
82
+ HW_GPU_NAME="Apple Silicon (${chip_info})"
83
+ HW_GPU_VRAM_MB="${HW_TOTAL_RAM_MB}"
84
+ HW_DRIVER_VERSION="Metal"
85
+ HW_ACCELERATION="gpu"
86
+ fi
87
+ fi
88
+
89
+ if [[ "${HW_ACCELERATION}" == "cpu" ]]; then
90
+ if (( HW_TOTAL_RAM_MB >= 32768 )); then
91
+ HW_CPU_PROFILE="cpu-large"
92
+ elif (( HW_TOTAL_RAM_MB >= 16384 )); then
93
+ HW_CPU_PROFILE="cpu-medium"
94
+ else
95
+ HW_CPU_PROFILE="cpu-small"
96
+ fi
97
+ else
98
+ HW_CPU_PROFILE="with-gpu"
99
+ fi
100
+
101
+ export HW_CPU_ARCH HW_CPU_CORES HW_TOTAL_RAM_MB HW_GPU_NAME HW_GPU_VRAM_MB
102
+ export HW_DRIVER_VERSION HW_ACCELERATION HW_PLATFORM HW_CPU_PROFILE
103
+
104
+ local ram_gb=$(( HW_TOTAL_RAM_MB / 1024 ))
105
+ local vram_gb=$(( HW_GPU_VRAM_MB / 1024 ))
106
+ print_box \
107
+ "${BOLD}v2 Hardware Summary${RESET}" \
108
+ "" \
109
+ "Platform : ${CYAN}${HW_PLATFORM}${RESET}" \
110
+ "Acceleration : ${HW_ACCELERATION}" \
111
+ "GPU : ${HW_GPU_NAME}" \
112
+ "VRAM : ${vram_gb} GB" \
113
+ "System RAM : ${ram_gb} GB" \
114
+ "CPU Cores : ${HW_CPU_CORES} (${HW_CPU_ARCH})" \
115
+ "CPU Profile : ${HW_CPU_PROFILE}" \
116
+ "Driver : ${HW_DRIVER_VERSION}"
117
+
118
+ log_success "Hardware profile ready: ${HW_PLATFORM} / ${HW_ACCELERATION}"
119
+ }
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ select_runtime_v2() {
5
+ log_info "Selecting runtime mode for v2..."
6
+
7
+ local runtime_options=()
8
+ local default_idx=0
9
+
10
+ if [[ "${HW_ACCELERATION:-cpu}" == "gpu" ]]; then
11
+ runtime_options=("local-gpu" "local-cpu" "hybrid" "api-only")
12
+ default_idx=0
13
+ else
14
+ runtime_options=("local-cpu" "api-only" "hybrid")
15
+ default_idx=0
16
+ fi
17
+
18
+ if [[ -n "${FLAG_RUNTIME_MODE:-}" ]]; then
19
+ RUNTIME_MODE="${FLAG_RUNTIME_MODE}"
20
+ else
21
+ RUNTIME_MODE=$(prompt_choice "Choose deployment mode" runtime_options "${default_idx}")
22
+ fi
23
+
24
+ RUNTIME_MODE=$(to_lower "${RUNTIME_MODE}")
25
+ export RUNTIME_MODE
26
+ log_success "Runtime mode: ${RUNTIME_MODE}"
27
+ }
28
+
29
+ select_provider_v2() {
30
+ log_info "Selecting inference provider..."
31
+
32
+ local provider_options=()
33
+ local default_idx=0
34
+
35
+ case "${RUNTIME_MODE}" in
36
+ local-gpu|local-cpu)
37
+ provider_options=("ollama")
38
+ default_idx=0
39
+ ;;
40
+ api-only|hybrid)
41
+ provider_options=("openai" "anthropic" "openrouter" "google" "custom")
42
+ default_idx=0
43
+ ;;
44
+ *)
45
+ provider_options=("ollama")
46
+ default_idx=0
47
+ ;;
48
+ esac
49
+
50
+ if [[ -n "${FLAG_PROVIDER:-}" ]]; then
51
+ PRIMARY_PROVIDER="${FLAG_PROVIDER}"
52
+ else
53
+ PRIMARY_PROVIDER=$(prompt_choice "Choose primary provider" provider_options "${default_idx}")
54
+ fi
55
+
56
+ PRIMARY_PROVIDER=$(to_lower "${PRIMARY_PROVIDER}")
57
+ export PRIMARY_PROVIDER
58
+ log_success "Primary provider: ${PRIMARY_PROVIDER}"
59
+ }
60
+
61
+ select_model_v2() {
62
+ log_info "Selecting default model..."
63
+
64
+ local model_options=()
65
+ local model_ids=()
66
+ local default_idx=0
67
+
68
+ case "${PRIMARY_PROVIDER}" in
69
+ ollama)
70
+ if [[ "${RUNTIME_MODE}" == "local-cpu" ]]; then
71
+ if [[ "${HW_CPU_PROFILE}" == "cpu-large" ]]; then
72
+ model_ids=("qwen2.5:14b-instruct-q4_K_M" "qwen2.5:7b-instruct-q4_K_M" "llama3.1:8b-instruct-q4_K_M")
73
+ model_options=(
74
+ "qwen2.5:14b-instruct-q4_K_M - best CPU quality for 32GB+ RAM"
75
+ "qwen2.5:7b-instruct-q4_K_M - balanced CPU default"
76
+ "llama3.1:8b-instruct-q4_K_M - stable general assistant"
77
+ )
78
+ default_idx=1
79
+ elif [[ "${HW_CPU_PROFILE}" == "cpu-medium" ]]; then
80
+ model_ids=("qwen2.5:7b-instruct-q4_K_M" "llama3.1:8b-instruct-q4_K_M" "phi4-mini")
81
+ model_options=(
82
+ "qwen2.5:7b-instruct-q4_K_M - recommended for 16GB CPUs"
83
+ "llama3.1:8b-instruct-q4_K_M - general purpose CPU model"
84
+ "phi4-mini - lightweight and responsive"
85
+ )
86
+ default_idx=0
87
+ else
88
+ model_ids=("phi4-mini" "qwen2.5:3b-instruct-q4_K_M" "llama3.2:3b")
89
+ model_options=(
90
+ "phi4-mini - lowest RAM recommendation"
91
+ "qwen2.5:3b-instruct-q4_K_M - compact instruct model"
92
+ "llama3.2:3b - small local fallback"
93
+ )
94
+ default_idx=0
95
+ fi
96
+ else
97
+ model_ids=("qwen3.5:35b-a3b" "qwen3-coder:30b" "glm-4.7-flash")
98
+ model_options=(
99
+ "qwen3.5:35b-a3b - default GPU local model"
100
+ "qwen3-coder:30b - coding focused"
101
+ "glm-4.7-flash - faster compact option"
102
+ )
103
+ default_idx=0
104
+ fi
105
+ ;;
106
+ openai)
107
+ model_ids=("gpt-4.1" "gpt-4.1-mini" "gpt-5-mini")
108
+ model_options=(
109
+ "gpt-4.1 - strong general purpose API model"
110
+ "gpt-4.1-mini - cheaper fast default"
111
+ "gpt-5-mini - compact next-gen option"
112
+ )
113
+ default_idx=1
114
+ ;;
115
+ anthropic)
116
+ model_ids=("claude-sonnet-4-20250514" "claude-3-5-haiku-latest")
117
+ model_options=(
118
+ "claude-sonnet-4-20250514 - balanced reasoning"
119
+ "claude-3-5-haiku-latest - fast and lower cost"
120
+ )
121
+ default_idx=0
122
+ ;;
123
+ openrouter)
124
+ model_ids=("openai/gpt-4.1-mini" "anthropic/claude-3.5-haiku" "google/gemini-2.0-flash-001")
125
+ model_options=(
126
+ "openai/gpt-4.1-mini - interoperable default"
127
+ "anthropic/claude-3.5-haiku - Anthropic via OpenRouter"
128
+ "google/gemini-2.0-flash-001 - fast multimodal route"
129
+ )
130
+ default_idx=0
131
+ ;;
132
+ google)
133
+ model_ids=("gemini-2.0-flash" "gemini-1.5-pro")
134
+ model_options=(
135
+ "gemini-2.0-flash - default fast API model"
136
+ "gemini-1.5-pro - higher reasoning quality"
137
+ )
138
+ default_idx=0
139
+ ;;
140
+ custom)
141
+ if [[ -n "${FLAG_MODEL:-}" ]]; then
142
+ SELECTED_MODEL_ID="${FLAG_MODEL}"
143
+ SELECTED_MODEL_NAME="${FLAG_MODEL}"
144
+ export SELECTED_MODEL_ID SELECTED_MODEL_NAME
145
+ log_success "Selected model: ${SELECTED_MODEL_ID}"
146
+ return 0
147
+ fi
148
+
149
+ local custom_model_default="custom-model"
150
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
151
+ if [[ -z "${FLAG_MODEL:-}" ]]; then
152
+ log_error "Custom provider requires --model when --defaults is used."
153
+ return 1
154
+ fi
155
+ fi
156
+
157
+ SELECTED_MODEL_ID=$(prompt_input "Enter custom model ID" "${custom_model_default}")
158
+ SELECTED_MODEL_NAME="${SELECTED_MODEL_ID}"
159
+ export SELECTED_MODEL_ID SELECTED_MODEL_NAME
160
+ log_success "Selected model: ${SELECTED_MODEL_ID}"
161
+ return 0
162
+ ;;
163
+ *)
164
+ model_ids=("qwen2.5:7b-instruct-q4_K_M")
165
+ model_options=("qwen2.5:7b-instruct-q4_K_M - safe default")
166
+ default_idx=0
167
+ ;;
168
+ esac
169
+
170
+ local choice_label
171
+ choice_label=$(prompt_choice "Choose default model" model_options "${default_idx}")
172
+
173
+ local idx=0
174
+ for idx in $(seq 0 $(( ${#model_options[@]} - 1 ))); do
175
+ if [[ "${model_options[${idx}]}" == "${choice_label}" ]]; then
176
+ SELECTED_MODEL_ID="${model_ids[${idx}]}"
177
+ SELECTED_MODEL_NAME="${model_ids[${idx}]}"
178
+ break
179
+ fi
180
+ done
181
+
182
+ if [[ -z "${SELECTED_MODEL_ID:-}" ]]; then
183
+ SELECTED_MODEL_ID="${model_ids[${default_idx}]}"
184
+ SELECTED_MODEL_NAME="${model_ids[${default_idx}]}"
185
+ fi
186
+
187
+ export SELECTED_MODEL_ID SELECTED_MODEL_NAME
188
+ log_success "Selected model: ${SELECTED_MODEL_ID}"
189
+ }
190
+
191
+ collect_provider_credentials_v2() {
192
+ API_BASE_URL=""
193
+ API_KEY=""
194
+ CUSTOM_PROVIDER_NAME=""
195
+ FALLBACK_PROVIDER=""
196
+ FALLBACK_MODEL_ID=""
197
+
198
+ case "${PRIMARY_PROVIDER}" in
199
+ openai)
200
+ API_KEY=$(prompt_secret "Enter OPENAI_API_KEY")
201
+ API_BASE_URL=$(prompt_input "OpenAI base URL" "${FLAG_BASE_URL:-https://api.openai.com/v1}")
202
+ ;;
203
+ anthropic)
204
+ API_KEY=$(prompt_secret "Enter ANTHROPIC_API_KEY")
205
+ API_BASE_URL=$(prompt_input "Anthropic base URL" "${FLAG_BASE_URL:-https://api.anthropic.com}")
206
+ ;;
207
+ openrouter)
208
+ API_KEY=$(prompt_secret "Enter OPENROUTER_API_KEY")
209
+ API_BASE_URL=$(prompt_input "OpenRouter base URL" "${FLAG_BASE_URL:-https://openrouter.ai/api/v1}")
210
+ ;;
211
+ google)
212
+ API_KEY=$(prompt_secret "Enter GOOGLE_API_KEY")
213
+ API_BASE_URL=$(prompt_input "Google AI Studio base URL" "${FLAG_BASE_URL:-https://generativelanguage.googleapis.com/v1beta/openai}")
214
+ ;;
215
+ custom)
216
+ CUSTOM_PROVIDER_NAME="${FLAG_PROVIDER_NAME:-}"
217
+ if [[ -z "${CUSTOM_PROVIDER_NAME}" ]]; then
218
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
219
+ log_error "Custom provider requires --provider-name when --defaults is used."
220
+ return 1
221
+ fi
222
+ CUSTOM_PROVIDER_NAME=$(prompt_input "Custom provider label" "Custom AI")
223
+ fi
224
+
225
+ if [[ -n "${FLAG_API_KEY:-}" ]]; then
226
+ API_KEY="${FLAG_API_KEY}"
227
+ else
228
+ API_KEY=$(prompt_secret "Enter custom provider API key")
229
+ fi
230
+
231
+ if [[ -z "${FLAG_BASE_URL:-}" ]]; then
232
+ if [[ "${CLAWSPARK_DEFAULTS}" == "true" ]]; then
233
+ log_error "Custom provider requires --base-url when --defaults is used."
234
+ return 1
235
+ fi
236
+ API_BASE_URL=$(prompt_input "Custom provider base URL" "https://your-provider.example.com/v1")
237
+ else
238
+ API_BASE_URL="${FLAG_BASE_URL}"
239
+ fi
240
+ ;;
241
+ ollama)
242
+ API_KEY="ollama"
243
+ API_BASE_URL=$(prompt_input "Ollama base URL" "${FLAG_BASE_URL:-http://127.0.0.1:11434/v1}")
244
+ ;;
245
+ esac
246
+
247
+ if [[ "${PRIMARY_PROVIDER}" != "ollama" && -n "${FLAG_API_KEY:-}" ]]; then
248
+ API_KEY="${FLAG_API_KEY}"
249
+ fi
250
+
251
+ if [[ "${PRIMARY_PROVIDER}" != "ollama" && -z "${API_KEY}" ]]; then
252
+ log_error "API key is required for provider ${PRIMARY_PROVIDER}."
253
+ return 1
254
+ fi
255
+
256
+ if [[ -z "${API_BASE_URL}" ]]; then
257
+ log_error "Base URL is required for provider ${PRIMARY_PROVIDER}."
258
+ return 1
259
+ fi
260
+
261
+ if [[ "${RUNTIME_MODE}" == "hybrid" ]]; then
262
+ if [[ "${PRIMARY_PROVIDER}" == "ollama" ]]; then
263
+ FALLBACK_PROVIDER="openai"
264
+ FALLBACK_MODEL_ID="gpt-4.1-mini"
265
+ else
266
+ FALLBACK_PROVIDER="ollama"
267
+ FALLBACK_MODEL_ID="qwen2.5:7b-instruct-q4_K_M"
268
+ fi
269
+ fi
270
+
271
+ export API_BASE_URL API_KEY CUSTOM_PROVIDER_NAME FALLBACK_PROVIDER FALLBACK_MODEL_ID
272
+ log_info "Credential collection finished for ${PRIMARY_PROVIDER}"
273
+ }