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 +1 -1
- package/scripts/install +279 -0
- package/scripts/install.sh +264 -106
- package/supervisor/fluxy-agent.ts +2 -19
- package/worker/claude-auth.ts +23 -0
package/package.json
CHANGED
package/scripts/install
ADDED
|
@@ -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"
|
package/scripts/install.sh
CHANGED
|
@@ -1,121 +1,279 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
set -
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
31
|
+
# ─── Detect platform ────────────────────────────────────────────────────────
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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 =
|
|
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;
|
package/worker/claude-auth.ts
CHANGED
|
@@ -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
|
/**
|