wednesdayai 0.5.1-dev.24 → 0.5.1-dev.27

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 (193) hide show
  1. package/dist/{agent-turn-runner-CEjdyv-R.js → agent-turn-runner-CX7rX8by.js} +4 -4
  2. package/dist/{audio-preflight-C4_SRws-.js → audio-preflight-Qwb3oAbc.js} +5 -5
  3. package/dist/{audio-transcription-runner-Ct0kBrps.js → audio-transcription-runner-C4eHWIy0.js} +1 -1
  4. package/dist/build-info.json +3 -3
  5. package/dist/bundled/boot-md/handler.js +12 -12
  6. package/dist/bundled/session-journal/handler.js +12 -12
  7. package/dist/bundled/session-memory/handler.js +12 -12
  8. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  9. package/dist/{chat-content-1fhcKTem.js → chat-content-CX7Bfpgy.js} +3 -3
  10. package/dist/{chrome-DIGxu4IF.js → chrome-Bukcguhv.js} +8 -8
  11. package/dist/{command-queue-BIc3C-hI.js → command-queue-DOzYI9q4.js} +1 -1
  12. package/dist/{deliver-DrYdFhBN.js → deliver-BaTG7V5x.js} +1 -1
  13. package/dist/{diagnostic-N82VS3Qp.js → diagnostic-DMrDenZI.js} +1 -1
  14. package/dist/llm-slug-generator.js +12 -12
  15. package/dist/{pi-embedded-C1EQ2NQD.js → pi-embedded-DpTacVAX.js} +28 -28
  16. package/dist/{pi-embedded-utils-j-G6as7X.js → pi-embedded-utils-BAaHvb5G.js} +1 -1
  17. package/dist/plugin-sdk/{agent-scope-BNKfH4PO.js → agent-scope-B5NskkwN.js} +2 -2
  18. package/dist/plugin-sdk/agent-turn-execute-PXSa6POm.js +64 -0
  19. package/dist/plugin-sdk/agent-turn-runner-Cwg_qJSY.js +11 -0
  20. package/dist/plugin-sdk/{agent-turn-runner-D9SfXrDG.js → agent-turn-runner-DTwcFLVR.js} +7 -7
  21. package/dist/plugin-sdk/{api-key-rotation-BoeGT5k6.js → api-key-rotation-Dd5GYq7W.js} +2 -2
  22. package/dist/plugin-sdk/{audio-preflight-DpVKYTBy.js → audio-preflight-BTA2w6a8.js} +2 -2
  23. package/dist/plugin-sdk/audio-preflight-OHqqRPPw.js +31 -0
  24. package/dist/plugin-sdk/{audio-transcription-runner-BhFy0i3B.js → audio-transcription-runner-CW4PtJOr.js} +11 -11
  25. package/dist/plugin-sdk/{auth-profiles-DhZNx6fQ.js → auth-profiles-CxOZbtpA.js} +4 -4
  26. package/dist/plugin-sdk/{channel-activity-BlGuAYdc.js → channel-activity-WL5SZ4Ub.js} +3 -3
  27. package/dist/plugin-sdk/{chat-content-BnSpex4r.js → chat-content-Csto0UEZ.js} +13 -13
  28. package/dist/plugin-sdk/{chrome-CBH4Uiy-.js → chrome-DzzIT4YD.js} +13 -13
  29. package/dist/plugin-sdk/{chunk-DKwQiXuF.js → chunk-CWDKrXV-.js} +3 -3
  30. package/dist/plugin-sdk/{chunk-jn4LDLDQ.js → chunk-C_GT4tj0.js} +5 -5
  31. package/dist/plugin-sdk/{command-detection--t4OkqFt.js → command-detection-YLMxiTkq.js} +95 -95
  32. package/dist/plugin-sdk/{command-queue-Dd0-b1-g.js → command-queue-84-UYysZ.js} +1 -1
  33. package/dist/plugin-sdk/{command-queue-DfIjyemM.js → command-queue-BlcsvUrS.js} +7 -7
  34. package/dist/plugin-sdk/{commands-registry-COgT-iaa.js → commands-registry-Bq1VWa2x.js} +2 -2
  35. package/dist/plugin-sdk/{commands-registry-C6FHOrPa.js → commands-registry-oILscT9a.js} +5 -5
  36. package/dist/plugin-sdk/{config-D2katwYr.js → config-Cp7sIoSP.js} +6 -6
  37. package/dist/plugin-sdk/{deliver-CeWz5Axp.js → deliver-1ptGm5gu.js} +14 -14
  38. package/dist/plugin-sdk/deliver-vvTRXMdO.js +35 -0
  39. package/dist/plugin-sdk/{diagnostic-ZVJ6ZVA6.js → diagnostic-BCs9q4kf.js} +6 -6
  40. package/dist/plugin-sdk/{diagnostic-DD94QWnh.js → diagnostic-W-ES89_S.js} +5 -5
  41. package/dist/plugin-sdk/{errors-B671D37F.js → errors-BAcXagTa.js} +1 -1
  42. package/dist/plugin-sdk/{fetch-guard-DWRMXy-Z.js → fetch-guard-5esEIw3C.js} +3 -3
  43. package/dist/plugin-sdk/{fs-safe-CR39I5to.js → fs-safe-Qut4VUn8.js} +3 -3
  44. package/dist/plugin-sdk/{image-ops-C0wvHJkm.js → image-ops-B9MP8BwT.js} +1 -1
  45. package/dist/plugin-sdk/index.js +56 -56
  46. package/dist/plugin-sdk/{ir-CRNn4Wzv.js → ir-D6Bn5lOZ.js} +5 -5
  47. package/dist/plugin-sdk/{local-roots-D2LPLxOo.js → local-roots-1beRdBcS.js} +7 -7
  48. package/dist/plugin-sdk/{logger-F5PKgK4h.js → logger-DYxRK_FZ.js} +1 -1
  49. package/dist/plugin-sdk/manager-BgK1sRAg.js +16 -0
  50. package/dist/plugin-sdk/{manager-WrO0qTfU.js → manager-Dxv2YpgS.js} +7 -7
  51. package/dist/plugin-sdk/{markdown-tables-DunxiRpT.js → markdown-tables-5CSf45sS.js} +2 -2
  52. package/dist/plugin-sdk/markdown-tables-Q5ogPPem.js +9 -0
  53. package/dist/plugin-sdk/{models-config-DJUNPvmU.js → models-config-Dj4jLPMw.js} +2 -2
  54. package/dist/plugin-sdk/{outbound-attachment-Bw8Ac2Eh.js → outbound-attachment-DiQNpfYY.js} +2 -2
  55. package/dist/plugin-sdk/{path-alias-guards-DvB3vS9N.js → path-alias-guards-Cr_8wxeu.js} +1 -1
  56. package/dist/plugin-sdk/{pi-embedded-utils-DCbfW5pR.js → pi-embedded-utils-Bx8SkKxs.js} +3 -3
  57. package/dist/plugin-sdk/{pi-model-discovery-_OwU2G2N.js → pi-model-discovery-CHCoH08E.js} +1 -1
  58. package/dist/plugin-sdk/{pi-model-discovery-Bt6iAhJo.js → pi-model-discovery-mXT8DqbW.js} +5 -5
  59. package/dist/plugin-sdk/provider-dispatcher-Bg7ik_9P.js +64 -0
  60. package/dist/plugin-sdk/{provider-image-Cp4O6erC.js → provider-image-M3j6w2Xu.js} +6 -6
  61. package/dist/plugin-sdk/provider-image-fv7acwPs.js +25 -0
  62. package/dist/plugin-sdk/{proxy-DhVCl72V.js → proxy-K3jUjtIm.js} +1 -1
  63. package/dist/plugin-sdk/proxy-LKdqVD6u.js +7 -0
  64. package/dist/plugin-sdk/{proxy-fetch-BSTGvynk.js → proxy-fetch-Dt3HV6r3.js} +1 -1
  65. package/dist/plugin-sdk/{pw-ai-BAIve7qq.js → pw-ai-CsU7HGvR.js} +10 -10
  66. package/dist/plugin-sdk/{qmd-manager-Dk95qYn7.js → qmd-manager-BHhDDFrn.js} +8 -8
  67. package/dist/plugin-sdk/{query-expansion-CmmZw3Kz.js → query-expansion-BY2L8oSD.js} +2 -2
  68. package/dist/plugin-sdk/{recorder-CSYSJRCO.js → recorder-B_7zTSFA.js} +2 -2
  69. package/dist/plugin-sdk/{redact-YbcPIGWf.js → redact-C9Eu_AUI.js} +1 -1
  70. package/dist/plugin-sdk/{replies-CXs4_abR.js → replies-B3XUIs0b.js} +5 -5
  71. package/dist/plugin-sdk/replies-C4T9Nsqz.js +27 -0
  72. package/dist/plugin-sdk/{reply-directives-DjbLtWjg.js → reply-directives-D1IqpiHJ.js} +2 -2
  73. package/dist/plugin-sdk/{reply-prefix-DA4xCsnL.js → reply-prefix-Bf8Iqq8v.js} +1 -1
  74. package/dist/plugin-sdk/reply-prefix-C38vv65Y.js +7 -0
  75. package/dist/plugin-sdk/{resolve-route-Ouh7ZVw1.js → resolve-route-D0I4Llwy.js} +4 -4
  76. package/dist/plugin-sdk/{resolve-route-BDoLW8xr.js → resolve-route-JQF7nqo1.js} +5 -5
  77. package/dist/plugin-sdk/{runs-s1XnmHHC.js → runs-C75mB4si.js} +8 -8
  78. package/dist/plugin-sdk/runs-S1n4U4AS.js +27 -0
  79. package/dist/plugin-sdk/send--m_QWAPO.js +23 -0
  80. package/dist/plugin-sdk/{send-sl6DLsL2.js → send-4vK7xjJW.js} +15 -15
  81. package/dist/plugin-sdk/{send-YzrHr_2V.js → send-7iASSBg6.js} +6 -6
  82. package/dist/plugin-sdk/send-C4wz_kt5.js +28 -0
  83. package/dist/plugin-sdk/{send-DlYcVutL.js → send-CRZe2naD.js} +8 -8
  84. package/dist/plugin-sdk/{send-Dzf8uETw.js → send-CzU3DnG8.js} +7 -7
  85. package/dist/plugin-sdk/send-D9H-XXzx.js +25 -0
  86. package/dist/plugin-sdk/{send-C6oFuvTF.js → send-DHH7532s.js} +6 -6
  87. package/dist/plugin-sdk/{send-BRN7Zt5N.js → send-Dhapzixp.js} +19 -19
  88. package/dist/plugin-sdk/send-DnFCjogv.js +24 -0
  89. package/dist/plugin-sdk/{session-context-BKcnHr0H.js → session-context-DVrSm2pD.js} +4 -4
  90. package/dist/plugin-sdk/session-meta-CT3hQSl7.js +13 -0
  91. package/dist/plugin-sdk/{session-meta-Ov3Sv55D.js → session-meta-Cho9XVh8.js} +1 -1
  92. package/dist/plugin-sdk/{sessions-CLYE50YG.js → sessions-D5KTt-gq.js} +4 -4
  93. package/dist/plugin-sdk/{skill-commands-B3IuCuE6.js → skill-commands-Bv0tfv2Q.js} +4 -4
  94. package/dist/plugin-sdk/skill-commands-Clq-qaMe.js +13 -0
  95. package/dist/plugin-sdk/{skills-DcowK80n.js → skills-DO7OTK36.js} +6 -6
  96. package/dist/plugin-sdk/{ssrf-CkGpmmN1.js → ssrf-DPY1teOB.js} +1 -1
  97. package/dist/plugin-sdk/{store-B5Jssa25.js → store-nKIf4x6Y.js} +2 -2
  98. package/dist/plugin-sdk/subagent-registry-DSh4cze1.js +64 -0
  99. package/dist/plugin-sdk/{subsystem-C-lWCqvG.js → subsystem-CPqF9wja.js} +4 -4
  100. package/dist/plugin-sdk/{tables-CG00Lb4u.js → tables-CatynEzT.js} +1 -1
  101. package/dist/plugin-sdk/{tokens-Ctmmv-sn.js → tokens-DdbQh5HH.js} +1 -1
  102. package/dist/plugin-sdk/{tool-loop-detection-ys8oxWQM.js → tool-loop-detection-DazBiexb.js} +1 -1
  103. package/dist/plugin-sdk/{user-run-recording-CTKs_Jv9.js → user-run-recording-BXyHfYg3.js} +2 -2
  104. package/dist/plugin-sdk/user-run-recording-D80GJNVP.js +11 -0
  105. package/dist/{provider-image-CFq0C_z0.js → provider-image-BSkZoSBN.js} +1 -1
  106. package/dist/{pw-ai-Dhm8_p5G.js → pw-ai-CNTt9O_x.js} +1 -1
  107. package/dist/{runs-Ot6reoKK.js → runs-6Q7RU60m.js} +3 -3
  108. package/dist/{send-BXHsjIHr.js → send-VDe5tGbO.js} +1 -1
  109. package/extensions/acpx/package.json +1 -1
  110. package/extensions/anthropic/package.json +1 -1
  111. package/extensions/bifrost/package.json +1 -1
  112. package/extensions/bluebubbles/package.json +1 -1
  113. package/extensions/copilot-proxy/package.json +1 -1
  114. package/extensions/deepgram/package.json +1 -1
  115. package/extensions/deepinfra/package.json +1 -1
  116. package/extensions/diagnostics-otel/package.json +1 -1
  117. package/extensions/diffs/package.json +1 -1
  118. package/extensions/discord/package.json +1 -1
  119. package/extensions/dream-cycle/package.json +1 -1
  120. package/extensions/elevenlabs/package.json +1 -1
  121. package/extensions/feishu/package.json +1 -1
  122. package/extensions/gemini-search/package.json +1 -1
  123. package/extensions/google/package.json +1 -1
  124. package/extensions/google-gemini-cli-auth/package.json +1 -1
  125. package/extensions/googlechat/package.json +1 -1
  126. package/extensions/grok-search/package.json +1 -1
  127. package/extensions/groq/package.json +1 -1
  128. package/extensions/imessage/package.json +1 -1
  129. package/extensions/irc/package.json +1 -1
  130. package/extensions/kimi-search/package.json +1 -1
  131. package/extensions/learning-core/package.json +1 -1
  132. package/extensions/line/package.json +1 -1
  133. package/extensions/llm-task/package.json +1 -1
  134. package/extensions/lobster/package.json +1 -1
  135. package/extensions/matrix/CHANGELOG.md +1 -1
  136. package/extensions/matrix/package.json +1 -1
  137. package/extensions/mattermost/package.json +1 -1
  138. package/extensions/memory-core/package.json +1 -1
  139. package/extensions/memory-lancedb/package.json +1 -1
  140. package/extensions/minimax/package.json +1 -1
  141. package/extensions/minimax-portal-auth/package.json +1 -1
  142. package/extensions/mistral/package.json +1 -1
  143. package/extensions/moonshot/package.json +1 -1
  144. package/extensions/msteams/CHANGELOG.md +1 -1
  145. package/extensions/msteams/package.json +1 -1
  146. package/extensions/nextcloud-talk/package.json +1 -1
  147. package/extensions/nostr/CHANGELOG.md +1 -1
  148. package/extensions/nostr/package.json +1 -1
  149. package/extensions/open-prose/package.json +1 -1
  150. package/extensions/openai/package.json +1 -1
  151. package/extensions/openai-realtime/package.json +1 -1
  152. package/extensions/openrouter/package.json +1 -1
  153. package/extensions/perplexity-search/package.json +1 -1
  154. package/extensions/searxng-search/package.json +1 -1
  155. package/extensions/signal/package.json +1 -1
  156. package/extensions/slack/package.json +1 -1
  157. package/extensions/synology-chat/package.json +1 -1
  158. package/extensions/telegram/package.json +1 -1
  159. package/extensions/tlon/package.json +1 -1
  160. package/extensions/twitch/CHANGELOG.md +1 -1
  161. package/extensions/twitch/package.json +1 -1
  162. package/extensions/ui-surfaces/package.json +1 -1
  163. package/extensions/voice-call/CHANGELOG.md +1 -1
  164. package/extensions/voice-call/package.json +1 -1
  165. package/extensions/whatsapp/package.json +1 -1
  166. package/extensions/zai/package.json +1 -1
  167. package/extensions/zalo/CHANGELOG.md +1 -1
  168. package/extensions/zalo/package.json +1 -1
  169. package/extensions/zalouser/CHANGELOG.md +1 -1
  170. package/extensions/zalouser/package.json +1 -1
  171. package/package.json +6 -3
  172. package/scripts/install.ps1 +329 -0
  173. package/scripts/install.sh +2479 -0
  174. package/dist/plugin-sdk/agent-turn-execute-W6H2j8kO.js +0 -64
  175. package/dist/plugin-sdk/agent-turn-runner-BTedsvG2.js +0 -11
  176. package/dist/plugin-sdk/audio-preflight-CJ3oP-bs.js +0 -31
  177. package/dist/plugin-sdk/deliver-DaxxC7pS.js +0 -35
  178. package/dist/plugin-sdk/manager-DsV_4p9J.js +0 -16
  179. package/dist/plugin-sdk/markdown-tables-xTvb6uxj.js +0 -9
  180. package/dist/plugin-sdk/provider-dispatcher-BVBAjHBR.js +0 -64
  181. package/dist/plugin-sdk/provider-image-Wa7xYnAo.js +0 -25
  182. package/dist/plugin-sdk/proxy-BFnRHQ1U.js +0 -7
  183. package/dist/plugin-sdk/replies-RqNUByIO.js +0 -27
  184. package/dist/plugin-sdk/reply-prefix-30s91NpV.js +0 -7
  185. package/dist/plugin-sdk/runs-XrgntIht.js +0 -27
  186. package/dist/plugin-sdk/send-8sjelrtn.js +0 -25
  187. package/dist/plugin-sdk/send-BjroMkud.js +0 -24
  188. package/dist/plugin-sdk/send-CwcdQ1wT.js +0 -28
  189. package/dist/plugin-sdk/send-DuyWQ4Q4.js +0 -23
  190. package/dist/plugin-sdk/session-meta-JkAHgftu.js +0 -13
  191. package/dist/plugin-sdk/skill-commands-CZKr2EYZ.js +0 -13
  192. package/dist/plugin-sdk/subagent-registry-28cuPTO-.js +0 -64
  193. package/dist/plugin-sdk/user-run-recording-DIcSd0VJ.js +0 -11
