fluxy-bot 0.2.35 → 0.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,279 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ # ─── Fluxy Installer ────────────────────────────────────────────────────────
5
+ # curl -fsSL https://fluxy.bot/install | sh
6
+ #
7
+ # Downloads Node.js + Fluxy into ~/.fluxy — no system dependencies needed.
8
+ # ─────────────────────────────────────────────────────────────────────────────
9
+
10
+ MIN_NODE_MAJOR=18
11
+ NODE_VERSION="22.14.0"
12
+ FLUXY_HOME="$HOME/.fluxy"
13
+ TOOLS_DIR="$FLUXY_HOME/tools"
14
+ NODE_DIR="$TOOLS_DIR/node"
15
+ BIN_DIR="$FLUXY_HOME/bin"
16
+ USE_SYSTEM_NODE=false
17
+
18
+ CYAN='\033[36m'
19
+ GREEN='\033[32m'
20
+ YELLOW='\033[33m'
21
+ RED='\033[31m'
22
+ DIM='\033[2m'
23
+ BOLD='\033[1m'
24
+ RESET='\033[0m'
25
+
26
+ printf "\n${CYAN}${BOLD} ╔═══════════════════════════════╗${RESET}\n"
27
+ printf "${CYAN}${BOLD} ║ FLUXY ║${RESET}\n"
28
+ printf "${CYAN}${BOLD} ╚═══════════════════════════════╝${RESET}\n"
29
+ printf "${DIM} Self-hosted AI bot${RESET}\n\n"
30
+
31
+ # ─── Detect platform ────────────────────────────────────────────────────────
32
+
33
+ detect_platform() {
34
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
35
+ ARCH=$(uname -m)
36
+
37
+ case "$OS" in
38
+ linux) PLATFORM="linux" ;;
39
+ darwin) PLATFORM="darwin" ;;
40
+ *)
41
+ printf " ${RED}✗${RESET} Unsupported OS: $OS\n"
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ case "$ARCH" in
47
+ x86_64) NODEARCH="x64" ;;
48
+ aarch64|arm64) NODEARCH="arm64" ;;
49
+ armv7l|armv6l) NODEARCH="armv7l" ;;
50
+ *)
51
+ printf " ${RED}✗${RESET} Unsupported architecture: $ARCH\n"
52
+ exit 1
53
+ ;;
54
+ esac
55
+
56
+ printf " ${DIM}Platform: ${PLATFORM}/${NODEARCH}${RESET}\n"
57
+ }
58
+
59
+ # ─── Check for system Node.js ─────────────────────────────────────────────
60
+
61
+ check_system_node() {
62
+ if command -v node >/dev/null 2>&1; then
63
+ SYS_NODE_VERSION=$(node -v 2>/dev/null || echo "")
64
+ if [ -n "$SYS_NODE_VERSION" ]; then
65
+ MAJOR=$(echo "$SYS_NODE_VERSION" | sed 's/^v//' | cut -d. -f1)
66
+ if [ "$MAJOR" -ge "$MIN_NODE_MAJOR" ] 2>/dev/null; then
67
+ USE_SYSTEM_NODE=true
68
+ printf " ${GREEN}✔${RESET} Node.js ${SYS_NODE_VERSION} (system)\n"
69
+ return 0
70
+ fi
71
+ fi
72
+ fi
73
+ return 1
74
+ }
75
+
76
+ # ─── Download Node.js ───────────────────────────────────────────────────────
77
+
78
+ install_node() {
79
+ # Check if we already have a bundled node that works
80
+ if [ -x "$NODE_DIR/bin/node" ]; then
81
+ EXISTING=$("$NODE_DIR/bin/node" -v 2>/dev/null || echo "")
82
+ if [ -n "$EXISTING" ]; then
83
+ printf " ${GREEN}✔${RESET} Node.js ${EXISTING} (bundled)\n"
84
+ return 0
85
+ fi
86
+ fi
87
+
88
+ printf " ${CYAN}↓${RESET} Downloading Node.js v${NODE_VERSION}...\n"
89
+
90
+ NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${PLATFORM}-${NODEARCH}.tar.xz"
91
+ TMPFILE=$(mktemp /tmp/node-XXXXXX.tar.xz)
92
+
93
+ # Download
94
+ if command -v curl >/dev/null 2>&1; then
95
+ curl -fsSL -o "$TMPFILE" "$NODE_URL"
96
+ elif command -v wget >/dev/null 2>&1; then
97
+ wget -qO "$TMPFILE" "$NODE_URL"
98
+ else
99
+ printf " ${RED}✗${RESET} curl or wget required\n"
100
+ exit 1
101
+ fi
102
+
103
+ # Extract
104
+ mkdir -p "$TOOLS_DIR"
105
+ rm -rf "$NODE_DIR"
106
+ mkdir -p "$NODE_DIR"
107
+
108
+ tar xf "$TMPFILE" -C "$NODE_DIR" --strip-components=1
109
+ rm -f "$TMPFILE"
110
+
111
+ # Verify
112
+ if [ ! -x "$NODE_DIR/bin/node" ]; then
113
+ printf " ${RED}✗${RESET} Node.js download failed\n"
114
+ exit 1
115
+ fi
116
+
117
+ printf " ${GREEN}✔${RESET} Node.js v${NODE_VERSION} installed\n"
118
+ }
119
+
120
+ # ─── Install Fluxy ────────────────────────────────────────────────────────
121
+
122
+ install_fluxy() {
123
+ if [ "$USE_SYSTEM_NODE" = true ]; then
124
+ NPM="npm"
125
+ NODE="node"
126
+ else
127
+ NPM="$NODE_DIR/bin/npm"
128
+ NODE="$NODE_DIR/bin/node"
129
+ fi
130
+
131
+ printf " ${CYAN}↓${RESET} Installing fluxy...\n"
132
+
133
+ # Get tarball URL from npm registry
134
+ TARBALL_URL=$("$NPM" view fluxy-bot dist.tarball 2>/dev/null)
135
+ if [ -z "$TARBALL_URL" ]; then
136
+ printf " ${RED}✗${RESET} Failed to fetch package info from npm\n"
137
+ exit 1
138
+ fi
139
+
140
+ # Download and extract tarball
141
+ TMPDIR=$(mktemp -d)
142
+ if command -v curl >/dev/null 2>&1; then
143
+ curl -fsSL -o "$TMPDIR/fluxy.tgz" "$TARBALL_URL"
144
+ elif command -v wget >/dev/null 2>&1; then
145
+ wget -qO "$TMPDIR/fluxy.tgz" "$TARBALL_URL"
146
+ fi
147
+
148
+ tar xzf "$TMPDIR/fluxy.tgz" -C "$TMPDIR"
149
+ EXTRACTED="$TMPDIR/package"
150
+
151
+ if [ ! -d "$EXTRACTED" ]; then
152
+ rm -rf "$TMPDIR"
153
+ printf " ${RED}✗${RESET} Installation failed\n"
154
+ exit 1
155
+ fi
156
+
157
+ # Copy source files to ~/.fluxy/ (preserves existing config.json, memory.db, etc.)
158
+ cp -r "$EXTRACTED"/* "$FLUXY_HOME"/
159
+ cp -r "$EXTRACTED"/.[!.]* "$FLUXY_HOME"/ 2>/dev/null || true
160
+ rm -rf "$TMPDIR"
161
+
162
+ # Install dependencies inside ~/.fluxy/
163
+ (cd "$FLUXY_HOME" && "$NPM" install --omit=dev 2>/dev/null)
164
+
165
+ # Build chat interface if not pre-built in the tarball
166
+ if [ -d "$FLUXY_HOME/dist-fluxy" ] && [ -f "$FLUXY_HOME/dist-fluxy/onboard.html" ]; then
167
+ printf " ${GREEN}✔${RESET} Chat interface ready\n"
168
+ else
169
+ printf " ${CYAN}↓${RESET} Building chat interface...\n"
170
+ if (cd "$FLUXY_HOME" && "$NPM" run build:fluxy 2>/dev/null); then
171
+ printf " ${GREEN}✔${RESET} Chat interface built\n"
172
+ else
173
+ printf " ${YELLOW}!${RESET} Chat build skipped — will build on first start\n"
174
+ fi
175
+ fi
176
+
177
+ # Verify
178
+ if [ ! -f "$FLUXY_HOME/bin/cli.js" ]; then
179
+ printf " ${RED}✗${RESET} Installation failed\n"
180
+ exit 1
181
+ fi
182
+
183
+ VERSION=$("$NODE" -e "const p=JSON.parse(require('fs').readFileSync('$FLUXY_HOME/package.json','utf8'));console.log(p.version)" 2>/dev/null || echo "unknown")
184
+
185
+ printf " ${GREEN}✔${RESET} Fluxy v${VERSION} installed\n"
186
+ }
187
+
188
+ # ─── Create wrapper script ──────────────────────────────────────────────────
189
+
190
+ create_wrapper() {
191
+ mkdir -p "$BIN_DIR"
192
+
193
+ # Remove any existing wrapper/symlink
194
+ rm -f "$BIN_DIR/fluxy"
195
+
196
+ if [ "$USE_SYSTEM_NODE" = true ]; then
197
+ cat > "$BIN_DIR/fluxy" << 'WRAPPER'
198
+ #!/bin/sh
199
+ CLI="$HOME/.fluxy/bin/cli.js"
200
+ exec node "$CLI" "$@"
201
+ WRAPPER
202
+ else
203
+ cat > "$BIN_DIR/fluxy" << 'WRAPPER'
204
+ #!/bin/sh
205
+ FLUXY_HOME="$HOME/.fluxy"
206
+ NODE="$FLUXY_HOME/tools/node/bin/node"
207
+ CLI="$FLUXY_HOME/bin/cli.js"
208
+ exec "$NODE" "$CLI" "$@"
209
+ WRAPPER
210
+ fi
211
+
212
+ chmod +x "$BIN_DIR/fluxy"
213
+ printf " ${GREEN}✔${RESET} Created ${DIM}~/.fluxy/bin/fluxy${RESET}\n"
214
+ }
215
+
216
+ # ─── Add to PATH ────────────────────────────────────────────────────────────
217
+
218
+ setup_path() {
219
+ SHELL_NAME=$(basename "$SHELL" 2>/dev/null || echo "sh")
220
+ EXPORT_LINE='export PATH="$HOME/.fluxy/bin:$PATH"'
221
+ ALREADY_IN_PATH=false
222
+
223
+ case ":$PATH:" in
224
+ *":$BIN_DIR:"*) ALREADY_IN_PATH=true ;;
225
+ esac
226
+
227
+ if [ "$ALREADY_IN_PATH" = true ]; then
228
+ return 0
229
+ fi
230
+
231
+ # Add to shell profile
232
+ PROFILE=""
233
+ case "$SHELL_NAME" in
234
+ zsh) PROFILE="$HOME/.zshrc" ;;
235
+ bash)
236
+ if [ -f "$HOME/.bash_profile" ]; then
237
+ PROFILE="$HOME/.bash_profile"
238
+ else
239
+ PROFILE="$HOME/.bashrc"
240
+ fi
241
+ ;;
242
+ fish)
243
+ mkdir -p "$HOME/.config/fish"
244
+ if ! grep -q "fluxy/bin" "$HOME/.config/fish/config.fish" 2>/dev/null; then
245
+ echo 'set -gx PATH "$HOME/.fluxy/bin" $PATH' >> "$HOME/.config/fish/config.fish"
246
+ fi
247
+ printf " ${GREEN}✔${RESET} Added to PATH ${DIM}(~/.config/fish/config.fish)${RESET}\n"
248
+ return 0
249
+ ;;
250
+ *) PROFILE="$HOME/.profile" ;;
251
+ esac
252
+
253
+ if [ -n "$PROFILE" ]; then
254
+ if ! grep -q "fluxy/bin" "$PROFILE" 2>/dev/null; then
255
+ printf "\n# Fluxy\n%s\n" "$EXPORT_LINE" >> "$PROFILE"
256
+ fi
257
+ printf " ${GREEN}✔${RESET} Added to PATH ${DIM}(${PROFILE})${RESET}\n"
258
+ fi
259
+
260
+ export PATH="$BIN_DIR:$PATH"
261
+ }
262
+
263
+ # ─── Main ────────────────────────────────────────────────────────────────────
264
+
265
+ mkdir -p "$FLUXY_HOME"
266
+
267
+ detect_platform
268
+ check_system_node || install_node
269
+ install_fluxy
270
+ create_wrapper
271
+ setup_path
272
+
273
+ printf "\n ${GREEN}${BOLD}✔ Fluxy is ready!${RESET}\n\n"
274
+ printf " Get started:\n\n"
275
+ printf " ${CYAN}fluxy init${RESET} Set up your bot\n"
276
+ printf " ${CYAN}fluxy start${RESET} Start your bot\n"
277
+ printf " ${CYAN}fluxy status${RESET} Check if it's running\n\n"
278
+ printf " ${DIM}Run ${RESET}${CYAN}fluxy init${RESET}${DIM} to begin.${RESET}\n"
279
+ printf " ${DIM}(Open a new terminal if 'fluxy' isn't found yet)${RESET}\n\n"
@@ -1,121 +1,279 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
1
+ #!/bin/sh
2
+ set -e
3
3
 
4
- # ── Fluxy Installer ──
5
- # Downloads the latest fluxy-bot from npm and installs to ~/.fluxy/
4
+ # ─── Fluxy Installer ────────────────────────────────────────────────────────
5
+ # curl -fsSL https://fluxy.bot/install | sh
6
+ #
7
+ # Downloads Node.js + Fluxy into ~/.fluxy — no system dependencies needed.
8
+ # ─────────────────────────────────────────────────────────────────────────────
6
9
 
10
+ MIN_NODE_MAJOR=18
11
+ NODE_VERSION="22.14.0"
7
12
  FLUXY_HOME="$HOME/.fluxy"
13
+ TOOLS_DIR="$FLUXY_HOME/tools"
14
+ NODE_DIR="$TOOLS_DIR/node"
15
+ BIN_DIR="$FLUXY_HOME/bin"
16
+ USE_SYSTEM_NODE=false
8
17
 
9
- c_reset='\033[0m'
10
- c_cyan='\033[36m'
11
- c_green='\033[32m'
12
- c_red='\033[31m'
13
- c_dim='\033[2m'
14
- c_bold='\033[1m'
18
+ CYAN='\033[36m'
19
+ GREEN='\033[32m'
20
+ YELLOW='\033[33m'
21
+ RED='\033[31m'
22
+ DIM='\033[2m'
23
+ BOLD='\033[1m'
24
+ RESET='\033[0m'
15
25
 
16
- info() { echo -e " ${c_cyan}▸${c_reset} $1"; }
17
- ok() { echo -e " ${c_green}✔${c_reset} $1"; }
18
- err() { echo -e " ${c_red}✘${c_reset} $1" >&2; }
26
+ printf "\n${CYAN}${BOLD} ╔═══════════════════════════════╗${RESET}\n"
27
+ printf "${CYAN}${BOLD} ║ FLUXY ║${RESET}\n"
28
+ printf "${CYAN}${BOLD} ╚═══════════════════════════════╝${RESET}\n"
29
+ printf "${DIM} Self-hosted AI bot${RESET}\n\n"
19
30
 
20
- echo -e "\n ${c_cyan}${c_bold}Fluxy Installer${c_reset}\n"
31
+ # ─── Detect platform ────────────────────────────────────────────────────────
21
32
 
22
- # ── Check dependencies ──
23
- for cmd in node npm curl tar; do
24
- if ! command -v "$cmd" &>/dev/null; then
25
- err "$cmd is required but not installed."
33
+ detect_platform() {
34
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
35
+ ARCH=$(uname -m)
36
+
37
+ case "$OS" in
38
+ linux) PLATFORM="linux" ;;
39
+ darwin) PLATFORM="darwin" ;;
40
+ *)
41
+ printf " ${RED}✗${RESET} Unsupported OS: $OS\n"
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ case "$ARCH" in
47
+ x86_64) NODEARCH="x64" ;;
48
+ aarch64|arm64) NODEARCH="arm64" ;;
49
+ armv7l|armv6l) NODEARCH="armv7l" ;;
50
+ *)
51
+ printf " ${RED}✗${RESET} Unsupported architecture: $ARCH\n"
52
+ exit 1
53
+ ;;
54
+ esac
55
+
56
+ printf " ${DIM}Platform: ${PLATFORM}/${NODEARCH}${RESET}\n"
57
+ }
58
+
59
+ # ─── Check for system Node.js ─────────────────────────────────────────────
60
+
61
+ check_system_node() {
62
+ if command -v node >/dev/null 2>&1; then
63
+ SYS_NODE_VERSION=$(node -v 2>/dev/null || echo "")
64
+ if [ -n "$SYS_NODE_VERSION" ]; then
65
+ MAJOR=$(echo "$SYS_NODE_VERSION" | sed 's/^v//' | cut -d. -f1)
66
+ if [ "$MAJOR" -ge "$MIN_NODE_MAJOR" ] 2>/dev/null; then
67
+ USE_SYSTEM_NODE=true
68
+ printf " ${GREEN}✔${RESET} Node.js ${SYS_NODE_VERSION} (system)\n"
69
+ return 0
70
+ fi
71
+ fi
72
+ fi
73
+ return 1
74
+ }
75
+
76
+ # ─── Download Node.js ───────────────────────────────────────────────────────
77
+
78
+ install_node() {
79
+ # Check if we already have a bundled node that works
80
+ if [ -x "$NODE_DIR/bin/node" ]; then
81
+ EXISTING=$("$NODE_DIR/bin/node" -v 2>/dev/null || echo "")
82
+ if [ -n "$EXISTING" ]; then
83
+ printf " ${GREEN}✔${RESET} Node.js ${EXISTING} (bundled)\n"
84
+ return 0
85
+ fi
86
+ fi
87
+
88
+ printf " ${CYAN}↓${RESET} Downloading Node.js v${NODE_VERSION}...\n"
89
+
90
+ NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${PLATFORM}-${NODEARCH}.tar.xz"
91
+ TMPFILE=$(mktemp /tmp/node-XXXXXX.tar.xz)
92
+
93
+ # Download
94
+ if command -v curl >/dev/null 2>&1; then
95
+ curl -fsSL -o "$TMPFILE" "$NODE_URL"
96
+ elif command -v wget >/dev/null 2>&1; then
97
+ wget -qO "$TMPFILE" "$NODE_URL"
98
+ else
99
+ printf " ${RED}✗${RESET} curl or wget required\n"
26
100
  exit 1
27
101
  fi
28
- done
29
-
30
- # ── Get tarball URL ──
31
- info "Fetching latest version..."
32
- TARBALL_URL=$(npm view fluxy-bot dist.tarball 2>/dev/null)
33
- if [ -z "$TARBALL_URL" ]; then
34
- err "Failed to fetch tarball URL from npm."
35
- exit 1
36
- fi
37
- VERSION=$(npm view fluxy-bot version 2>/dev/null)
38
- ok "Found fluxy-bot@${VERSION}"
39
-
40
- # ── Download and extract ──
41
- TMPDIR=$(mktemp -d)
42
- trap 'rm -rf "$TMPDIR"' EXIT
43
-
44
- info "Downloading..."
45
- curl -fsSL "$TARBALL_URL" -o "$TMPDIR/fluxy.tgz"
46
- ok "Downloaded"
47
-
48
- info "Extracting..."
49
- tar xzf "$TMPDIR/fluxy.tgz" -C "$TMPDIR"
50
- ok "Extracted"
51
-
52
- # npm pack tarballs extract into a 'package/' directory
53
- EXTRACTED="$TMPDIR/package"
54
- if [ ! -d "$EXTRACTED" ]; then
55
- err "Unexpected tarball structure."
56
- exit 1
57
- fi
58
-
59
- # ── Copy to ~/.fluxy/ ──
60
- info "Installing to $FLUXY_HOME..."
61
- mkdir -p "$FLUXY_HOME"
62
102
 
63
- # cp -r overwrites existing files but does NOT delete extras (config.json, memory.db, etc.)
64
- cp -r "$EXTRACTED"/* "$FLUXY_HOME"/
65
- # Also copy hidden files if any
66
- cp -r "$EXTRACTED"/.[!.]* "$FLUXY_HOME"/ 2>/dev/null || true
67
- ok "Files copied"
68
-
69
- # ── Install dependencies ──
70
- info "Installing dependencies (this may take a moment)..."
71
- cd "$FLUXY_HOME"
72
- npm install --omit=dev 2>/dev/null
73
- ok "Dependencies installed"
74
-
75
- # ── Build fluxy chat + onboard (served as static files) ──
76
- if [ -d "$FLUXY_HOME/dist-fluxy" ] && [ -f "$FLUXY_HOME/dist-fluxy/onboard.html" ]; then
77
- ok "Chat interface (pre-built)"
78
- else
79
- info "Building chat interface..."
80
- if npm run build:fluxy; then
81
- ok "Chat interface built"
103
+ # Extract
104
+ mkdir -p "$TOOLS_DIR"
105
+ rm -rf "$NODE_DIR"
106
+ mkdir -p "$NODE_DIR"
107
+
108
+ tar xf "$TMPFILE" -C "$NODE_DIR" --strip-components=1
109
+ rm -f "$TMPFILE"
110
+
111
+ # Verify
112
+ if [ ! -x "$NODE_DIR/bin/node" ]; then
113
+ printf " ${RED}✗${RESET} Node.js download failed\n"
114
+ exit 1
115
+ fi
116
+
117
+ printf " ${GREEN}✔${RESET} Node.js v${NODE_VERSION} installed\n"
118
+ }
119
+
120
+ # ─── Install Fluxy ────────────────────────────────────────────────────────
121
+
122
+ install_fluxy() {
123
+ if [ "$USE_SYSTEM_NODE" = true ]; then
124
+ NPM="npm"
125
+ NODE="node"
126
+ else
127
+ NPM="$NODE_DIR/bin/npm"
128
+ NODE="$NODE_DIR/bin/node"
129
+ fi
130
+
131
+ printf " ${CYAN}↓${RESET} Installing fluxy...\n"
132
+
133
+ # Get tarball URL from npm registry
134
+ TARBALL_URL=$("$NPM" view fluxy-bot dist.tarball 2>/dev/null)
135
+ if [ -z "$TARBALL_URL" ]; then
136
+ printf " ${RED}✗${RESET} Failed to fetch package info from npm\n"
137
+ exit 1
138
+ fi
139
+
140
+ # Download and extract tarball
141
+ TMPDIR=$(mktemp -d)
142
+ if command -v curl >/dev/null 2>&1; then
143
+ curl -fsSL -o "$TMPDIR/fluxy.tgz" "$TARBALL_URL"
144
+ elif command -v wget >/dev/null 2>&1; then
145
+ wget -qO "$TMPDIR/fluxy.tgz" "$TARBALL_URL"
146
+ fi
147
+
148
+ tar xzf "$TMPDIR/fluxy.tgz" -C "$TMPDIR"
149
+ EXTRACTED="$TMPDIR/package"
150
+
151
+ if [ ! -d "$EXTRACTED" ]; then
152
+ rm -rf "$TMPDIR"
153
+ printf " ${RED}✗${RESET} Installation failed\n"
154
+ exit 1
155
+ fi
156
+
157
+ # Copy source files to ~/.fluxy/ (preserves existing config.json, memory.db, etc.)
158
+ cp -r "$EXTRACTED"/* "$FLUXY_HOME"/
159
+ cp -r "$EXTRACTED"/.[!.]* "$FLUXY_HOME"/ 2>/dev/null || true
160
+ rm -rf "$TMPDIR"
161
+
162
+ # Install dependencies inside ~/.fluxy/
163
+ (cd "$FLUXY_HOME" && "$NPM" install --omit=dev 2>/dev/null)
164
+
165
+ # Build chat interface if not pre-built in the tarball
166
+ if [ -d "$FLUXY_HOME/dist-fluxy" ] && [ -f "$FLUXY_HOME/dist-fluxy/onboard.html" ]; then
167
+ printf " ${GREEN}✔${RESET} Chat interface ready\n"
168
+ else
169
+ printf " ${CYAN}↓${RESET} Building chat interface...\n"
170
+ if (cd "$FLUXY_HOME" && "$NPM" run build:fluxy 2>/dev/null); then
171
+ printf " ${GREEN}✔${RESET} Chat interface built\n"
172
+ else
173
+ printf " ${YELLOW}!${RESET} Chat build skipped — will build on first start\n"
174
+ fi
175
+ fi
176
+
177
+ # Verify
178
+ if [ ! -f "$FLUXY_HOME/bin/cli.js" ]; then
179
+ printf " ${RED}✗${RESET} Installation failed\n"
180
+ exit 1
181
+ fi
182
+
183
+ VERSION=$("$NODE" -e "const p=JSON.parse(require('fs').readFileSync('$FLUXY_HOME/package.json','utf8'));console.log(p.version)" 2>/dev/null || echo "unknown")
184
+
185
+ printf " ${GREEN}✔${RESET} Fluxy v${VERSION} installed\n"
186
+ }
187
+
188
+ # ─── Create wrapper script ──────────────────────────────────────────────────
189
+
190
+ create_wrapper() {
191
+ mkdir -p "$BIN_DIR"
192
+
193
+ # Remove any existing wrapper/symlink
194
+ rm -f "$BIN_DIR/fluxy"
195
+
196
+ if [ "$USE_SYSTEM_NODE" = true ]; then
197
+ cat > "$BIN_DIR/fluxy" << 'WRAPPER'
198
+ #!/bin/sh
199
+ CLI="$HOME/.fluxy/bin/cli.js"
200
+ exec node "$CLI" "$@"
201
+ WRAPPER
82
202
  else
83
- err "Chat interface build failed — it will be built on first start"
203
+ cat > "$BIN_DIR/fluxy" << 'WRAPPER'
204
+ #!/bin/sh
205
+ FLUXY_HOME="$HOME/.fluxy"
206
+ NODE="$FLUXY_HOME/tools/node/bin/node"
207
+ CLI="$FLUXY_HOME/bin/cli.js"
208
+ exec "$NODE" "$CLI" "$@"
209
+ WRAPPER
84
210
  fi
85
- fi
86
-
87
- # ── Create symlink ──
88
- info "Creating fluxy command..."
89
- chmod +x "$FLUXY_HOME/bin/cli.js"
90
-
91
- SYMLINK_TARGET="$FLUXY_HOME/bin/cli.js"
92
- SYMLINK_CREATED=false
93
-
94
- # Try /usr/local/bin first
95
- if [ -d "/usr/local/bin" ] && [ -w "/usr/local/bin" ]; then
96
- ln -sf "$SYMLINK_TARGET" /usr/local/bin/fluxy
97
- SYMLINK_CREATED=true
98
- ok "Created /usr/local/bin/fluxy"
99
- elif [ -d "/usr/local/bin" ]; then
100
- # Try with sudo
101
- if sudo ln -sf "$SYMLINK_TARGET" /usr/local/bin/fluxy 2>/dev/null; then
102
- SYMLINK_CREATED=true
103
- ok "Created /usr/local/bin/fluxy (sudo)"
211
+
212
+ chmod +x "$BIN_DIR/fluxy"
213
+ printf " ${GREEN}✔${RESET} Created ${DIM}~/.fluxy/bin/fluxy${RESET}\n"
214
+ }
215
+
216
+ # ─── Add to PATH ────────────────────────────────────────────────────────────
217
+
218
+ setup_path() {
219
+ SHELL_NAME=$(basename "$SHELL" 2>/dev/null || echo "sh")
220
+ EXPORT_LINE='export PATH="$HOME/.fluxy/bin:$PATH"'
221
+ ALREADY_IN_PATH=false
222
+
223
+ case ":$PATH:" in
224
+ *":$BIN_DIR:"*) ALREADY_IN_PATH=true ;;
225
+ esac
226
+
227
+ if [ "$ALREADY_IN_PATH" = true ]; then
228
+ return 0
104
229
  fi
105
- fi
106
-
107
- # Fallback to ~/.local/bin
108
- if [ "$SYMLINK_CREATED" = false ]; then
109
- mkdir -p "$HOME/.local/bin"
110
- ln -sf "$SYMLINK_TARGET" "$HOME/.local/bin/fluxy"
111
- ok "Created ~/.local/bin/fluxy"
112
-
113
- # Check if ~/.local/bin is in PATH
114
- if ! echo "$PATH" | tr ':' '\n' | grep -q "$HOME/.local/bin"; then
115
- echo -e "\n ${c_dim}Add ~/.local/bin to your PATH:${c_reset}"
116
- echo -e " ${c_dim} export PATH=\"\$HOME/.local/bin:\$PATH\"${c_reset}"
230
+
231
+ # Add to shell profile
232
+ PROFILE=""
233
+ case "$SHELL_NAME" in
234
+ zsh) PROFILE="$HOME/.zshrc" ;;
235
+ bash)
236
+ if [ -f "$HOME/.bash_profile" ]; then
237
+ PROFILE="$HOME/.bash_profile"
238
+ else
239
+ PROFILE="$HOME/.bashrc"
240
+ fi
241
+ ;;
242
+ fish)
243
+ mkdir -p "$HOME/.config/fish"
244
+ if ! grep -q "fluxy/bin" "$HOME/.config/fish/config.fish" 2>/dev/null; then
245
+ echo 'set -gx PATH "$HOME/.fluxy/bin" $PATH' >> "$HOME/.config/fish/config.fish"
246
+ fi
247
+ printf " ${GREEN}✔${RESET} Added to PATH ${DIM}(~/.config/fish/config.fish)${RESET}\n"
248
+ return 0
249
+ ;;
250
+ *) PROFILE="$HOME/.profile" ;;
251
+ esac
252
+
253
+ if [ -n "$PROFILE" ]; then
254
+ if ! grep -q "fluxy/bin" "$PROFILE" 2>/dev/null; then
255
+ printf "\n# Fluxy\n%s\n" "$EXPORT_LINE" >> "$PROFILE"
256
+ fi
257
+ printf " ${GREEN}✔${RESET} Added to PATH ${DIM}(${PROFILE})${RESET}\n"
117
258
  fi
118
- fi
119
259
 
120
- echo -e "\n ${c_green}${c_bold}Fluxy v${VERSION} installed successfully!${c_reset}"
121
- echo -e " ${c_dim}Run 'fluxy' to start your bot.${c_reset}\n"
260
+ export PATH="$BIN_DIR:$PATH"
261
+ }
262
+
263
+ # ─── Main ────────────────────────────────────────────────────────────────────
264
+
265
+ mkdir -p "$FLUXY_HOME"
266
+
267
+ detect_platform
268
+ check_system_node || install_node
269
+ install_fluxy
270
+ create_wrapper
271
+ setup_path
272
+
273
+ printf "\n ${GREEN}${BOLD}✔ Fluxy is ready!${RESET}\n\n"
274
+ printf " Get started:\n\n"
275
+ printf " ${CYAN}fluxy init${RESET} Set up your bot\n"
276
+ printf " ${CYAN}fluxy start${RESET} Start your bot\n"
277
+ printf " ${CYAN}fluxy status${RESET} Check if it's running\n\n"
278
+ printf " ${DIM}Run ${RESET}${CYAN}fluxy init${RESET}${DIM} to begin.${RESET}\n"
279
+ printf " ${DIM}(Open a new terminal if 'fluxy' isn't found yet)${RESET}\n\n"
@@ -6,11 +6,10 @@
6
6
  import { query, type SDKMessage, type SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
- import os from 'os';
10
9
  import { log } from '../shared/logger.js';
11
10
  import { DATA_DIR, PKG_DIR } from '../shared/paths.js';
11
+ import { getClaudeAccessToken } from '../worker/claude-auth.js';
12
12
 
13
- const CREDENTIALS_FILE = path.join(os.homedir(), '.claude', '.credentials.json');
14
13
  const PROMPT_FILE = path.join(import.meta.dirname, '..', 'worker', 'prompts', 'fluxy-system-prompt.txt');
15
14
 
16
15
  // In-memory session tracking (conversationId → sessionId)
@@ -58,22 +57,6 @@ function buildMultiPartPrompt(text: string, attachments: AgentAttachment[]): Asy
58
57
  })();
59
58
  }
60
59
 
61
- /** Read the OAuth token stored by claude-auth */
62
- function readOAuthToken(): string | null {
63
- try {
64
- if (fs.existsSync(CREDENTIALS_FILE)) {
65
- const creds = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf-8'));
66
- // Support both flat format ({ accessToken }) and nested ({ claudeAiOauth: { accessToken } })
67
- const oauth = creds.claudeAiOauth || creds;
68
- if (oauth.accessToken) {
69
- if (oauth.expiresAt && Date.now() >= oauth.expiresAt) return null;
70
- return oauth.accessToken;
71
- }
72
- }
73
- } catch {}
74
- return null;
75
- }
76
-
77
60
  /** Read the custom system prompt addendum */
78
61
  function readSystemPromptAddendum(): string {
79
62
  try {
@@ -94,7 +77,7 @@ export async function startFluxyAgentQuery(
94
77
  onMessage: (type: string, data: any) => void,
95
78
  attachments?: AgentAttachment[],
96
79
  ): Promise<void> {
97
- const oauthToken = readOAuthToken();
80
+ const oauthToken = await getClaudeAccessToken();
98
81
  if (!oauthToken) {
99
82
  onMessage('bot:error', { conversationId, error: 'Claude OAuth token not found. Please authenticate via the dashboard.' });
100
83
  return;
@@ -117,6 +117,29 @@ export function readClaudeAccessToken(): string | null {
117
117
  return oauth.accessToken;
118
118
  }
119
119
 
120
+ /** Read a valid access token, refreshing if expired. Returns null if unavailable. */
121
+ export async function getClaudeAccessToken(): Promise<string | null> {
122
+ const oauth = readOAuthBlock();
123
+ if (!oauth?.accessToken) return null;
124
+
125
+ // Token still valid
126
+ if (oauth.expiresAt && Date.now() < oauth.expiresAt) {
127
+ return oauth.accessToken;
128
+ }
129
+
130
+ // Try refresh
131
+ if (oauth.refreshToken) {
132
+ const refreshed = await refreshClaudeToken(oauth.refreshToken);
133
+ if (refreshed) {
134
+ // Re-read after refresh
135
+ const fresh = readOAuthBlock();
136
+ return fresh?.accessToken || null;
137
+ }
138
+ }
139
+
140
+ return null;
141
+ }
142
+
120
143
  /* ── Helpers ── */
121
144
 
122
145
  /**