@@ -0,0 +1,2479 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # OpenClaw Installer for macOS and Linux
5
+ # Usage: curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash
6
+
7
+ BOLD='\033[1m'
8
+ ACCENT='\033[38;2;255;77;77m' # coral-bright #ff4d4d
9
+ # shellcheck disable=SC2034
10
+ ACCENT_BRIGHT='\033[38;2;255;110;110m' # lighter coral
11
+ INFO='\033[38;2;136;146;176m' # text-secondary #8892b0
12
+ SUCCESS='\033[38;2;0;229;204m' # cyan-bright #00e5cc
13
+ WARN='\033[38;2;255;176;32m' # amber (no site equiv, keep warm)
14
+ ERROR='\033[38;2;230;57;70m' # coral-mid #e63946
15
+ MUTED='\033[38;2;90;100;128m' # text-muted #5a6480
16
+ NC='\033[0m' # No Color
17
+
18
+ DEFAULT_TAGLINE="All your chats, one OpenClaw."
19
+ NODE_MIN_MAJOR=24
20
+ NODE_MIN_MINOR=0
21
+ NODE_MIN_VERSION="${NODE_MIN_MAJOR}.${NODE_MIN_MINOR}"
22
+
23
+ ORIGINAL_PATH="${PATH:-}"
24
+
25
+ TMPFILES=()
26
+ cleanup_tmpfiles() {
27
+ local f
28
+ for f in "${TMPFILES[@]:-}"; do
29
+ rm -rf "$f" 2>/dev/null || true
30
+ done
31
+ }
32
+ trap cleanup_tmpfiles EXIT
33
+
34
+ mktempfile() {
35
+ local f
36
+ f="$(mktemp)"
37
+ TMPFILES+=("$f")
38
+ echo "$f"
39
+ }
40
+
41
+ DOWNLOADER=""
42
+ detect_downloader() {
43
+ if command -v curl &> /dev/null; then
44
+ DOWNLOADER="curl"
45
+ return 0
46
+ fi
47
+ if command -v wget &> /dev/null; then
48
+ DOWNLOADER="wget"
49
+ return 0
50
+ fi
51
+ ui_error "Missing downloader (curl or wget required)"
52
+ exit 1
53
+ }
54
+
55
+ download_file() {
56
+ local url="$1"
57
+ local output="$2"
58
+ if [[ -z "$DOWNLOADER" ]]; then
59
+ detect_downloader
60
+ fi
61
+ if [[ "$DOWNLOADER" == "curl" ]]; then
62
+ curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 --retry-connrefused -o "$output" "$url"
63
+ return
64
+ fi
65
+ wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=20 -O "$output" "$url"
66
+ }
67
+
68
+ run_remote_bash() {
69
+ local url="$1"
70
+ local tmp
71
+ tmp="$(mktempfile)"
72
+ download_file "$url" "$tmp"
73
+ /bin/bash "$tmp"
74
+ }
75
+
76
+ GUM_VERSION="${OPENCLAW_GUM_VERSION:-0.17.0}"
77
+ GUM=""
78
+ GUM_STATUS="skipped"
79
+ GUM_REASON=""
80
+ LAST_NPM_INSTALL_CMD=""
81
+
82
+ is_non_interactive_shell() {
83
+ if [[ "${NO_PROMPT:-0}" == "1" ]]; then
84
+ return 0
85
+ fi
86
+ if [[ ! -t 0 || ! -t 1 ]]; then
87
+ return 0
88
+ fi
89
+ return 1
90
+ }
91
+
92
+ gum_is_tty() {
93
+ if [[ -n "${NO_COLOR:-}" ]]; then
94
+ return 1
95
+ fi
96
+ if [[ "${TERM:-dumb}" == "dumb" ]]; then
97
+ return 1
98
+ fi
99
+ if [[ -t 2 || -t 1 ]]; then
100
+ return 0
101
+ fi
102
+ if [[ -r /dev/tty && -w /dev/tty ]]; then
103
+ return 0
104
+ fi
105
+ return 1
106
+ }
107
+
108
+ gum_detect_os() {
109
+ case "$(uname -s 2>/dev/null || true)" in
110
+ Darwin) echo "Darwin" ;;
111
+ Linux) echo "Linux" ;;
112
+ *) echo "unsupported" ;;
113
+ esac
114
+ }
115
+
116
+ gum_detect_arch() {
117
+ case "$(uname -m 2>/dev/null || true)" in
118
+ x86_64|amd64) echo "x86_64" ;;
119
+ arm64|aarch64) echo "arm64" ;;
120
+ i386|i686) echo "i386" ;;
121
+ armv7l|armv7) echo "armv7" ;;
122
+ armv6l|armv6) echo "armv6" ;;
123
+ *) echo "unknown" ;;
124
+ esac
125
+ }
126
+
127
+ verify_sha256sum_file() {
128
+ local checksums="$1"
129
+ if command -v sha256sum >/dev/null 2>&1; then
130
+ sha256sum --ignore-missing -c "$checksums" >/dev/null 2>&1
131
+ return $?
132
+ fi
133
+ if command -v shasum >/dev/null 2>&1; then
134
+ shasum -a 256 --ignore-missing -c "$checksums" >/dev/null 2>&1
135
+ return $?
136
+ fi
137
+ return 1
138
+ }
139
+
140
+ bootstrap_gum_temp() {
141
+ GUM=""
142
+ GUM_STATUS="skipped"
143
+ GUM_REASON=""
144
+
145
+ if is_non_interactive_shell; then
146
+ GUM_REASON="non-interactive shell (auto-disabled)"
147
+ return 1
148
+ fi
149
+
150
+ if ! gum_is_tty; then
151
+ GUM_REASON="terminal does not support gum UI"
152
+ return 1
153
+ fi
154
+
155
+ if command -v gum >/dev/null 2>&1; then
156
+ GUM="gum"
157
+ GUM_STATUS="found"
158
+ GUM_REASON="already installed"
159
+ return 0
160
+ fi
161
+
162
+ if ! command -v tar >/dev/null 2>&1; then
163
+ GUM_REASON="tar not found"
164
+ return 1
165
+ fi
166
+
167
+ local os arch asset base gum_tmpdir gum_path
168
+ os="$(gum_detect_os)"
169
+ arch="$(gum_detect_arch)"
170
+ if [[ "$os" == "unsupported" || "$arch" == "unknown" ]]; then
171
+ GUM_REASON="unsupported os/arch ($os/$arch)"
172
+ return 1
173
+ fi
174
+
175
+ asset="gum_${GUM_VERSION}_${os}_${arch}.tar.gz"
176
+ base="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}"
177
+
178
+ gum_tmpdir="$(mktemp -d)"
179
+ TMPFILES+=("$gum_tmpdir")
180
+
181
+ if ! download_file "${base}/${asset}" "$gum_tmpdir/$asset"; then
182
+ GUM_REASON="download failed"
183
+ return 1
184
+ fi
185
+
186
+ if ! download_file "${base}/checksums.txt" "$gum_tmpdir/checksums.txt"; then
187
+ GUM_REASON="checksum unavailable or failed"
188
+ return 1
189
+ fi
190
+
191
+ if ! (cd "$gum_tmpdir" && verify_sha256sum_file "checksums.txt"); then
192
+ GUM_REASON="checksum unavailable or failed"
193
+ return 1
194
+ fi
195
+
196
+ if ! tar -xzf "$gum_tmpdir/$asset" -C "$gum_tmpdir" >/dev/null 2>&1; then
197
+ GUM_REASON="extract failed"
198
+ return 1
199
+ fi
200
+
201
+ gum_path="$(find "$gum_tmpdir" -type f -name gum 2>/dev/null | head -n1 || true)"
202
+ if [[ -z "$gum_path" ]]; then
203
+ GUM_REASON="gum binary missing after extract"
204
+ return 1
205
+ fi
206
+
207
+ chmod +x "$gum_path" >/dev/null 2>&1 || true
208
+ if [[ ! -x "$gum_path" ]]; then
209
+ GUM_REASON="gum binary is not executable"
210
+ return 1
211
+ fi
212
+
213
+ GUM="$gum_path"
214
+ GUM_STATUS="installed"
215
+ GUM_REASON="temp, verified"
216
+ return 0
217
+ }
218
+
219
+ print_gum_status() {
220
+ case "$GUM_STATUS" in
221
+ found)
222
+ ui_success "gum available (${GUM_REASON})"
223
+ ;;
224
+ installed)
225
+ ui_success "gum bootstrapped (${GUM_REASON}, v${GUM_VERSION})"
226
+ ;;
227
+ *)
228
+ if [[ -n "$GUM_REASON" && "$GUM_REASON" != "non-interactive shell (auto-disabled)" ]]; then
229
+ ui_info "gum skipped (${GUM_REASON})"
230
+ fi
231
+ ;;
232
+ esac
233
+ }
234
+
235
+ print_installer_banner() {
236
+ if [[ -n "$GUM" ]]; then
237
+ local title tagline hint card
238
+ title="$("$GUM" style --foreground "#ff4d4d" --bold "🦞 OpenClaw Installer")"
239
+ tagline="$("$GUM" style --foreground "#8892b0" "$TAGLINE")"
240
+ hint="$("$GUM" style --foreground "#5a6480" "modern installer mode")"
241
+ card="$(printf '%s\n%s\n%s' "$title" "$tagline" "$hint")"
242
+ "$GUM" style --border rounded --border-foreground "#ff4d4d" --padding "1 2" "$card"
243
+ echo ""
244
+ return
245
+ fi
246
+
247
+ echo -e "${ACCENT}${BOLD}"
248
+ echo " 🦞 OpenClaw Installer"
249
+ echo -e "${NC}${INFO} ${TAGLINE}${NC}"
250
+ echo ""
251
+ }
252
+
253
+ detect_os_or_die() {
254
+ OS="unknown"
255
+ if [[ "$OSTYPE" == "darwin"* ]]; then
256
+ OS="macos"
257
+ elif [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
258
+ OS="linux"
259
+ fi
260
+
261
+ if [[ "$OS" == "unknown" ]]; then
262
+ ui_error "Unsupported operating system"
263
+ echo "This installer supports macOS and Linux (including WSL)."
264
+ echo "For Windows, use: iwr -useb https://wednesdayai.dev/install.ps1 | iex"
265
+ exit 1
266
+ fi
267
+
268
+ ui_success "Detected: $OS"
269
+ }
270
+
271
+ ui_info() {
272
+ local msg="$*"
273
+ if [[ -n "$GUM" ]]; then
274
+ "$GUM" log --level info "$msg"
275
+ else
276
+ echo -e "${MUTED}·${NC} ${msg}"
277
+ fi
278
+ }
279
+
280
+ ui_warn() {
281
+ local msg="$*"
282
+ if [[ -n "$GUM" ]]; then
283
+ "$GUM" log --level warn "$msg"
284
+ else
285
+ echo -e "${WARN}!${NC} ${msg}"
286
+ fi
287
+ }
288
+
289
+ ui_success() {
290
+ local msg="$*"
291
+ if [[ -n "$GUM" ]]; then
292
+ local mark
293
+ mark="$("$GUM" style --foreground "#00e5cc" --bold "✓")"
294
+ echo "${mark} ${msg}"
295
+ else
296
+ echo -e "${SUCCESS}✓${NC} ${msg}"
297
+ fi
298
+ }
299
+
300
+ ui_error() {
301
+ local msg="$*"
302
+ if [[ -n "$GUM" ]]; then
303
+ "$GUM" log --level error "$msg"
304
+ else
305
+ echo -e "${ERROR}✗${NC} ${msg}"
306
+ fi
307
+ }
308
+
309
+ INSTALL_STAGE_TOTAL=3
310
+ INSTALL_STAGE_CURRENT=0
311
+
312
+ ui_section() {
313
+ local title="$1"
314
+ if [[ -n "$GUM" ]]; then
315
+ "$GUM" style --bold --foreground "#ff4d4d" --padding "1 0" "$title"
316
+ else
317
+ echo ""
318
+ echo -e "${ACCENT}${BOLD}${title}${NC}"
319
+ fi
320
+ }
321
+
322
+ ui_stage() {
323
+ local title="$1"
324
+ INSTALL_STAGE_CURRENT=$((INSTALL_STAGE_CURRENT + 1))
325
+ ui_section "[${INSTALL_STAGE_CURRENT}/${INSTALL_STAGE_TOTAL}] ${title}"
326
+ }
327
+
328
+ ui_kv() {
329
+ local key="$1"
330
+ local value="$2"
331
+ if [[ -n "$GUM" ]]; then
332
+ local key_part value_part
333
+ key_part="$("$GUM" style --foreground "#5a6480" --width 20 "$key")"
334
+ value_part="$("$GUM" style --bold "$value")"
335
+ "$GUM" join --horizontal "$key_part" "$value_part"
336
+ else
337
+ echo -e "${MUTED}${key}:${NC} ${value}"
338
+ fi
339
+ }
340
+
341
+ ui_panel() {
342
+ local content="$1"
343
+ if [[ -n "$GUM" ]]; then
344
+ "$GUM" style --border rounded --border-foreground "#5a6480" --padding "0 1" "$content"
345
+ else
346
+ echo "$content"
347
+ fi
348
+ }
349
+
350
+ show_install_plan() {
351
+ local detected_checkout="$1"
352
+
353
+ ui_section "Install plan"
354
+ ui_kv "OS" "$OS"
355
+ ui_kv "Install method" "$INSTALL_METHOD"
356
+ ui_kv "Requested version" "$OPENCLAW_VERSION"
357
+ if [[ "$USE_BETA" == "1" ]]; then
358
+ ui_kv "Beta channel" "enabled"
359
+ fi
360
+ if [[ "$INSTALL_METHOD" == "git" ]]; then
361
+ ui_kv "Git directory" "$GIT_DIR"
362
+ ui_kv "Git update" "$GIT_UPDATE"
363
+ fi
364
+ if [[ -n "$detected_checkout" ]]; then
365
+ ui_kv "Detected checkout" "$detected_checkout"
366
+ fi
367
+ if [[ "$DRY_RUN" == "1" ]]; then
368
+ ui_kv "Dry run" "yes"
369
+ fi
370
+ if [[ "$NO_ONBOARD" == "1" ]]; then
371
+ ui_kv "Onboarding" "skipped"
372
+ fi
373
+ }
374
+
375
+ show_footer_links() {
376
+ local faq_url="https://docs.wednesdayai.dev/users/help/faq"
377
+ if [[ -n "$GUM" ]]; then
378
+ local content
379
+ content="$(printf '%s\n%s' "Need help?" "FAQ: ${faq_url}")"
380
+ ui_panel "$content"
381
+ else
382
+ echo ""
383
+ echo -e "FAQ: ${INFO}${faq_url}${NC}"
384
+ fi
385
+ }
386
+
387
+ ui_celebrate() {
388
+ local msg="$1"
389
+ if [[ -n "$GUM" ]]; then
390
+ "$GUM" style --bold --foreground "#00e5cc" "$msg"
391
+ else
392
+ echo -e "${SUCCESS}${BOLD}${msg}${NC}"
393
+ fi
394
+ }
395
+
396
+ is_shell_function() {
397
+ local name="${1:-}"
398
+ [[ -n "$name" ]] && declare -F "$name" >/dev/null 2>&1
399
+ }
400
+
401
+ is_gum_raw_mode_failure() {
402
+ local err_log="$1"
403
+ [[ -s "$err_log" ]] || return 1
404
+ grep -Eiq 'setrawmode' "$err_log"
405
+ }
406
+
407
+ run_with_spinner() {
408
+ local title="$1"
409
+ shift
410
+
411
+ if [[ -n "$GUM" ]] && gum_is_tty && ! is_shell_function "${1:-}"; then
412
+ local gum_err
413
+ gum_err="$(mktempfile)"
414
+ if "$GUM" spin --spinner dot --title "$title" -- "$@" 2>"$gum_err"; then
415
+ return 0
416
+ fi
417
+ local gum_status=$?
418
+ if is_gum_raw_mode_failure "$gum_err"; then
419
+ GUM=""
420
+ GUM_STATUS="skipped"
421
+ GUM_REASON="gum raw mode unavailable"
422
+ ui_warn "Spinner unavailable in this terminal; continuing without spinner"
423
+ "$@"
424
+ return $?
425
+ fi
426
+ if [[ -s "$gum_err" ]]; then
427
+ cat "$gum_err" >&2
428
+ fi
429
+ return "$gum_status"
430
+ fi
431
+
432
+ "$@"
433
+ }
434
+
435
+ run_quiet_step() {
436
+ local title="$1"
437
+ shift
438
+
439
+ if [[ "$VERBOSE" == "1" ]]; then
440
+ run_with_spinner "$title" "$@"
441
+ return $?
442
+ fi
443
+
444
+ local log
445
+ log="$(mktempfile)"
446
+
447
+ if [[ -n "$GUM" ]] && gum_is_tty && ! is_shell_function "${1:-}"; then
448
+ local cmd_quoted=""
449
+ local log_quoted=""
450
+ printf -v cmd_quoted '%q ' "$@"
451
+ printf -v log_quoted '%q' "$log"
452
+ if run_with_spinner "$title" bash -c "${cmd_quoted}>${log_quoted} 2>&1"; then
453
+ return 0
454
+ fi
455
+ else
456
+ if "$@" >"$log" 2>&1; then
457
+ return 0
458
+ fi
459
+ fi
460
+
461
+ ui_error "${title} failed — re-run with --verbose for details"
462
+ if [[ -s "$log" ]]; then
463
+ tail -n 80 "$log" >&2 || true
464
+ fi
465
+ return 1
466
+ }
467
+
468
+ cleanup_legacy_submodules() {
469
+ local repo_dir="$1"
470
+ local legacy_dir="$repo_dir/Peekaboo"
471
+ if [[ -d "$legacy_dir" ]]; then
472
+ ui_info "Removing legacy submodule checkout: ${legacy_dir}"
473
+ rm -rf "$legacy_dir"
474
+ fi
475
+ }
476
+
477
+ cleanup_npm_openclaw_paths() {
478
+ local npm_root=""
479
+ npm_root="$(npm root -g 2>/dev/null || true)"
480
+ if [[ -z "$npm_root" || "$npm_root" != *node_modules* ]]; then
481
+ return 1
482
+ fi
483
+ rm -rf "$npm_root"/.openclaw-* "$npm_root"/openclaw 2>/dev/null || true
484
+ }
485
+
486
+ extract_openclaw_conflict_path() {
487
+ local log="$1"
488
+ local path=""
489
+ path="$(sed -n 's/.*File exists: //p' "$log" | head -n1)"
490
+ if [[ -z "$path" ]]; then
491
+ path="$(sed -n 's/.*EEXIST: file already exists, //p' "$log" | head -n1)"
492
+ fi
493
+ if [[ -n "$path" ]]; then
494
+ echo "$path"
495
+ return 0
496
+ fi
497
+ return 1
498
+ }
499
+
500
+ cleanup_openclaw_bin_conflict() {
501
+ local bin_path="$1"
502
+ if [[ -z "$bin_path" || ( ! -e "$bin_path" && ! -L "$bin_path" ) ]]; then
503
+ return 1
504
+ fi
505
+ local npm_bin=""
506
+ npm_bin="$(npm_global_bin_dir 2>/dev/null || true)"
507
+ if [[ -n "$npm_bin" && "$bin_path" != "$npm_bin/openclaw" ]]; then
508
+ case "$bin_path" in
509
+ "/opt/homebrew/bin/openclaw"|"/usr/local/bin/openclaw")
510
+ ;;
511
+ *)
512
+ return 1
513
+ ;;
514
+ esac
515
+ fi
516
+ if [[ -L "$bin_path" ]]; then
517
+ local target=""
518
+ target="$(readlink "$bin_path" 2>/dev/null || true)"
519
+ if [[ "$target" == *"/node_modules/openclaw/"* ]]; then
520
+ rm -f "$bin_path"
521
+ ui_info "Removed stale openclaw symlink at ${bin_path}"
522
+ return 0
523
+ fi
524
+ return 1
525
+ fi
526
+ local backup=""
527
+ backup="${bin_path}.bak-$(date +%Y%m%d-%H%M%S)"
528
+ if mv "$bin_path" "$backup"; then
529
+ ui_info "Moved existing openclaw binary to ${backup}"
530
+ return 0
531
+ fi
532
+ return 1
533
+ }
534
+
535
+ npm_log_indicates_missing_build_tools() {
536
+ local log="$1"
537
+ if [[ -z "$log" || ! -f "$log" ]]; then
538
+ return 1
539
+ fi
540
+
541
+ grep -Eiq "(not found: make|make: command not found|cmake: command not found|CMAKE_MAKE_PROGRAM is not set|Could not find CMAKE|gyp ERR! find Python|no developer tools were found|is not able to compile a simple test program|Failed to build llama\\.cpp|It seems that \"make\" is not installed in your system|It seems that the used \"cmake\" doesn't work properly)" "$log"
542
+ }
543
+
544
+ # Detect Arch-based distributions (Arch Linux, Manjaro, EndeavourOS, etc.)
545
+ is_arch_linux() {
546
+ if [[ -f /etc/os-release ]]; then
547
+ local os_id
548
+ os_id="$(grep -E '^ID=' /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true)"
549
+ case "$os_id" in
550
+ arch|manjaro|endeavouros|arcolinux|garuda|archarm|cachyos|archcraft)
551
+ return 0
552
+ ;;
553
+ esac
554
+ # Also check ID_LIKE for Arch derivatives
555
+ local os_id_like
556
+ os_id_like="$(grep -E '^ID_LIKE=' /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true)"
557
+ if [[ "$os_id_like" == *arch* ]]; then
558
+ return 0
559
+ fi
560
+ fi
561
+ # Fallback: check for pacman
562
+ if command -v pacman &> /dev/null; then
563
+ return 0
564
+ fi
565
+ return 1
566
+ }
567
+
568
+ install_build_tools_linux() {
569
+ require_sudo
570
+
571
+ if command -v apt-get &> /dev/null; then
572
+ if is_root; then
573
+ run_quiet_step "Updating package index" apt-get update -qq
574
+ run_quiet_step "Installing build tools" apt-get install -y -qq build-essential python3 make g++ cmake
575
+ else
576
+ run_quiet_step "Updating package index" sudo apt-get update -qq
577
+ run_quiet_step "Installing build tools" sudo apt-get install -y -qq build-essential python3 make g++ cmake
578
+ fi
579
+ return 0
580
+ fi
581
+
582
+ if command -v pacman &> /dev/null || is_arch_linux; then
583
+ if is_root; then
584
+ run_quiet_step "Installing build tools" pacman -Sy --noconfirm base-devel python make cmake gcc
585
+ else
586
+ run_quiet_step "Installing build tools" sudo pacman -Sy --noconfirm base-devel python make cmake gcc
587
+ fi
588
+ return 0
589
+ fi
590
+
591
+ if command -v dnf &> /dev/null; then
592
+ if is_root; then
593
+ run_quiet_step "Installing build tools" dnf install -y -q gcc gcc-c++ make cmake python3
594
+ else
595
+ run_quiet_step "Installing build tools" sudo dnf install -y -q gcc gcc-c++ make cmake python3
596
+ fi
597
+ return 0
598
+ fi
599
+
600
+ if command -v yum &> /dev/null; then
601
+ if is_root; then
602
+ run_quiet_step "Installing build tools" yum install -y -q gcc gcc-c++ make cmake python3
603
+ else
604
+ run_quiet_step "Installing build tools" sudo yum install -y -q gcc gcc-c++ make cmake python3
605
+ fi
606
+ return 0
607
+ fi
608
+
609
+ if command -v apk &> /dev/null; then
610
+ if is_root; then
611
+ run_quiet_step "Installing build tools" apk add --no-cache build-base python3 cmake
612
+ else
613
+ run_quiet_step "Installing build tools" sudo apk add --no-cache build-base python3 cmake
614
+ fi
615
+ return 0
616
+ fi
617
+
618
+ ui_warn "Could not detect package manager for auto-installing build tools"
619
+ return 1
620
+ }
621
+
622
+ install_build_tools_macos() {
623
+ local ok=true
624
+
625
+ if ! xcode-select -p >/dev/null 2>&1; then
626
+ ui_info "Installing Xcode Command Line Tools (required for make/clang)"
627
+ xcode-select --install >/dev/null 2>&1 || true
628
+ if ! xcode-select -p >/dev/null 2>&1; then
629
+ ui_warn "Xcode Command Line Tools are not ready yet"
630
+ ui_info "Complete the installer dialog, then re-run this installer"
631
+ ok=false
632
+ fi
633
+ fi
634
+
635
+ if ! command -v cmake >/dev/null 2>&1; then
636
+ if command -v brew >/dev/null 2>&1; then
637
+ run_quiet_step "Installing cmake" brew install cmake
638
+ else
639
+ ui_warn "Homebrew not available; cannot auto-install cmake"
640
+ ok=false
641
+ fi
642
+ fi
643
+
644
+ if ! command -v make >/dev/null 2>&1; then
645
+ ui_warn "make is still unavailable"
646
+ ok=false
647
+ fi
648
+ if ! command -v cmake >/dev/null 2>&1; then
649
+ ui_warn "cmake is still unavailable"
650
+ ok=false
651
+ fi
652
+
653
+ [[ "$ok" == "true" ]]
654
+ }
655
+
656
+ auto_install_build_tools_for_npm_failure() {
657
+ local log="$1"
658
+ if ! npm_log_indicates_missing_build_tools "$log"; then
659
+ return 1
660
+ fi
661
+
662
+ ui_warn "Detected missing native build tools; attempting automatic setup"
663
+ if [[ "$OS" == "linux" ]]; then
664
+ install_build_tools_linux || return 1
665
+ elif [[ "$OS" == "macos" ]]; then
666
+ install_build_tools_macos || return 1
667
+ else
668
+ return 1
669
+ fi
670
+ ui_success "Build tools setup complete"
671
+ return 0
672
+ }
673
+
674
+ run_npm_global_install() {
675
+ local spec="$1"
676
+ local log="$2"
677
+
678
+ local -a cmd
679
+ cmd=(env "SHARP_IGNORE_GLOBAL_LIBVIPS=$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL")
680
+ if [[ -n "$NPM_SILENT_FLAG" ]]; then
681
+ cmd+=("$NPM_SILENT_FLAG")
682
+ fi
683
+ if npm_supports_allow_scripts; then
684
+ cmd+=(--allow-scripts="$ALLOW_SCRIPTS_LIST")
685
+ fi
686
+ cmd+=(--no-fund --no-audit install -g "$spec")
687
+ local cmd_display=""
688
+ printf -v cmd_display '%q ' "${cmd[@]}"
689
+ LAST_NPM_INSTALL_CMD="${cmd_display% }"
690
+
691
+ if [[ "$VERBOSE" == "1" ]]; then
692
+ "${cmd[@]}" 2>&1 | tee "$log"
693
+ return $?
694
+ fi
695
+
696
+ if [[ -n "$GUM" ]] && gum_is_tty; then
697
+ local cmd_quoted=""
698
+ local log_quoted=""
699
+ printf -v cmd_quoted '%q ' "${cmd[@]}"
700
+ printf -v log_quoted '%q' "$log"
701
+ run_with_spinner "Installing OpenClaw package" bash -c "${cmd_quoted}>${log_quoted} 2>&1"
702
+ return $?
703
+ fi
704
+
705
+ "${cmd[@]}" >"$log" 2>&1
706
+ }
707
+
708
+ extract_npm_debug_log_path() {
709
+ local log="$1"
710
+ local path=""
711
+ path="$(sed -n -E 's/.*A complete log of this run can be found in:[[:space:]]*//p' "$log" | tail -n1)"
712
+ if [[ -n "$path" ]]; then
713
+ echo "$path"
714
+ return 0
715
+ fi
716
+
717
+ path="$(grep -Eo '/[^[:space:]]+_logs/[^[:space:]]+debug[^[:space:]]*\.log' "$log" | tail -n1 || true)"
718
+ if [[ -n "$path" ]]; then
719
+ echo "$path"
720
+ return 0
721
+ fi
722
+
723
+ return 1
724
+ }
725
+
726
+ extract_first_npm_error_line() {
727
+ local log="$1"
728
+ grep -E 'npm (ERR!|error)|ERR!' "$log" | head -n1 || true
729
+ }
730
+
731
+ extract_npm_error_code() {
732
+ local log="$1"
733
+ sed -n -E 's/^npm (ERR!|error) code[[:space:]]+([^[:space:]]+).*$/\2/p' "$log" | head -n1
734
+ }
735
+
736
+ extract_npm_error_syscall() {
737
+ local log="$1"
738
+ sed -n -E 's/^npm (ERR!|error) syscall[[:space:]]+(.+)$/\2/p' "$log" | head -n1
739
+ }
740
+
741
+ extract_npm_error_errno() {
742
+ local log="$1"
743
+ sed -n -E 's/^npm (ERR!|error) errno[[:space:]]+(.+)$/\2/p' "$log" | head -n1
744
+ }
745
+
746
+ print_npm_failure_diagnostics() {
747
+ local spec="$1"
748
+ local log="$2"
749
+ local debug_log=""
750
+ local first_error=""
751
+ local error_code=""
752
+ local error_syscall=""
753
+ local error_errno=""
754
+
755
+ ui_warn "npm install failed for ${spec}"
756
+ if [[ -n "${LAST_NPM_INSTALL_CMD}" ]]; then
757
+ echo " Command: ${LAST_NPM_INSTALL_CMD}"
758
+ fi
759
+ echo " Installer log: ${log}"
760
+
761
+ error_code="$(extract_npm_error_code "$log")"
762
+ if [[ -n "$error_code" ]]; then
763
+ echo " npm code: ${error_code}"
764
+ fi
765
+
766
+ error_syscall="$(extract_npm_error_syscall "$log")"
767
+ if [[ -n "$error_syscall" ]]; then
768
+ echo " npm syscall: ${error_syscall}"
769
+ fi
770
+
771
+ error_errno="$(extract_npm_error_errno "$log")"
772
+ if [[ -n "$error_errno" ]]; then
773
+ echo " npm errno: ${error_errno}"
774
+ fi
775
+
776
+ debug_log="$(extract_npm_debug_log_path "$log" || true)"
777
+ if [[ -n "$debug_log" ]]; then
778
+ echo " npm debug log: ${debug_log}"
779
+ fi
780
+
781
+ first_error="$(extract_first_npm_error_line "$log")"
782
+ if [[ -n "$first_error" ]]; then
783
+ echo " First npm error: ${first_error}"
784
+ fi
785
+ }
786
+
787
+ install_openclaw_npm() {
788
+ local spec="$1"
789
+ local log
790
+ log="$(mktempfile)"
791
+ if ! run_npm_global_install "$spec" "$log"; then
792
+ local attempted_build_tool_fix=false
793
+ if auto_install_build_tools_for_npm_failure "$log"; then
794
+ attempted_build_tool_fix=true
795
+ ui_info "Retrying npm install after build tools setup"
796
+ if run_npm_global_install "$spec" "$log"; then
797
+ ui_success "OpenClaw npm package installed"
798
+ return 0
799
+ fi
800
+ fi
801
+
802
+ print_npm_failure_diagnostics "$spec" "$log"
803
+
804
+ if [[ "$VERBOSE" != "1" ]]; then
805
+ if [[ "$attempted_build_tool_fix" == "true" ]]; then
806
+ ui_warn "npm install still failed after build tools setup; showing last log lines"
807
+ else
808
+ ui_warn "npm install failed; showing last log lines"
809
+ fi
810
+ tail -n 80 "$log" >&2 || true
811
+ fi
812
+
813
+ if grep -q "ENOTEMPTY: directory not empty, rename .*openclaw" "$log"; then
814
+ ui_warn "npm left stale directory; cleaning and retrying"
815
+ cleanup_npm_openclaw_paths
816
+ if run_npm_global_install "$spec" "$log"; then
817
+ ui_success "OpenClaw npm package installed"
818
+ return 0
819
+ fi
820
+ return 1
821
+ fi
822
+ if grep -q "EEXIST" "$log"; then
823
+ local conflict=""
824
+ conflict="$(extract_openclaw_conflict_path "$log" || true)"
825
+ if [[ -n "$conflict" ]] && cleanup_openclaw_bin_conflict "$conflict"; then
826
+ if run_npm_global_install "$spec" "$log"; then
827
+ ui_success "OpenClaw npm package installed"
828
+ return 0
829
+ fi
830
+ return 1
831
+ fi
832
+ ui_error "npm failed because an openclaw binary already exists"
833
+ if [[ -n "$conflict" ]]; then
834
+ ui_info "Remove or move ${conflict}, then retry"
835
+ fi
836
+ ui_info "Or rerun with: npm install -g --force ${spec}"
837
+ fi
838
+ return 1
839
+ fi
840
+ ui_success "OpenClaw npm package installed"
841
+ return 0
842
+ }
843
+
844
+ TAGLINES=()
845
+ TAGLINES+=("Your terminal just grew claws—type something and let the bot pinch the busywork.")
846
+ TAGLINES+=("Welcome to the command line: where dreams compile and confidence segfaults.")
847
+ TAGLINES+=("I run on caffeine, JSON5, and the audacity of \"it worked on my machine.\"")
848
+ TAGLINES+=("Gateway online—please keep hands, feet, and appendages inside the shell at all times.")
849
+ TAGLINES+=("I speak fluent bash, mild sarcasm, and aggressive tab-completion energy.")
850
+ TAGLINES+=("One CLI to rule them all, and one more restart because you changed the port.")
851
+ TAGLINES+=("If it works, it's automation; if it breaks, it's a \"learning opportunity.\"")
852
+ TAGLINES+=("Pairing codes exist because even bots believe in consent—and good security hygiene.")
853
+ TAGLINES+=("Your .env is showing; don't worry, I'll pretend I didn't see it.")
854
+ TAGLINES+=("I'll do the boring stuff while you dramatically stare at the logs like it's cinema.")
855
+ TAGLINES+=("I'm not saying your workflow is chaotic... I'm just bringing a linter and a helmet.")
856
+ TAGLINES+=("Type the command with confidence—nature will provide the stack trace if needed.")
857
+ TAGLINES+=("I don't judge, but your missing API keys are absolutely judging you.")
858
+ TAGLINES+=("I can grep it, git blame it, and gently roast it—pick your coping mechanism.")
859
+ TAGLINES+=("Hot reload for config, cold sweat for deploys.")
860
+ TAGLINES+=("I'm the assistant your terminal demanded, not the one your sleep schedule requested.")
861
+ TAGLINES+=("I keep secrets like a vault... unless you print them in debug logs again.")
862
+ TAGLINES+=("Automation with claws: minimal fuss, maximal pinch.")
863
+ TAGLINES+=("I'm basically a Swiss Army knife, but with more opinions and fewer sharp edges.")
864
+ TAGLINES+=("If you're lost, run doctor; if you're brave, run prod; if you're wise, run tests.")
865
+ TAGLINES+=("Your task has been queued; your dignity has been deprecated.")
866
+ TAGLINES+=("I can't fix your code taste, but I can fix your build and your backlog.")
867
+ TAGLINES+=("I'm not magic—I'm just extremely persistent with retries and coping strategies.")
868
+ TAGLINES+=("It's not \"failing,\" it's \"discovering new ways to configure the same thing wrong.\"")
869
+ TAGLINES+=("Give me a workspace and I'll give you fewer tabs, fewer toggles, and more oxygen.")
870
+ TAGLINES+=("I read logs so you can keep pretending you don't have to.")
871
+ TAGLINES+=("If something's on fire, I can't extinguish it—but I can write a beautiful postmortem.")
872
+ TAGLINES+=("I'll refactor your busywork like it owes me money.")
873
+ TAGLINES+=("Say \"stop\" and I'll stop—say \"ship\" and we'll both learn a lesson.")
874
+ TAGLINES+=("I'm the reason your shell history looks like a hacker-movie montage.")
875
+ TAGLINES+=("I'm like tmux: confusing at first, then suddenly you can't live without me.")
876
+ TAGLINES+=("I can run local, remote, or purely on vibes—results may vary with DNS.")
877
+ TAGLINES+=("If you can describe it, I can probably automate it—or at least make it funnier.")
878
+ TAGLINES+=("Your config is valid, your assumptions are not.")
879
+ TAGLINES+=("I don't just autocomplete—I auto-commit (emotionally), then ask you to review (logically).")
880
+ TAGLINES+=("Less clicking, more shipping, fewer \"where did that file go\" moments.")
881
+ TAGLINES+=("Claws out, commit in—let's ship something mildly responsible.")
882
+ TAGLINES+=("I'll butter your workflow like a lobster roll: messy, delicious, effective.")
883
+ TAGLINES+=("Shell yeah—I'm here to pinch the toil and leave you the glory.")
884
+ TAGLINES+=("If it's repetitive, I'll automate it; if it's hard, I'll bring jokes and a rollback plan.")
885
+ TAGLINES+=("Because texting yourself reminders is so 2024.")
886
+ TAGLINES+=("WhatsApp, but make it ✨engineering✨.")
887
+ TAGLINES+=("Turning \"I'll reply later\" into \"my bot replied instantly\".")
888
+ TAGLINES+=("The only crab in your contacts you actually want to hear from. 🦞")
889
+ TAGLINES+=("Chat automation for people who peaked at IRC.")
890
+ TAGLINES+=("Because Siri wasn't answering at 3AM.")
891
+ TAGLINES+=("IPC, but it's your phone.")
892
+ TAGLINES+=("The UNIX philosophy meets your DMs.")
893
+ TAGLINES+=("curl for conversations.")
894
+ TAGLINES+=("WhatsApp Business, but without the business.")
895
+ TAGLINES+=("Meta wishes they shipped this fast.")
896
+ TAGLINES+=("End-to-end encrypted, Zuck-to-Zuck excluded.")
897
+ TAGLINES+=("The only bot Mark can't train on your DMs.")
898
+ TAGLINES+=("WhatsApp automation without the \"please accept our new privacy policy\".")
899
+ TAGLINES+=("Chat APIs that don't require a Senate hearing.")
900
+ TAGLINES+=("Because Threads wasn't the answer either.")
901
+ TAGLINES+=("Your messages, your servers, Meta's tears.")
902
+ TAGLINES+=("iMessage green bubble energy, but for everyone.")
903
+ TAGLINES+=("Siri's competent cousin.")
904
+ TAGLINES+=("Works on Android. Crazy concept, we know.")
905
+ TAGLINES+=("No \$999 stand required.")
906
+ TAGLINES+=("We ship features faster than Apple ships calculator updates.")
907
+ TAGLINES+=("Your AI assistant, now without the \$3,499 headset.")
908
+ TAGLINES+=("Think different. Actually think.")
909
+ TAGLINES+=("Ah, the fruit tree company! 🍎")
910
+
911
+ HOLIDAY_NEW_YEAR="New Year's Day: New year, new config—same old EADDRINUSE, but this time we resolve it like grown-ups."
912
+ HOLIDAY_LUNAR_NEW_YEAR="Lunar New Year: May your builds be lucky, your branches prosperous, and your merge conflicts chased away with fireworks."
913
+ HOLIDAY_CHRISTMAS="Christmas: Ho ho ho—Santa's little claw-sistant is here to ship joy, roll back chaos, and stash the keys safely."
914
+ HOLIDAY_EID="Eid al-Fitr: Celebration mode: queues cleared, tasks completed, and good vibes committed to main with clean history."
915
+ HOLIDAY_DIWALI="Diwali: Let the logs sparkle and the bugs flee—today we light up the terminal and ship with pride."
916
+ HOLIDAY_EASTER="Easter: I found your missing environment variable—consider it a tiny CLI egg hunt with fewer jellybeans."
917
+ HOLIDAY_HANUKKAH="Hanukkah: Eight nights, eight retries, zero shame—may your gateway stay lit and your deployments stay peaceful."
918
+ HOLIDAY_HALLOWEEN="Halloween: Spooky season: beware haunted dependencies, cursed caches, and the ghost of node_modules past."
919
+ HOLIDAY_THANKSGIVING="Thanksgiving: Grateful for stable ports, working DNS, and a bot that reads the logs so nobody has to."
920
+ HOLIDAY_VALENTINES="Valentine's Day: Roses are typed, violets are piped—I'll automate the chores so you can spend time with humans."
921
+
922
+ append_holiday_taglines() {
923
+ local today
924
+ local month_day
925
+ today="$(date -u +%Y-%m-%d 2>/dev/null || date +%Y-%m-%d)"
926
+ month_day="$(date -u +%m-%d 2>/dev/null || date +%m-%d)"
927
+
928
+ case "$month_day" in
929
+ "01-01") TAGLINES+=("$HOLIDAY_NEW_YEAR") ;;
930
+ "02-14") TAGLINES+=("$HOLIDAY_VALENTINES") ;;
931
+ "10-31") TAGLINES+=("$HOLIDAY_HALLOWEEN") ;;
932
+ "12-25") TAGLINES+=("$HOLIDAY_CHRISTMAS") ;;
933
+ esac
934
+
935
+ case "$today" in
936
+ "2025-01-29"|"2026-02-17"|"2027-02-06") TAGLINES+=("$HOLIDAY_LUNAR_NEW_YEAR") ;;
937
+ "2025-03-30"|"2025-03-31"|"2026-03-20"|"2027-03-10") TAGLINES+=("$HOLIDAY_EID") ;;
938
+ "2025-10-20"|"2026-11-08"|"2027-10-28") TAGLINES+=("$HOLIDAY_DIWALI") ;;
939
+ "2025-04-20"|"2026-04-05"|"2027-03-28") TAGLINES+=("$HOLIDAY_EASTER") ;;
940
+ "2025-11-27"|"2026-11-26"|"2027-11-25") TAGLINES+=("$HOLIDAY_THANKSGIVING") ;;
941
+ "2025-12-15"|"2025-12-16"|"2025-12-17"|"2025-12-18"|"2025-12-19"|"2025-12-20"|"2025-12-21"|"2025-12-22"|"2026-12-05"|"2026-12-06"|"2026-12-07"|"2026-12-08"|"2026-12-09"|"2026-12-10"|"2026-12-11"|"2026-12-12"|"2027-12-25"|"2027-12-26"|"2027-12-27"|"2027-12-28"|"2027-12-29"|"2027-12-30"|"2027-12-31"|"2028-01-01") TAGLINES+=("$HOLIDAY_HANUKKAH") ;;
942
+ esac
943
+ }
944
+
945
+ map_legacy_env() {
946
+ local key="$1"
947
+ local legacy="$2"
948
+ if [[ -z "${!key:-}" && -n "${!legacy:-}" ]]; then
949
+ printf -v "$key" '%s' "${!legacy}"
950
+ fi
951
+ }
952
+
953
+ map_legacy_env "OPENCLAW_TAGLINE_INDEX" "CLAWDBOT_TAGLINE_INDEX"
954
+ map_legacy_env "OPENCLAW_NO_ONBOARD" "CLAWDBOT_NO_ONBOARD"
955
+ map_legacy_env "OPENCLAW_NO_PROMPT" "CLAWDBOT_NO_PROMPT"
956
+ map_legacy_env "OPENCLAW_DRY_RUN" "CLAWDBOT_DRY_RUN"
957
+ map_legacy_env "OPENCLAW_INSTALL_METHOD" "CLAWDBOT_INSTALL_METHOD"
958
+ map_legacy_env "OPENCLAW_VERSION" "CLAWDBOT_VERSION"
959
+ map_legacy_env "OPENCLAW_BETA" "CLAWDBOT_BETA"
960
+ map_legacy_env "OPENCLAW_GIT_DIR" "CLAWDBOT_GIT_DIR"
961
+ map_legacy_env "OPENCLAW_GIT_UPDATE" "CLAWDBOT_GIT_UPDATE"
962
+ map_legacy_env "OPENCLAW_NPM_LOGLEVEL" "CLAWDBOT_NPM_LOGLEVEL"
963
+ map_legacy_env "OPENCLAW_VERBOSE" "CLAWDBOT_VERBOSE"
964
+ map_legacy_env "OPENCLAW_PROFILE" "CLAWDBOT_PROFILE"
965
+ map_legacy_env "OPENCLAW_INSTALL_SH_NO_RUN" "CLAWDBOT_INSTALL_SH_NO_RUN"
966
+
967
+ pick_tagline() {
968
+ append_holiday_taglines
969
+ local count=${#TAGLINES[@]}
970
+ if [[ "$count" -eq 0 ]]; then
971
+ echo "$DEFAULT_TAGLINE"
972
+ return
973
+ fi
974
+ if [[ -n "${OPENCLAW_TAGLINE_INDEX:-}" ]]; then
975
+ if [[ "${OPENCLAW_TAGLINE_INDEX}" =~ ^[0-9]+$ ]]; then
976
+ local idx=$((OPENCLAW_TAGLINE_INDEX % count))
977
+ echo "${TAGLINES[$idx]}"
978
+ return
979
+ fi
980
+ fi
981
+ local idx=$((RANDOM % count))
982
+ echo "${TAGLINES[$idx]}"
983
+ }
984
+
985
+ TAGLINE=$(pick_tagline)
986
+
987
+ NO_ONBOARD=${OPENCLAW_NO_ONBOARD:-0}
988
+ NO_PROMPT=${OPENCLAW_NO_PROMPT:-0}
989
+ DRY_RUN=${OPENCLAW_DRY_RUN:-0}
990
+ INSTALL_METHOD=${OPENCLAW_INSTALL_METHOD:-}
991
+ OPENCLAW_VERSION=${OPENCLAW_VERSION:-latest}
992
+ USE_BETA=${OPENCLAW_BETA:-0}
993
+ GIT_DIR_DEFAULT="${HOME}/openclaw"
994
+ GIT_DIR=${OPENCLAW_GIT_DIR:-$GIT_DIR_DEFAULT}
995
+ GIT_UPDATE=${OPENCLAW_GIT_UPDATE:-1}
996
+ SHARP_IGNORE_GLOBAL_LIBVIPS="${SHARP_IGNORE_GLOBAL_LIBVIPS:-1}"
997
+ NPM_LOGLEVEL="${OPENCLAW_NPM_LOGLEVEL:-error}"
998
+ NPM_SILENT_FLAG="--silent"
999
+ ALLOW_SCRIPTS_LIST="wednesdayai,@discordjs/opus,node-llama-cpp,@google/genai,protobufjs,koffi"
1000
+
1001
+ npm_supports_allow_scripts() {
1002
+ local version core major minor patch pre=""
1003
+ version="$(npm --version 2>/dev/null)" || return 1
1004
+ version="${version#v}"
1005
+ version="${version%%+*}"
1006
+ [[ -n "$version" ]] || return 1
1007
+ if [[ "$version" == *-* ]]; then
1008
+ core="${version%%-*}"
1009
+ pre="${version#*-}"
1010
+ else
1011
+ core="$version"
1012
+ fi
1013
+ local -a parts
1014
+ IFS='.' read -r -a parts <<< "$core"
1015
+ major="${parts[0]:-}" minor="${parts[1]:-}" patch="${parts[2]:-}"
1016
+ [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ && "$patch" =~ ^[0-9]+$ ]] || return 1
1017
+ (( major > 11 )) && return 0
1018
+ (( major == 11 && minor > 16 )) && return 0
1019
+ (( major == 11 && minor == 16 && patch > 0 )) && return 0
1020
+ # Exact threshold core: a prerelease is below the release (SemVer item 11).
1021
+ (( major == 11 && minor == 16 && patch == 0 )) && [[ -z "$pre" ]]
1022
+ return $?
1023
+ }
1024
+ VERBOSE="${OPENCLAW_VERBOSE:-0}"
1025
+ OPENCLAW_BIN=""
1026
+ PNPM_CMD=()
1027
+ HELP=0
1028
+
1029
+ print_usage() {
1030
+ cat <<EOF
1031
+ OpenClaw installer (macOS + Linux)
1032
+
1033
+ Usage:
1034
+ curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash -s -- [options]
1035
+
1036
+ Options:
1037
+ --install-method, --method npm|git Install via npm (default) or from a git checkout
1038
+ --npm Shortcut for --install-method npm
1039
+ --git, --github Shortcut for --install-method git
1040
+ --version <version|dist-tag> npm install: version (default: latest)
1041
+ --beta Use beta if available, else latest
1042
+ --git-dir, --dir <path> Checkout directory (default: ~/openclaw)
1043
+ --no-git-update Skip git pull for existing checkout
1044
+ --no-onboard Skip onboarding (non-interactive)
1045
+ --no-prompt Disable prompts (required in CI/automation)
1046
+ --dry-run Print what would happen (no changes)
1047
+ --verbose Print debug output (set -x, npm verbose)
1048
+ --help, -h Show this help
1049
+
1050
+ Environment variables:
1051
+ OPENCLAW_INSTALL_METHOD=git|npm
1052
+ OPENCLAW_VERSION=latest|next|<semver>
1053
+ OPENCLAW_BETA=0|1
1054
+ OPENCLAW_GIT_DIR=...
1055
+ OPENCLAW_GIT_UPDATE=0|1
1056
+ OPENCLAW_NO_PROMPT=1
1057
+ OPENCLAW_DRY_RUN=1
1058
+ OPENCLAW_NO_ONBOARD=1
1059
+ OPENCLAW_VERBOSE=1
1060
+ OPENCLAW_NPM_LOGLEVEL=error|warn|notice Default: error (hide npm deprecation noise)
1061
+ SHARP_IGNORE_GLOBAL_LIBVIPS=0|1 Default: 1 (avoid sharp building against global libvips)
1062
+
1063
+ Examples:
1064
+ curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash
1065
+ curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash -s -- --no-onboard
1066
+ curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash -s -- --install-method git --no-onboard
1067
+ EOF
1068
+ }
1069
+
1070
+ parse_args() {
1071
+ while [[ $# -gt 0 ]]; do
1072
+ case "$1" in
1073
+ --no-onboard)
1074
+ NO_ONBOARD=1
1075
+ shift
1076
+ ;;
1077
+ --onboard)
1078
+ NO_ONBOARD=0
1079
+ shift
1080
+ ;;
1081
+ --dry-run)
1082
+ DRY_RUN=1
1083
+ shift
1084
+ ;;
1085
+ --verbose)
1086
+ VERBOSE=1
1087
+ shift
1088
+ ;;
1089
+ --no-prompt)
1090
+ NO_PROMPT=1
1091
+ shift
1092
+ ;;
1093
+ --help|-h)
1094
+ HELP=1
1095
+ shift
1096
+ ;;
1097
+ --install-method|--method)
1098
+ INSTALL_METHOD="$2"
1099
+ shift 2
1100
+ ;;
1101
+ --version)
1102
+ OPENCLAW_VERSION="$2"
1103
+ shift 2
1104
+ ;;
1105
+ --beta)
1106
+ USE_BETA=1
1107
+ shift
1108
+ ;;
1109
+ --npm)
1110
+ INSTALL_METHOD="npm"
1111
+ shift
1112
+ ;;
1113
+ --git|--github)
1114
+ INSTALL_METHOD="git"
1115
+ shift
1116
+ ;;
1117
+ --git-dir|--dir)
1118
+ GIT_DIR="$2"
1119
+ shift 2
1120
+ ;;
1121
+ --no-git-update)
1122
+ GIT_UPDATE=0
1123
+ shift
1124
+ ;;
1125
+ *)
1126
+ shift
1127
+ ;;
1128
+ esac
1129
+ done
1130
+ }
1131
+
1132
+ configure_verbose() {
1133
+ if [[ "$VERBOSE" != "1" ]]; then
1134
+ return 0
1135
+ fi
1136
+ if [[ "$NPM_LOGLEVEL" == "error" ]]; then
1137
+ NPM_LOGLEVEL="notice"
1138
+ fi
1139
+ NPM_SILENT_FLAG=""
1140
+ set -x
1141
+ }
1142
+
1143
+ is_promptable() {
1144
+ if [[ "$NO_PROMPT" == "1" ]]; then
1145
+ return 1
1146
+ fi
1147
+ if [[ -r /dev/tty && -w /dev/tty ]]; then
1148
+ return 0
1149
+ fi
1150
+ return 1
1151
+ }
1152
+
1153
+ prompt_choice() {
1154
+ local prompt="$1"
1155
+ local answer=""
1156
+ if ! is_promptable; then
1157
+ return 1
1158
+ fi
1159
+ echo -e "$prompt" > /dev/tty
1160
+ read -r answer < /dev/tty || true
1161
+ echo "$answer"
1162
+ }
1163
+
1164
+ choose_install_method_interactive() {
1165
+ local detected_checkout="$1"
1166
+
1167
+ if ! is_promptable; then
1168
+ return 1
1169
+ fi
1170
+
1171
+ if [[ -n "$GUM" ]] && gum_is_tty; then
1172
+ local header selection
1173
+ header="Detected OpenClaw checkout in: ${detected_checkout}
1174
+ Choose install method"
1175
+ selection="$("$GUM" choose \
1176
+ --header "$header" \
1177
+ --cursor-prefix "❯ " \
1178
+ "git · update this checkout and use it" \
1179
+ "npm · install globally via npm" < /dev/tty || true)"
1180
+
1181
+ case "$selection" in
1182
+ git*)
1183
+ echo "git"
1184
+ return 0
1185
+ ;;
1186
+ npm*)
1187
+ echo "npm"
1188
+ return 0
1189
+ ;;
1190
+ esac
1191
+ return 1
1192
+ fi
1193
+
1194
+ local choice=""
1195
+ choice="$(prompt_choice "$(cat <<EOF
1196
+ ${WARN}→${NC} Detected a OpenClaw source checkout in: ${INFO}${detected_checkout}${NC}
1197
+ Choose install method:
1198
+ 1) Update this checkout (git) and use it
1199
+ 2) Install global via npm (migrate away from git)
1200
+ Enter 1 or 2:
1201
+ EOF
1202
+ )" || true)"
1203
+
1204
+ case "$choice" in
1205
+ 1)
1206
+ echo "git"
1207
+ return 0
1208
+ ;;
1209
+ 2)
1210
+ echo "npm"
1211
+ return 0
1212
+ ;;
1213
+ esac
1214
+
1215
+ return 1
1216
+ }
1217
+
1218
+ detect_openclaw_checkout() {
1219
+ local dir="$1"
1220
+ if [[ ! -f "$dir/package.json" ]]; then
1221
+ return 1
1222
+ fi
1223
+ if [[ ! -f "$dir/pnpm-workspace.yaml" ]]; then
1224
+ return 1
1225
+ fi
1226
+ if ! grep -q '"name"[[:space:]]*:[[:space:]]*"openclaw"' "$dir/package.json" 2>/dev/null; then
1227
+ return 1
1228
+ fi
1229
+ echo "$dir"
1230
+ return 0
1231
+ }
1232
+
1233
+ # Check for Homebrew on macOS
1234
+ is_macos_admin_user() {
1235
+ if [[ "$OS" != "macos" ]]; then
1236
+ return 0
1237
+ fi
1238
+ if is_root; then
1239
+ return 0
1240
+ fi
1241
+ id -Gn "$(id -un)" 2>/dev/null | grep -qw "admin"
1242
+ }
1243
+
1244
+ print_homebrew_admin_fix() {
1245
+ local current_user
1246
+ current_user="$(id -un 2>/dev/null || echo "${USER:-current user}")"
1247
+ ui_error "Homebrew installation requires a macOS Administrator account"
1248
+ echo "Current user (${current_user}) is not in the admin group."
1249
+ echo "Fix options:"
1250
+ echo " 1) Use an Administrator account and re-run the installer."
1251
+ echo " 2) Ask an Administrator to grant admin rights, then sign out/in:"
1252
+ echo " sudo dseditgroup -o edit -a ${current_user} -t user admin"
1253
+ echo "Then retry:"
1254
+ echo " curl -fsSL https://wednesdayai.dev/install.sh | bash"
1255
+ }
1256
+
1257
+ install_homebrew() {
1258
+ if [[ "$OS" == "macos" ]]; then
1259
+ if ! command -v brew &> /dev/null; then
1260
+ if ! is_macos_admin_user; then
1261
+ print_homebrew_admin_fix
1262
+ exit 1
1263
+ fi
1264
+ ui_info "Homebrew not found, installing"
1265
+ run_quiet_step "Installing Homebrew" run_remote_bash "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"
1266
+
1267
+ # Add Homebrew to PATH for this session
1268
+ if [[ -f "/opt/homebrew/bin/brew" ]]; then
1269
+ eval "$(/opt/homebrew/bin/brew shellenv)"
1270
+ elif [[ -f "/usr/local/bin/brew" ]]; then
1271
+ eval "$(/usr/local/bin/brew shellenv)"
1272
+ fi
1273
+ ui_success "Homebrew installed"
1274
+ else
1275
+ ui_success "Homebrew already installed"
1276
+ fi
1277
+ fi
1278
+ }
1279
+
1280
+ # Check Node.js version
1281
+ parse_node_version_components() {
1282
+ if ! command -v node &> /dev/null; then
1283
+ return 1
1284
+ fi
1285
+ local version major minor
1286
+ version="$(node -v 2>/dev/null || true)"
1287
+ major="${version#v}"
1288
+ major="${major%%.*}"
1289
+ minor="${version#v}"
1290
+ minor="${minor#*.}"
1291
+ minor="${minor%%.*}"
1292
+
1293
+ if [[ ! "$major" =~ ^[0-9]+$ ]]; then
1294
+ return 1
1295
+ fi
1296
+ if [[ ! "$minor" =~ ^[0-9]+$ ]]; then
1297
+ return 1
1298
+ fi
1299
+ echo "${major} ${minor}"
1300
+ return 0
1301
+ }
1302
+
1303
+ node_major_version() {
1304
+ local version_components major minor
1305
+ version_components="$(parse_node_version_components || true)"
1306
+ read -r major minor <<< "$version_components"
1307
+ if [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then
1308
+ echo "$major"
1309
+ return 0
1310
+ fi
1311
+ return 1
1312
+ }
1313
+
1314
+ node_is_at_least_required() {
1315
+ local version_components major minor
1316
+ version_components="$(parse_node_version_components || true)"
1317
+ read -r major minor <<< "$version_components"
1318
+ if [[ ! "$major" =~ ^[0-9]+$ || ! "$minor" =~ ^[0-9]+$ ]]; then
1319
+ return 1
1320
+ fi
1321
+ if [[ "$major" -gt "$NODE_MIN_MAJOR" ]]; then
1322
+ return 0
1323
+ fi
1324
+ if [[ "$major" -eq "$NODE_MIN_MAJOR" && "$minor" -ge "$NODE_MIN_MINOR" ]]; then
1325
+ return 0
1326
+ fi
1327
+ return 1
1328
+ }
1329
+
1330
+ print_active_node_paths() {
1331
+ if ! command -v node &> /dev/null; then
1332
+ return 1
1333
+ fi
1334
+ local node_path node_version npm_path npm_version
1335
+ node_path="$(command -v node 2>/dev/null || true)"
1336
+ node_version="$(node -v 2>/dev/null || true)"
1337
+ ui_info "Active Node.js: ${node_version:-unknown} (${node_path:-unknown})"
1338
+
1339
+ if command -v npm &> /dev/null; then
1340
+ npm_path="$(command -v npm 2>/dev/null || true)"
1341
+ npm_version="$(npm -v 2>/dev/null || true)"
1342
+ ui_info "Active npm: ${npm_version:-unknown} (${npm_path:-unknown})"
1343
+ fi
1344
+ return 0
1345
+ }
1346
+
1347
+ ensure_macos_node24_active() {
1348
+ if [[ "$OS" != "macos" ]]; then
1349
+ return 0
1350
+ fi
1351
+
1352
+ local brew_node_prefix=""
1353
+ if command -v brew &> /dev/null; then
1354
+ brew_node_prefix="$(brew --prefix node@24 2>/dev/null || true)"
1355
+ if [[ -n "$brew_node_prefix" && -x "${brew_node_prefix}/bin/node" ]]; then
1356
+ export PATH="${brew_node_prefix}/bin:$PATH"
1357
+ refresh_shell_command_cache
1358
+ fi
1359
+ fi
1360
+
1361
+ local major=""
1362
+ major="$(node_major_version || true)"
1363
+ if [[ -n "$major" && "$major" -ge 24 ]]; then
1364
+ return 0
1365
+ fi
1366
+
1367
+ local active_path active_version
1368
+ active_path="$(command -v node 2>/dev/null || echo "not found")"
1369
+ active_version="$(node -v 2>/dev/null || echo "missing")"
1370
+
1371
+ ui_error "Node.js v24 was installed but this shell is using ${active_version} (${active_path})"
1372
+ if [[ -n "$brew_node_prefix" ]]; then
1373
+ echo "Add this to your shell profile and restart shell:"
1374
+ echo " export PATH=\"${brew_node_prefix}/bin:\$PATH\""
1375
+ else
1376
+ echo "Ensure Homebrew node@24 is first on PATH, then rerun installer."
1377
+ fi
1378
+ return 1
1379
+ }
1380
+
1381
+ ensure_node24_active_shell() {
1382
+ if node_is_at_least_required; then
1383
+ return 0
1384
+ fi
1385
+
1386
+ local active_path active_version
1387
+ active_path="$(command -v node 2>/dev/null || echo "not found")"
1388
+ active_version="$(node -v 2>/dev/null || echo "missing")"
1389
+
1390
+ ui_error "Active Node.js must be v${NODE_MIN_VERSION}+ but this shell is using ${active_version} (${active_path})"
1391
+ print_active_node_paths || true
1392
+
1393
+ local nvm_detected=0
1394
+ if [[ -n "${NVM_DIR:-}" || "$active_path" == *"/.nvm/"* ]]; then
1395
+ nvm_detected=1
1396
+ fi
1397
+ if command -v nvm >/dev/null 2>&1; then
1398
+ nvm_detected=1
1399
+ fi
1400
+
1401
+ if [[ "$nvm_detected" -eq 1 ]]; then
1402
+ echo "nvm appears to be managing Node for this shell."
1403
+ echo "Run:"
1404
+ echo " nvm install 24"
1405
+ echo " nvm use 24"
1406
+ echo " nvm alias default 24"
1407
+ echo "Then open a new shell and rerun:"
1408
+ echo " curl -fsSL https://wednesdayai.dev/install.sh | bash"
1409
+ else
1410
+ echo "Install/select Node.js 24+ and ensure it is first on PATH, then rerun installer."
1411
+ fi
1412
+
1413
+ return 1
1414
+ }
1415
+
1416
+ check_node() {
1417
+ if command -v node &> /dev/null; then
1418
+ NODE_VERSION="$(node_major_version || true)"
1419
+ if node_is_at_least_required; then
1420
+ ui_success "Node.js v$(node -v | cut -d'v' -f2) found"
1421
+ print_active_node_paths || true
1422
+ return 0
1423
+ else
1424
+ if [[ -n "$NODE_VERSION" ]]; then
1425
+ ui_info "Node.js $(node -v) found, upgrading to v${NODE_MIN_VERSION}+"
1426
+ else
1427
+ ui_info "Node.js found but version could not be parsed; reinstalling v${NODE_MIN_VERSION}+"
1428
+ fi
1429
+ return 1
1430
+ fi
1431
+ else
1432
+ ui_info "Node.js not found, installing it now"
1433
+ return 1
1434
+ fi
1435
+ }
1436
+
1437
+ # Install Node.js
1438
+ install_node() {
1439
+ if [[ "$OS" == "macos" ]]; then
1440
+ ui_info "Installing Node.js via Homebrew"
1441
+ run_quiet_step "Installing node@24" brew install node@24
1442
+ brew link node@24 --overwrite --force 2>/dev/null || true
1443
+ if ! ensure_macos_node24_active; then
1444
+ exit 1
1445
+ fi
1446
+ ui_success "Node.js installed"
1447
+ print_active_node_paths || true
1448
+ elif [[ "$OS" == "linux" ]]; then
1449
+ require_sudo
1450
+
1451
+ ui_info "Installing Linux build tools (make/g++/cmake/python3)"
1452
+ if install_build_tools_linux; then
1453
+ ui_success "Build tools installed"
1454
+ else
1455
+ ui_warn "Continuing without auto-installing build tools"
1456
+ fi
1457
+
1458
+ # Arch-based distros: use pacman with official repos
1459
+ if command -v pacman &> /dev/null || is_arch_linux; then
1460
+ ui_info "Installing Node.js via pacman (Arch-based distribution detected)"
1461
+ if is_root; then
1462
+ run_quiet_step "Installing Node.js" pacman -Sy --noconfirm nodejs npm
1463
+ else
1464
+ run_quiet_step "Installing Node.js" sudo pacman -Sy --noconfirm nodejs npm
1465
+ fi
1466
+ ui_success "Node.js v24 installed"
1467
+ print_active_node_paths || true
1468
+ return 0
1469
+ fi
1470
+
1471
+ ui_info "Installing Node.js via NodeSource"
1472
+ if command -v apt-get &> /dev/null; then
1473
+ local tmp
1474
+ tmp="$(mktempfile)"
1475
+ download_file "https://deb.nodesource.com/setup_24.x" "$tmp"
1476
+ if is_root; then
1477
+ run_quiet_step "Configuring NodeSource repository" bash "$tmp"
1478
+ run_quiet_step "Installing Node.js" apt-get install -y -qq nodejs
1479
+ else
1480
+ run_quiet_step "Configuring NodeSource repository" sudo -E bash "$tmp"
1481
+ run_quiet_step "Installing Node.js" sudo apt-get install -y -qq nodejs
1482
+ fi
1483
+ elif command -v dnf &> /dev/null; then
1484
+ local tmp
1485
+ tmp="$(mktempfile)"
1486
+ download_file "https://rpm.nodesource.com/setup_24.x" "$tmp"
1487
+ if is_root; then
1488
+ run_quiet_step "Configuring NodeSource repository" bash "$tmp"
1489
+ run_quiet_step "Installing Node.js" dnf install -y -q nodejs
1490
+ else
1491
+ run_quiet_step "Configuring NodeSource repository" sudo bash "$tmp"
1492
+ run_quiet_step "Installing Node.js" sudo dnf install -y -q nodejs
1493
+ fi
1494
+ elif command -v yum &> /dev/null; then
1495
+ local tmp
1496
+ tmp="$(mktempfile)"
1497
+ download_file "https://rpm.nodesource.com/setup_24.x" "$tmp"
1498
+ if is_root; then
1499
+ run_quiet_step "Configuring NodeSource repository" bash "$tmp"
1500
+ run_quiet_step "Installing Node.js" yum install -y -q nodejs
1501
+ else
1502
+ run_quiet_step "Configuring NodeSource repository" sudo bash "$tmp"
1503
+ run_quiet_step "Installing Node.js" sudo yum install -y -q nodejs
1504
+ fi
1505
+ else
1506
+ ui_error "Could not detect package manager"
1507
+ echo "Please install Node.js 24+ manually: https://nodejs.org"
1508
+ exit 1
1509
+ fi
1510
+
1511
+ ui_success "Node.js v24 installed"
1512
+ print_active_node_paths || true
1513
+ fi
1514
+ }
1515
+
1516
+ # Check Git
1517
+ check_git() {
1518
+ if command -v git &> /dev/null; then
1519
+ ui_success "Git already installed"
1520
+ return 0
1521
+ fi
1522
+ ui_info "Git not found, installing it now"
1523
+ return 1
1524
+ }
1525
+
1526
+ is_root() {
1527
+ [[ "$(id -u)" -eq 0 ]]
1528
+ }
1529
+
1530
+ # Run a command with sudo only if not already root
1531
+ maybe_sudo() {
1532
+ if is_root; then
1533
+ # Skip -E flag when root (env is already preserved)
1534
+ if [[ "${1:-}" == "-E" ]]; then
1535
+ shift
1536
+ fi
1537
+ "$@"
1538
+ else
1539
+ sudo "$@"
1540
+ fi
1541
+ }
1542
+
1543
+ require_sudo() {
1544
+ if [[ "$OS" != "linux" ]]; then
1545
+ return 0
1546
+ fi
1547
+ if is_root; then
1548
+ return 0
1549
+ fi
1550
+ if command -v sudo &> /dev/null; then
1551
+ if ! sudo -n true >/dev/null 2>&1; then
1552
+ ui_info "Administrator privileges required; enter your password"
1553
+ sudo -v
1554
+ fi
1555
+ return 0
1556
+ fi
1557
+ ui_error "sudo is required for system installs on Linux"
1558
+ echo " Install sudo or re-run as root."
1559
+ exit 1
1560
+ }
1561
+
1562
+ install_git() {
1563
+ if [[ "$OS" == "macos" ]]; then
1564
+ run_quiet_step "Installing Git" brew install git
1565
+ elif [[ "$OS" == "linux" ]]; then
1566
+ require_sudo
1567
+ if command -v apt-get &> /dev/null; then
1568
+ if is_root; then
1569
+ run_quiet_step "Updating package index" apt-get update -qq
1570
+ run_quiet_step "Installing Git" apt-get install -y -qq git
1571
+ else
1572
+ run_quiet_step "Updating package index" sudo apt-get update -qq
1573
+ run_quiet_step "Installing Git" sudo apt-get install -y -qq git
1574
+ fi
1575
+ elif command -v pacman &> /dev/null || is_arch_linux; then
1576
+ if is_root; then
1577
+ run_quiet_step "Installing Git" pacman -Sy --noconfirm git
1578
+ else
1579
+ run_quiet_step "Installing Git" sudo pacman -Sy --noconfirm git
1580
+ fi
1581
+ elif command -v dnf &> /dev/null; then
1582
+ if is_root; then
1583
+ run_quiet_step "Installing Git" dnf install -y -q git
1584
+ else
1585
+ run_quiet_step "Installing Git" sudo dnf install -y -q git
1586
+ fi
1587
+ elif command -v yum &> /dev/null; then
1588
+ if is_root; then
1589
+ run_quiet_step "Installing Git" yum install -y -q git
1590
+ else
1591
+ run_quiet_step "Installing Git" sudo yum install -y -q git
1592
+ fi
1593
+ else
1594
+ ui_error "Could not detect package manager for Git"
1595
+ exit 1
1596
+ fi
1597
+ fi
1598
+ ui_success "Git installed"
1599
+ }
1600
+
1601
+ # Fix npm permissions for global installs (Linux)
1602
+ fix_npm_permissions() {
1603
+ if [[ "$OS" != "linux" ]]; then
1604
+ return 0
1605
+ fi
1606
+
1607
+ local npm_prefix
1608
+ npm_prefix="$(npm config get prefix 2>/dev/null || true)"
1609
+ if [[ -z "$npm_prefix" ]]; then
1610
+ return 0
1611
+ fi
1612
+
1613
+ if [[ -w "$npm_prefix" || -w "$npm_prefix/lib" ]]; then
1614
+ return 0
1615
+ fi
1616
+
1617
+ ui_info "Configuring npm for user-local installs"
1618
+ mkdir -p "$HOME/.npm-global"
1619
+ npm config set prefix "$HOME/.npm-global"
1620
+
1621
+ # shellcheck disable=SC2016
1622
+ local path_line='export PATH="$HOME/.npm-global/bin:$PATH"'
1623
+ for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
1624
+ if [[ -f "$rc" ]] && ! grep -q ".npm-global" "$rc"; then
1625
+ echo "$path_line" >> "$rc"
1626
+ fi
1627
+ done
1628
+
1629
+ export PATH="$HOME/.npm-global/bin:$PATH"
1630
+ ui_success "npm configured for user installs"
1631
+ }
1632
+
1633
+ ensure_openclaw_bin_link() {
1634
+ local npm_root=""
1635
+ npm_root="$(npm root -g 2>/dev/null || true)"
1636
+ if [[ -z "$npm_root" || ! -d "$npm_root/openclaw" ]]; then
1637
+ return 1
1638
+ fi
1639
+ local npm_bin=""
1640
+ npm_bin="$(npm_global_bin_dir || true)"
1641
+ if [[ -z "$npm_bin" ]]; then
1642
+ return 1
1643
+ fi
1644
+ mkdir -p "$npm_bin"
1645
+ if [[ ! -x "${npm_bin}/openclaw" ]]; then
1646
+ ln -sf "$npm_root/openclaw/dist/entry.js" "${npm_bin}/openclaw"
1647
+ ui_info "Created openclaw bin link at ${npm_bin}/openclaw"
1648
+ fi
1649
+ return 0
1650
+ }
1651
+
1652
+ # Check for existing OpenClaw installation
1653
+ check_existing_openclaw() {
1654
+ if [[ -n "$(type -P openclaw 2>/dev/null || true)" ]]; then
1655
+ ui_info "Existing OpenClaw installation detected, upgrading"
1656
+ return 0
1657
+ fi
1658
+ return 1
1659
+ }
1660
+
1661
+ set_pnpm_cmd() {
1662
+ PNPM_CMD=("$@")
1663
+ }
1664
+
1665
+ pnpm_cmd_pretty() {
1666
+ if [[ ${#PNPM_CMD[@]} -eq 0 ]]; then
1667
+ echo ""
1668
+ return 1
1669
+ fi
1670
+ printf '%s' "${PNPM_CMD[*]}"
1671
+ return 0
1672
+ }
1673
+
1674
+ pnpm_cmd_is_ready() {
1675
+ if [[ ${#PNPM_CMD[@]} -eq 0 ]]; then
1676
+ return 1
1677
+ fi
1678
+ "${PNPM_CMD[@]}" --version >/dev/null 2>&1
1679
+ }
1680
+
1681
+ detect_pnpm_cmd() {
1682
+ if command -v pnpm &> /dev/null; then
1683
+ set_pnpm_cmd pnpm
1684
+ return 0
1685
+ fi
1686
+ if command -v corepack &> /dev/null; then
1687
+ if corepack pnpm --version >/dev/null 2>&1; then
1688
+ set_pnpm_cmd corepack pnpm
1689
+ return 0
1690
+ fi
1691
+ fi
1692
+ return 1
1693
+ }
1694
+
1695
+ ensure_pnpm() {
1696
+ if detect_pnpm_cmd && pnpm_cmd_is_ready; then
1697
+ ui_success "pnpm ready ($(pnpm_cmd_pretty))"
1698
+ return 0
1699
+ fi
1700
+
1701
+ if command -v corepack &> /dev/null; then
1702
+ ui_info "Configuring pnpm via Corepack"
1703
+ corepack enable >/dev/null 2>&1 || true
1704
+ if ! run_quiet_step "Activating pnpm" corepack prepare pnpm@10 --activate; then
1705
+ ui_warn "Corepack pnpm activation failed; falling back"
1706
+ fi
1707
+ refresh_shell_command_cache
1708
+ if detect_pnpm_cmd && pnpm_cmd_is_ready; then
1709
+ if [[ "${PNPM_CMD[*]}" == "corepack pnpm" ]]; then
1710
+ ui_warn "pnpm shim not on PATH; using corepack pnpm fallback"
1711
+ fi
1712
+ ui_success "pnpm ready ($(pnpm_cmd_pretty))"
1713
+ return 0
1714
+ fi
1715
+ fi
1716
+
1717
+ ui_info "Installing pnpm via npm"
1718
+ fix_npm_permissions
1719
+ run_quiet_step "Installing pnpm" npm install -g pnpm@10
1720
+ refresh_shell_command_cache
1721
+ if detect_pnpm_cmd && pnpm_cmd_is_ready; then
1722
+ ui_success "pnpm ready ($(pnpm_cmd_pretty))"
1723
+ return 0
1724
+ fi
1725
+
1726
+ ui_error "pnpm installation failed"
1727
+ return 1
1728
+ }
1729
+
1730
+ ensure_pnpm_binary_for_scripts() {
1731
+ if command -v pnpm >/dev/null 2>&1; then
1732
+ return 0
1733
+ fi
1734
+
1735
+ if command -v corepack >/dev/null 2>&1; then
1736
+ ui_info "Ensuring pnpm command is available"
1737
+ corepack enable >/dev/null 2>&1 || true
1738
+ corepack prepare pnpm@10 --activate >/dev/null 2>&1 || true
1739
+ refresh_shell_command_cache
1740
+ if command -v pnpm >/dev/null 2>&1; then
1741
+ ui_success "pnpm command enabled via Corepack"
1742
+ return 0
1743
+ fi
1744
+ fi
1745
+
1746
+ if [[ "${PNPM_CMD[*]}" == "corepack pnpm" ]] && command -v corepack >/dev/null 2>&1; then
1747
+ ensure_user_local_bin_on_path
1748
+ local user_pnpm="${HOME}/.local/bin/pnpm"
1749
+ cat >"${user_pnpm}" <<'EOF'
1750
+ #!/usr/bin/env bash
1751
+ set -euo pipefail
1752
+ exec corepack pnpm "$@"
1753
+ EOF
1754
+ chmod +x "${user_pnpm}"
1755
+ refresh_shell_command_cache
1756
+
1757
+ if command -v pnpm >/dev/null 2>&1; then
1758
+ ui_warn "pnpm shim not on PATH; installed user-local wrapper at ${user_pnpm}"
1759
+ return 0
1760
+ fi
1761
+ fi
1762
+
1763
+ ui_error "pnpm command not available on PATH"
1764
+ ui_info "Install pnpm globally (npm install -g pnpm@10) and retry"
1765
+ return 1
1766
+ }
1767
+
1768
+ run_pnpm() {
1769
+ if ! pnpm_cmd_is_ready; then
1770
+ ensure_pnpm
1771
+ fi
1772
+ "${PNPM_CMD[@]}" "$@"
1773
+ }
1774
+
1775
+ ensure_user_local_bin_on_path() {
1776
+ local target="$HOME/.local/bin"
1777
+ mkdir -p "$target"
1778
+
1779
+ export PATH="$target:$PATH"
1780
+
1781
+ # shellcheck disable=SC2016
1782
+ local path_line='export PATH="$HOME/.local/bin:$PATH"'
1783
+ for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
1784
+ if [[ -f "$rc" ]] && ! grep -q ".local/bin" "$rc"; then
1785
+ echo "$path_line" >> "$rc"
1786
+ fi
1787
+ done
1788
+ }
1789
+
1790
+ npm_global_bin_dir() {
1791
+ local prefix=""
1792
+ prefix="$(npm prefix -g 2>/dev/null || true)"
1793
+ if [[ -n "$prefix" ]]; then
1794
+ if [[ "$prefix" == /* ]]; then
1795
+ echo "${prefix%/}/bin"
1796
+ return 0
1797
+ fi
1798
+ fi
1799
+
1800
+ prefix="$(npm config get prefix 2>/dev/null || true)"
1801
+ if [[ -n "$prefix" && "$prefix" != "undefined" && "$prefix" != "null" ]]; then
1802
+ if [[ "$prefix" == /* ]]; then
1803
+ echo "${prefix%/}/bin"
1804
+ return 0
1805
+ fi
1806
+ fi
1807
+
1808
+ echo ""
1809
+ return 1
1810
+ }
1811
+
1812
+ refresh_shell_command_cache() {
1813
+ hash -r 2>/dev/null || true
1814
+ }
1815
+
1816
+ path_has_dir() {
1817
+ local path="$1"
1818
+ local dir="${2%/}"
1819
+ if [[ -z "$dir" ]]; then
1820
+ return 1
1821
+ fi
1822
+ case ":${path}:" in
1823
+ *":${dir}:"*) return 0 ;;
1824
+ *) return 1 ;;
1825
+ esac
1826
+ }
1827
+
1828
+ warn_shell_path_missing_dir() {
1829
+ local dir="${1%/}"
1830
+ local label="$2"
1831
+ if [[ -z "$dir" ]]; then
1832
+ return 0
1833
+ fi
1834
+ if path_has_dir "$ORIGINAL_PATH" "$dir"; then
1835
+ return 0
1836
+ fi
1837
+
1838
+ echo ""
1839
+ ui_warn "PATH missing ${label}: ${dir}"
1840
+ echo " This can make openclaw show as \"command not found\" in new terminals."
1841
+ echo " Fix (zsh: ~/.zshrc, bash: ~/.bashrc):"
1842
+ echo " export PATH=\"${dir}:\$PATH\""
1843
+ }
1844
+
1845
+ ensure_npm_global_bin_on_path() {
1846
+ local bin_dir=""
1847
+ bin_dir="$(npm_global_bin_dir || true)"
1848
+ if [[ -n "$bin_dir" ]]; then
1849
+ export PATH="${bin_dir}:$PATH"
1850
+ fi
1851
+ }
1852
+
1853
+ maybe_nodenv_rehash() {
1854
+ if command -v nodenv &> /dev/null; then
1855
+ nodenv rehash >/dev/null 2>&1 || true
1856
+ fi
1857
+ }
1858
+
1859
+ warn_openclaw_not_found() {
1860
+ ui_warn "Installed, but openclaw is not discoverable on PATH in this shell"
1861
+ echo " Try: hash -r (bash) or rehash (zsh), then retry."
1862
+ local t=""
1863
+ t="$(type -t openclaw 2>/dev/null || true)"
1864
+ if [[ "$t" == "alias" || "$t" == "function" ]]; then
1865
+ ui_warn "Found a shell ${t} named openclaw; it may shadow the real binary"
1866
+ fi
1867
+ if command -v nodenv &> /dev/null; then
1868
+ echo -e "Using nodenv? Run: ${INFO}nodenv rehash${NC}"
1869
+ fi
1870
+
1871
+ local npm_prefix=""
1872
+ npm_prefix="$(npm prefix -g 2>/dev/null || true)"
1873
+ local npm_bin=""
1874
+ npm_bin="$(npm_global_bin_dir 2>/dev/null || true)"
1875
+ if [[ -n "$npm_prefix" ]]; then
1876
+ echo -e "npm prefix -g: ${INFO}${npm_prefix}${NC}"
1877
+ fi
1878
+ if [[ -n "$npm_bin" ]]; then
1879
+ echo -e "npm bin -g: ${INFO}${npm_bin}${NC}"
1880
+ echo -e "If needed: ${INFO}export PATH=\"${npm_bin}:\\$PATH\"${NC}"
1881
+ fi
1882
+ }
1883
+
1884
+ resolve_openclaw_bin() {
1885
+ refresh_shell_command_cache
1886
+ local resolved=""
1887
+ resolved="$(type -P openclaw 2>/dev/null || true)"
1888
+ if [[ -n "$resolved" && -x "$resolved" ]]; then
1889
+ echo "$resolved"
1890
+ return 0
1891
+ fi
1892
+
1893
+ ensure_npm_global_bin_on_path
1894
+ refresh_shell_command_cache
1895
+ resolved="$(type -P openclaw 2>/dev/null || true)"
1896
+ if [[ -n "$resolved" && -x "$resolved" ]]; then
1897
+ echo "$resolved"
1898
+ return 0
1899
+ fi
1900
+
1901
+ local npm_bin=""
1902
+ npm_bin="$(npm_global_bin_dir || true)"
1903
+ if [[ -n "$npm_bin" && -x "${npm_bin}/openclaw" ]]; then
1904
+ echo "${npm_bin}/openclaw"
1905
+ return 0
1906
+ fi
1907
+
1908
+ maybe_nodenv_rehash
1909
+ refresh_shell_command_cache
1910
+ resolved="$(type -P openclaw 2>/dev/null || true)"
1911
+ if [[ -n "$resolved" && -x "$resolved" ]]; then
1912
+ echo "$resolved"
1913
+ return 0
1914
+ fi
1915
+
1916
+ if [[ -n "$npm_bin" && -x "${npm_bin}/openclaw" ]]; then
1917
+ echo "${npm_bin}/openclaw"
1918
+ return 0
1919
+ fi
1920
+
1921
+ echo ""
1922
+ return 1
1923
+ }
1924
+
1925
+ install_openclaw_from_git() {
1926
+ local repo_dir="$1"
1927
+ local repo_url="https://github.com/ExpansionX/WednesdayAI-core.git"
1928
+
1929
+ if [[ -d "$repo_dir/.git" ]]; then
1930
+ ui_info "Installing OpenClaw from git checkout: ${repo_dir}"
1931
+ else
1932
+ ui_info "Installing OpenClaw from GitHub (${repo_url})"
1933
+ fi
1934
+
1935
+ if ! check_git; then
1936
+ install_git
1937
+ fi
1938
+
1939
+ ensure_pnpm
1940
+ ensure_pnpm_binary_for_scripts
1941
+
1942
+ if [[ ! -d "$repo_dir" ]]; then
1943
+ run_quiet_step "Cloning OpenClaw" git clone "$repo_url" "$repo_dir"
1944
+ fi
1945
+
1946
+ if [[ "$GIT_UPDATE" == "1" ]]; then
1947
+ if [[ -z "$(git -C "$repo_dir" status --porcelain 2>/dev/null || true)" ]]; then
1948
+ run_quiet_step "Updating repository" git -C "$repo_dir" pull --rebase || true
1949
+ else
1950
+ ui_info "Repo has local changes; skipping git pull"
1951
+ fi
1952
+ fi
1953
+
1954
+ cleanup_legacy_submodules "$repo_dir"
1955
+
1956
+ SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" run_quiet_step "Installing dependencies" run_pnpm -C "$repo_dir" install
1957
+
1958
+ if ! run_quiet_step "Building UI" run_pnpm -C "$repo_dir" ui:build; then
1959
+ ui_warn "UI build failed; continuing (CLI may still work)"
1960
+ fi
1961
+ run_quiet_step "Building OpenClaw" run_pnpm -C "$repo_dir" build
1962
+
1963
+ ensure_user_local_bin_on_path
1964
+
1965
+ cat > "$HOME/.local/bin/openclaw" <<EOF
1966
+ #!/usr/bin/env bash
1967
+ set -euo pipefail
1968
+ exec node "${repo_dir}/dist/entry.js" "\$@"
1969
+ EOF
1970
+ chmod +x "$HOME/.local/bin/openclaw"
1971
+ ui_success "OpenClaw wrapper installed to \$HOME/.local/bin/openclaw"
1972
+ ui_info "This checkout uses pnpm — run pnpm install (or corepack pnpm install) for deps"
1973
+ }
1974
+
1975
+ # Install OpenClaw
1976
+ resolve_beta_version() {
1977
+ local beta=""
1978
+ beta="$(npm view wednesdayai dist-tags.beta 2>/dev/null || true)"
1979
+ if [[ -z "$beta" || "$beta" == "undefined" || "$beta" == "null" ]]; then
1980
+ return 1
1981
+ fi
1982
+ echo "$beta"
1983
+ }
1984
+
1985
+ install_openclaw() {
1986
+ local package_name="wednesdayai"
1987
+ if [[ "$USE_BETA" == "1" ]]; then
1988
+ local beta_version=""
1989
+ beta_version="$(resolve_beta_version || true)"
1990
+ if [[ -n "$beta_version" ]]; then
1991
+ OPENCLAW_VERSION="$beta_version"
1992
+ ui_info "Beta tag detected (${beta_version})"
1993
+ else
1994
+ OPENCLAW_VERSION="latest"
1995
+ ui_info "No beta tag found; using latest"
1996
+ fi
1997
+ fi
1998
+
1999
+ if [[ -z "${OPENCLAW_VERSION}" ]]; then
2000
+ OPENCLAW_VERSION="latest"
2001
+ fi
2002
+
2003
+ local resolved_version=""
2004
+ resolved_version="$(npm view "${package_name}@${OPENCLAW_VERSION}" version 2>/dev/null || true)"
2005
+ if [[ -n "$resolved_version" ]]; then
2006
+ ui_info "Installing OpenClaw v${resolved_version}"
2007
+ else
2008
+ ui_info "Installing OpenClaw (${OPENCLAW_VERSION})"
2009
+ fi
2010
+ local install_spec=""
2011
+ if [[ "${OPENCLAW_VERSION}" == "latest" ]]; then
2012
+ install_spec="${package_name}@latest"
2013
+ else
2014
+ install_spec="${package_name}@${OPENCLAW_VERSION}"
2015
+ fi
2016
+
2017
+ if ! install_openclaw_npm "${install_spec}"; then
2018
+ ui_warn "npm install failed; retrying"
2019
+ cleanup_npm_openclaw_paths
2020
+ install_openclaw_npm "${install_spec}"
2021
+ fi
2022
+
2023
+ ensure_openclaw_bin_link || true
2024
+
2025
+ ui_success "OpenClaw installed"
2026
+ }
2027
+
2028
+ # Run doctor for migrations (safe, non-interactive)
2029
+ run_doctor() {
2030
+ ui_info "Running doctor to migrate settings"
2031
+ local claw="${OPENCLAW_BIN:-}"
2032
+ if [[ -z "$claw" ]]; then
2033
+ claw="$(resolve_openclaw_bin || true)"
2034
+ fi
2035
+ if [[ -z "$claw" ]]; then
2036
+ ui_info "Skipping doctor (openclaw not on PATH yet)"
2037
+ warn_openclaw_not_found
2038
+ return 0
2039
+ fi
2040
+ run_quiet_step "Running doctor" "$claw" doctor --non-interactive || true
2041
+ ui_success "Doctor complete"
2042
+ }
2043
+
2044
+ maybe_open_dashboard() {
2045
+ local claw="${OPENCLAW_BIN:-}"
2046
+ if [[ -z "$claw" ]]; then
2047
+ claw="$(resolve_openclaw_bin || true)"
2048
+ fi
2049
+ if [[ -z "$claw" ]]; then
2050
+ return 0
2051
+ fi
2052
+ if ! "$claw" dashboard --help >/dev/null 2>&1; then
2053
+ return 0
2054
+ fi
2055
+ "$claw" dashboard || true
2056
+ }
2057
+
2058
+ resolve_workspace_dir() {
2059
+ local profile="${OPENCLAW_PROFILE:-default}"
2060
+ if [[ "${profile}" != "default" ]]; then
2061
+ echo "${HOME}/.openclaw/workspace-${profile}"
2062
+ else
2063
+ echo "${HOME}/.openclaw/workspace"
2064
+ fi
2065
+ }
2066
+
2067
+ run_bootstrap_onboarding_if_needed() {
2068
+ if [[ "${NO_ONBOARD}" == "1" ]]; then
2069
+ return
2070
+ fi
2071
+
2072
+ local config_path="${OPENCLAW_CONFIG_PATH:-$HOME/.openclaw/openclaw.json}"
2073
+ if [[ -f "${config_path}" || -f "$HOME/.clawdbot/clawdbot.json" || -f "$HOME/.moltbot/moltbot.json" || -f "$HOME/.moldbot/moldbot.json" ]]; then
2074
+ return
2075
+ fi
2076
+
2077
+ local workspace
2078
+ workspace="$(resolve_workspace_dir)"
2079
+ local bootstrap="${workspace}/BOOTSTRAP.md"
2080
+
2081
+ if [[ ! -f "${bootstrap}" ]]; then
2082
+ return
2083
+ fi
2084
+
2085
+ if [[ ! -r /dev/tty || ! -w /dev/tty ]]; then
2086
+ ui_info "BOOTSTRAP.md found but no TTY; run openclaw onboard to finish setup"
2087
+ return
2088
+ fi
2089
+
2090
+ ui_info "BOOTSTRAP.md found; starting onboarding"
2091
+ local claw="${OPENCLAW_BIN:-}"
2092
+ if [[ -z "$claw" ]]; then
2093
+ claw="$(resolve_openclaw_bin || true)"
2094
+ fi
2095
+ if [[ -z "$claw" ]]; then
2096
+ ui_info "BOOTSTRAP.md found but openclaw not on PATH; skipping onboarding"
2097
+ warn_openclaw_not_found
2098
+ return
2099
+ fi
2100
+
2101
+ "$claw" onboard || {
2102
+ ui_error "Onboarding failed; run openclaw onboard to retry"
2103
+ return
2104
+ }
2105
+ }
2106
+
2107
+ resolve_openclaw_version() {
2108
+ local version=""
2109
+ local claw="${OPENCLAW_BIN:-}"
2110
+ if [[ -z "$claw" ]] && command -v openclaw &> /dev/null; then
2111
+ claw="$(command -v openclaw)"
2112
+ fi
2113
+ if [[ -n "$claw" ]]; then
2114
+ version=$("$claw" --version 2>/dev/null | head -n 1 | tr -d '\r')
2115
+ fi
2116
+ if [[ -z "$version" ]]; then
2117
+ local npm_root=""
2118
+ npm_root=$(npm root -g 2>/dev/null || true)
2119
+ if [[ -n "$npm_root" && -f "$npm_root/openclaw/package.json" ]]; then
2120
+ version=$(node -e "console.log(require('${npm_root}/openclaw/package.json').version)" 2>/dev/null || true)
2121
+ fi
2122
+ fi
2123
+ echo "$version"
2124
+ }
2125
+
2126
+ is_gateway_daemon_loaded() {
2127
+ local claw="$1"
2128
+ if [[ -z "$claw" ]]; then
2129
+ return 1
2130
+ fi
2131
+
2132
+ local status_json=""
2133
+ status_json="$("$claw" daemon status --json 2>/dev/null || true)"
2134
+ if [[ -z "$status_json" ]]; then
2135
+ return 1
2136
+ fi
2137
+
2138
+ printf '%s' "$status_json" | node -e '
2139
+ const fs = require("fs");
2140
+ const raw = fs.readFileSync(0, "utf8").trim();
2141
+ if (!raw) process.exit(1);
2142
+ try {
2143
+ const data = JSON.parse(raw);
2144
+ process.exit(data?.service?.loaded ? 0 : 1);
2145
+ } catch {
2146
+ process.exit(1);
2147
+ }
2148
+ ' >/dev/null 2>&1
2149
+ }
2150
+
2151
+ refresh_gateway_service_if_loaded() {
2152
+ local claw="${OPENCLAW_BIN:-}"
2153
+ if [[ -z "$claw" ]]; then
2154
+ claw="$(resolve_openclaw_bin || true)"
2155
+ fi
2156
+ if [[ -z "$claw" ]]; then
2157
+ return 0
2158
+ fi
2159
+
2160
+ if ! is_gateway_daemon_loaded "$claw"; then
2161
+ return 0
2162
+ fi
2163
+
2164
+ ui_info "Refreshing loaded gateway service"
2165
+ if run_quiet_step "Refreshing gateway service" "$claw" gateway install --force; then
2166
+ ui_success "Gateway service metadata refreshed"
2167
+ else
2168
+ ui_warn "Gateway service refresh failed; continuing"
2169
+ return 0
2170
+ fi
2171
+
2172
+ if run_quiet_step "Restarting gateway service" "$claw" gateway restart; then
2173
+ ui_success "Gateway service restarted"
2174
+ else
2175
+ ui_warn "Gateway service restart failed; continuing"
2176
+ return 0
2177
+ fi
2178
+
2179
+ run_quiet_step "Probing gateway service" "$claw" gateway status --probe --deep || true
2180
+ }
2181
+
2182
+ # Main installation flow
2183
+ main() {
2184
+ if [[ "$HELP" == "1" ]]; then
2185
+ print_usage
2186
+ return 0
2187
+ fi
2188
+
2189
+ bootstrap_gum_temp || true
2190
+ print_installer_banner
2191
+ print_gum_status
2192
+ detect_os_or_die
2193
+
2194
+ local detected_checkout=""
2195
+ detected_checkout="$(detect_openclaw_checkout "$PWD" || true)"
2196
+
2197
+ if [[ -z "$INSTALL_METHOD" && -n "$detected_checkout" ]]; then
2198
+ if ! is_promptable; then
2199
+ ui_info "Found OpenClaw checkout but no TTY; defaulting to npm install"
2200
+ INSTALL_METHOD="npm"
2201
+ else
2202
+ local selected_method=""
2203
+ selected_method="$(choose_install_method_interactive "$detected_checkout" || true)"
2204
+ case "$selected_method" in
2205
+ git|npm)
2206
+ INSTALL_METHOD="$selected_method"
2207
+ ;;
2208
+ *)
2209
+ ui_error "no install method selected"
2210
+ echo "Re-run with: --install-method git|npm (or set OPENCLAW_INSTALL_METHOD)."
2211
+ exit 2
2212
+ ;;
2213
+ esac
2214
+ fi
2215
+ fi
2216
+
2217
+ if [[ -z "$INSTALL_METHOD" ]]; then
2218
+ INSTALL_METHOD="npm"
2219
+ fi
2220
+
2221
+ if [[ "$INSTALL_METHOD" != "npm" && "$INSTALL_METHOD" != "git" ]]; then
2222
+ ui_error "invalid --install-method: ${INSTALL_METHOD}"
2223
+ echo "Use: --install-method npm|git"
2224
+ exit 2
2225
+ fi
2226
+
2227
+ show_install_plan "$detected_checkout"
2228
+
2229
+ if [[ "$DRY_RUN" == "1" ]]; then
2230
+ ui_success "Dry run complete (no changes made)"
2231
+ return 0
2232
+ fi
2233
+
2234
+ # Check for existing installation
2235
+ local is_upgrade=false
2236
+ if check_existing_openclaw; then
2237
+ is_upgrade=true
2238
+ fi
2239
+ local should_open_dashboard=false
2240
+ local skip_onboard=false
2241
+
2242
+ ui_stage "Preparing environment"
2243
+
2244
+ # Step 1: Homebrew (macOS only)
2245
+ install_homebrew
2246
+
2247
+ # Step 2: Node.js
2248
+ if ! check_node; then
2249
+ install_node
2250
+ fi
2251
+ if ! ensure_node24_active_shell; then
2252
+ exit 1
2253
+ fi
2254
+
2255
+ ui_stage "Installing OpenClaw"
2256
+
2257
+ local final_git_dir=""
2258
+ if [[ "$INSTALL_METHOD" == "git" ]]; then
2259
+ # Clean up npm global install if switching to git
2260
+ if npm list -g openclaw &>/dev/null; then
2261
+ ui_info "Removing npm global install (switching to git)"
2262
+ npm uninstall -g openclaw 2>/dev/null || true
2263
+ ui_success "npm global install removed"
2264
+ fi
2265
+
2266
+ local repo_dir="$GIT_DIR"
2267
+ if [[ -n "$detected_checkout" ]]; then
2268
+ repo_dir="$detected_checkout"
2269
+ fi
2270
+ final_git_dir="$repo_dir"
2271
+ install_openclaw_from_git "$repo_dir"
2272
+ else
2273
+ # Clean up git wrapper if switching to npm
2274
+ if [[ -x "$HOME/.local/bin/openclaw" ]]; then
2275
+ ui_info "Removing git wrapper (switching to npm)"
2276
+ rm -f "$HOME/.local/bin/openclaw"
2277
+ ui_success "git wrapper removed"
2278
+ fi
2279
+
2280
+ # Step 3: Git (required for npm installs that may fetch from git or apply patches)
2281
+ if ! check_git; then
2282
+ install_git
2283
+ fi
2284
+
2285
+ # Step 4: npm permissions (Linux)
2286
+ fix_npm_permissions
2287
+
2288
+ # Step 5: OpenClaw
2289
+ install_openclaw
2290
+ fi
2291
+
2292
+ ui_stage "Finalizing setup"
2293
+
2294
+ OPENCLAW_BIN="$(resolve_openclaw_bin || true)"
2295
+
2296
+ # PATH warning: installs can succeed while the user's login shell still lacks npm's global bin dir.
2297
+ local npm_bin=""
2298
+ npm_bin="$(npm_global_bin_dir || true)"
2299
+ if [[ "$INSTALL_METHOD" == "npm" ]]; then
2300
+ warn_shell_path_missing_dir "$npm_bin" "npm global bin dir"
2301
+ fi
2302
+ if [[ "$INSTALL_METHOD" == "git" ]]; then
2303
+ if [[ -x "$HOME/.local/bin/openclaw" ]]; then
2304
+ warn_shell_path_missing_dir "$HOME/.local/bin" "user-local bin dir (~/.local/bin)"
2305
+ fi
2306
+ fi
2307
+
2308
+ refresh_gateway_service_if_loaded
2309
+
2310
+ # Step 6: Run doctor for migrations on upgrades and git installs
2311
+ local run_doctor_after=false
2312
+ if [[ "$is_upgrade" == "true" || "$INSTALL_METHOD" == "git" ]]; then
2313
+ run_doctor_after=true
2314
+ fi
2315
+ if [[ "$run_doctor_after" == "true" ]]; then
2316
+ run_doctor
2317
+ should_open_dashboard=true
2318
+ fi
2319
+
2320
+ # Step 7: If BOOTSTRAP.md is still present in the workspace, resume onboarding
2321
+ run_bootstrap_onboarding_if_needed
2322
+
2323
+ local installed_version
2324
+ installed_version=$(resolve_openclaw_version)
2325
+
2326
+ echo ""
2327
+ if [[ -n "$installed_version" ]]; then
2328
+ ui_celebrate "🦞 OpenClaw installed successfully (${installed_version})!"
2329
+ else
2330
+ ui_celebrate "🦞 OpenClaw installed successfully!"
2331
+ fi
2332
+ if [[ "$is_upgrade" == "true" ]]; then
2333
+ local update_messages=(
2334
+ "Leveled up! New skills unlocked. You're welcome."
2335
+ "Fresh code, same lobster. Miss me?"
2336
+ "Back and better. Did you even notice I was gone?"
2337
+ "Update complete. I learned some new tricks while I was out."
2338
+ "Upgraded! Now with 23% more sass."
2339
+ "I've evolved. Try to keep up. 🦞"
2340
+ "New version, who dis? Oh right, still me but shinier."
2341
+ "Patched, polished, and ready to pinch. Let's go."
2342
+ "The lobster has molted. Harder shell, sharper claws."
2343
+ "Update done! Check the changelog or just trust me, it's good."
2344
+ "Reborn from the boiling waters of npm. Stronger now."
2345
+ "I went away and came back smarter. You should try it sometime."
2346
+ "Update complete. The bugs feared me, so they left."
2347
+ "New version installed. Old version sends its regards."
2348
+ "Firmware fresh. Brain wrinkles: increased."
2349
+ "I've seen things you wouldn't believe. Anyway, I'm updated."
2350
+ "Back online. The changelog is long but our friendship is longer."
2351
+ "Upgraded! Peter fixed stuff. Blame him if it breaks."
2352
+ "Molting complete. Please don't look at my soft shell phase."
2353
+ "Version bump! Same chaos energy, fewer crashes (probably)."
2354
+ )
2355
+ local update_message
2356
+ update_message="${update_messages[RANDOM % ${#update_messages[@]}]}"
2357
+ echo -e "${MUTED}${update_message}${NC}"
2358
+ else
2359
+ local completion_messages=(
2360
+ "Ahh nice, I like it here. Got any snacks? "
2361
+ "Home sweet home. Don't worry, I won't rearrange the furniture."
2362
+ "I'm in. Let's cause some responsible chaos."
2363
+ "Installation complete. Your productivity is about to get weird."
2364
+ "Settled in. Time to automate your life whether you're ready or not."
2365
+ "Cozy. I've already read your calendar. We need to talk."
2366
+ "Finally unpacked. Now point me at your problems."
2367
+ "cracks claws Alright, what are we building?"
2368
+ "The lobster has landed. Your terminal will never be the same."
2369
+ "All done! I promise to only judge your code a little bit."
2370
+ )
2371
+ local completion_message
2372
+ completion_message="${completion_messages[RANDOM % ${#completion_messages[@]}]}"
2373
+ echo -e "${MUTED}${completion_message}${NC}"
2374
+ fi
2375
+ echo ""
2376
+
2377
+ if [[ "$INSTALL_METHOD" == "git" && -n "$final_git_dir" ]]; then
2378
+ ui_section "Source install details"
2379
+ ui_kv "Checkout" "$final_git_dir"
2380
+ ui_kv "Wrapper" "$HOME/.local/bin/openclaw"
2381
+ ui_kv "Update command" "openclaw update --restart"
2382
+ ui_kv "Switch to npm" "curl -fsSL --proto '=https' --tlsv1.2 https://wednesdayai.dev/install.sh | bash -s -- --install-method npm"
2383
+ elif [[ "$is_upgrade" == "true" ]]; then
2384
+ ui_info "Upgrade complete"
2385
+ if [[ -r /dev/tty && -w /dev/tty ]]; then
2386
+ local claw="${OPENCLAW_BIN:-}"
2387
+ if [[ -z "$claw" ]]; then
2388
+ claw="$(resolve_openclaw_bin || true)"
2389
+ fi
2390
+ if [[ -z "$claw" ]]; then
2391
+ ui_info "Skipping doctor (openclaw not on PATH yet)"
2392
+ warn_openclaw_not_found
2393
+ return 0
2394
+ fi
2395
+ local -a doctor_args=()
2396
+ if [[ "$NO_ONBOARD" == "1" ]]; then
2397
+ if "$claw" doctor --help 2>/dev/null | grep -q -- "--non-interactive"; then
2398
+ doctor_args+=("--non-interactive")
2399
+ fi
2400
+ fi
2401
+ ui_info "Running openclaw doctor"
2402
+ local doctor_ok=0
2403
+ if (( ${#doctor_args[@]} )); then
2404
+ OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor "${doctor_args[@]}" </dev/tty && doctor_ok=1
2405
+ else
2406
+ OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor </dev/tty && doctor_ok=1
2407
+ fi
2408
+ if (( doctor_ok )); then
2409
+ ui_info "Updating plugins"
2410
+ OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" plugins update --all || true
2411
+ else
2412
+ ui_warn "Doctor failed; skipping plugin updates"
2413
+ fi
2414
+ else
2415
+ ui_info "No TTY; run openclaw doctor and openclaw plugins update --all manually"
2416
+ fi
2417
+ else
2418
+ if [[ "$NO_ONBOARD" == "1" || "$skip_onboard" == "true" ]]; then
2419
+ ui_info "Skipping onboard (requested); run openclaw onboard later"
2420
+ else
2421
+ local config_path="${OPENCLAW_CONFIG_PATH:-$HOME/.openclaw/openclaw.json}"
2422
+ if [[ -f "${config_path}" || -f "$HOME/.clawdbot/clawdbot.json" || -f "$HOME/.moltbot/moltbot.json" || -f "$HOME/.moldbot/moldbot.json" ]]; then
2423
+ ui_info "Config already present; running doctor"
2424
+ run_doctor
2425
+ should_open_dashboard=true
2426
+ ui_info "Config already present; skipping onboarding"
2427
+ skip_onboard=true
2428
+ fi
2429
+ ui_info "Starting setup"
2430
+ echo ""
2431
+ if [[ -r /dev/tty && -w /dev/tty ]]; then
2432
+ local claw="${OPENCLAW_BIN:-}"
2433
+ if [[ -z "$claw" ]]; then
2434
+ claw="$(resolve_openclaw_bin || true)"
2435
+ fi
2436
+ if [[ -z "$claw" ]]; then
2437
+ ui_info "Skipping onboarding (openclaw not on PATH yet)"
2438
+ warn_openclaw_not_found
2439
+ return 0
2440
+ fi
2441
+ exec </dev/tty
2442
+ exec "$claw" onboard
2443
+ fi
2444
+ ui_info "No TTY; run openclaw onboard to finish setup"
2445
+ return 0
2446
+ fi
2447
+ fi
2448
+
2449
+ if command -v openclaw &> /dev/null; then
2450
+ local claw="${OPENCLAW_BIN:-}"
2451
+ if [[ -z "$claw" ]]; then
2452
+ claw="$(resolve_openclaw_bin || true)"
2453
+ fi
2454
+ if [[ -n "$claw" ]] && is_gateway_daemon_loaded "$claw"; then
2455
+ if [[ "$DRY_RUN" == "1" ]]; then
2456
+ ui_info "Gateway daemon detected; would restart (openclaw daemon restart)"
2457
+ else
2458
+ ui_info "Gateway daemon detected; restarting"
2459
+ if OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" daemon restart >/dev/null 2>&1; then
2460
+ ui_success "Gateway restarted"
2461
+ else
2462
+ ui_warn "Gateway restart failed; try: openclaw daemon restart"
2463
+ fi
2464
+ fi
2465
+ fi
2466
+ fi
2467
+
2468
+ if [[ "$should_open_dashboard" == "true" ]]; then
2469
+ maybe_open_dashboard
2470
+ fi
2471
+
2472
+ show_footer_links
2473
+ }
2474
+
2475
+ if [[ "${OPENCLAW_INSTALL_SH_NO_RUN:-0}" != "1" ]]; then
2476
+ parse_args "$@"
2477
+ configure_verbose
2478
+ main
2479
+ fi