-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathinstall.sh
More file actions
255 lines (233 loc) · 11.6 KB
/
Copy pathinstall.sh
File metadata and controls
255 lines (233 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/usr/bin/env bash
set -e
# PinkyBot Installer
# Usage: curl -fsSL https://pinkybot.ai/install.sh | bash
REPO="https://github.com/bradbrok/PinkyBot.git"
INSTALL_DIR="$HOME/.pinkybot"
BOLD="\033[1m"
DIM="\033[2m"
YELLOW="\033[33m"
GREEN="\033[32m"
RED="\033[31m"
RESET="\033[0m"
banner() {
echo ""
echo -e "${YELLOW}${BOLD} ██████╗ ██╗███╗ ██╗██╗ ██╗██╗ ██╗${RESET}"
echo -e "${YELLOW}${BOLD} ██╔══██╗██║████╗ ██║██║ ██╔╝╚██╗ ██╔╝${RESET}"
echo -e "${YELLOW}${BOLD} ██████╔╝██║██╔██╗ ██║█████╔╝ ╚████╔╝ ${RESET}"
echo -e "${YELLOW}${BOLD} ██╔═══╝ ██║██║╚██╗██║██╔═██╗ ╚██╔╝ ${RESET}"
echo -e "${YELLOW}${BOLD} ██║ ██║██║ ╚████║██║ ██╗ ██║ ${RESET}"
echo -e "${YELLOW}${BOLD} ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ${RESET}"
echo ""
echo -e " ${DIM}Your personal AI sidekick — pinkybot.ai${RESET}"
echo ""
}
step() { echo -e "${YELLOW}▸${RESET} $1"; }
ok() { echo -e "${GREEN}✓${RESET} $1"; }
err() { echo -e "${RED}✗${RESET} $1"; exit 1; }
banner
# ── 1. Check Python ────────────────────────────────────────────────────────────
step "Checking Python 3.11+..."
PYTHON=""
for cmd in python3 python; do
if command -v "$cmd" &>/dev/null; then
VER=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null)
MAJOR=$(echo "$VER" | cut -d. -f1)
MINOR=$(echo "$VER" | cut -d. -f2)
if [ "$MAJOR" -ge 3 ] && [ "$MINOR" -ge 11 ]; then
PYTHON="$cmd"
ok "Found Python $VER ($cmd)"
break
fi
fi
done
[ -z "$PYTHON" ] && err "Python 3.11+ required. Install from https://python.org"
# ── 2. Check Git ───────────────────────────────────────────────────────────────
step "Checking git..."
command -v git &>/dev/null || err "git required. Install from https://git-scm.com"
ok "Found $(git --version)"
# ── 3. Check Claude Code ───────────────────────────────────────────────────────
step "Checking Claude Code..."
if ! command -v claude &>/dev/null; then
echo ""
echo -e " ${DIM}Claude Code not found. Installing...${RESET}"
curl -fsSL https://claude.ai/install.sh | bash
# Re-source PATH in case it was just installed
export PATH="$HOME/.claude/bin:$PATH"
command -v claude &>/dev/null || err "Claude Code install failed. Try: curl -fsSL https://claude.ai/install.sh | bash"
fi
ok "Found Claude Code"
# ── 4. Check Claude auth ───────────────────────────────────────────────────────
step "Checking Claude authentication..."
if claude auth status 2>/dev/null | grep -q '"loggedIn":true\|loggedIn.*true\|authenticated'; then
ok "Claude authenticated"
else
echo ""
echo -e " ${YELLOW}Not logged in to Claude.${RESET}"
echo -e " Run ${YELLOW}claude login${RESET} after installation, or set ${YELLOW}ANTHROPIC_API_KEY${RESET} in your environment."
echo ""
fi
# ── 5. Clone or update PinkyBot ────────────────────────────────────────────────
step "Installing PinkyBot to $INSTALL_DIR..."
TAG=$(curl -s https://api.github.com/repos/bradbrok/PinkyBot/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
if [ -z "$TAG" ]; then
echo -e " ${YELLOW}Warning: no releases found — falling back to main branch.${RESET}"
TAG="main"
fi
if [ -d "$INSTALL_DIR/.git" ]; then
echo -e " ${DIM}Existing install found — updating to $TAG...${RESET}"
git -C "$INSTALL_DIR" fetch --depth 1 origin --tags
git -C "$INSTALL_DIR" checkout "$TAG" 2>/dev/null || git -C "$INSTALL_DIR" checkout "tags/$TAG"
else
git clone --branch "$TAG" --depth 1 "$REPO" "$INSTALL_DIR"
fi
ok "PinkyBot $TAG ready"
# ── 6. Check/install Node.js (for frontend builds) ───────────────────────────
step "Checking Node.js..."
if command -v node &>/dev/null; then
NODE_VER=$(node -v 2>/dev/null | tr -d 'v')
NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1)
if [ "$NODE_MAJOR" -ge 18 ] 2>/dev/null; then
ok "Found Node.js $NODE_VER"
else
echo -e " ${YELLOW}Node.js $NODE_VER is too old (need 18+). Will attempt to install.${RESET}"
NODE_MAJOR=0
fi
else
NODE_MAJOR=0
fi
if [ "$NODE_MAJOR" -lt 18 ] 2>/dev/null; then
OS_TYPE="$(uname -s)"
ARCH="$(uname -m)"
if [ "$OS_TYPE" = "Darwin" ]; then
if command -v brew &>/dev/null; then
echo -e " ${DIM}Installing Node.js via Homebrew...${RESET}"
brew install node
else
err "Node.js 18+ required. Install from https://nodejs.org or run: brew install node"
fi
elif [ "$OS_TYPE" = "Linux" ]; then
echo -e " ${DIM}Installing Node.js 22 via NodeSource...${RESET}"
if command -v apt-get &>/dev/null; then
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
elif command -v dnf &>/dev/null; then
curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
sudo dnf install -y nodejs
else
err "Node.js 18+ required. Install from https://nodejs.org"
fi
fi
command -v node &>/dev/null && ok "Node.js $(node -v) installed" || err "Node.js install failed. Install manually from https://nodejs.org"
fi
# ── 7. Create venv + install ───────────────────────────────────────────────────
step "Setting up Python environment..."
cd "$INSTALL_DIR"
if [ ! -d ".venv" ]; then
"$PYTHON" -m venv .venv
fi
source .venv/bin/activate
pip install --quiet --upgrade pip
pip install --quiet -e ".[all]"
ok "Dependencies installed"
# ── 8. Build frontend ─────────────────────────────────────────────────────────
# The daemon serves the compiled SPA from frontend-dist/. If this build is
# skipped or fails, the dashboard 503s with manual build instructions — but
# that's a bad first impression, so try hard to build here and fail loudly
# (not silently) if it breaks. We don't `err`/exit on failure: the daemon can
# still run headless and the user can rebuild later, so a build hiccup
# shouldn't trap them in a reinstall loop.
FRONTEND_OK=0
step "Building frontend..."
if [ ! -d "frontend-svelte" ]; then
echo -e " ${YELLOW}No frontend-svelte/ directory — skipping frontend build.${RESET}"
elif ! command -v npm &>/dev/null; then
echo -e " ${YELLOW}npm not found — skipping frontend build.${RESET}"
echo -e " ${DIM}Install Node.js 18+ then build manually:${RESET}"
echo -e " ${DIM} cd $INSTALL_DIR/frontend-svelte && npm install && npm run build${RESET}"
else
cd frontend-svelte
BUILD_FAILED=0
echo -e " ${DIM}Installing frontend dependencies (npm install)...${RESET}"
if ! npm install --no-audit --no-fund >/tmp/pinky-npm-install.log 2>&1; then
BUILD_FAILED=1
echo -e " ${RED}npm install failed.${RESET} Last lines:"
tail -5 /tmp/pinky-npm-install.log | sed 's/^/ /'
fi
if [ "$BUILD_FAILED" -eq 0 ]; then
echo -e " ${DIM}Compiling frontend (npm run build)...${RESET}"
if ! npm run build >/tmp/pinky-npm-build.log 2>&1; then
BUILD_FAILED=1
echo -e " ${RED}npm run build failed.${RESET} Last lines:"
tail -5 /tmp/pinky-npm-build.log | sed 's/^/ /'
fi
fi
cd "$INSTALL_DIR"
# Verify the build actually produced a servable SPA, regardless of exit code.
if [ "$BUILD_FAILED" -eq 0 ] && [ -f "frontend-dist/index.html" ]; then
FRONTEND_OK=1
ok "Frontend built"
else
echo -e " ${YELLOW}⚠ Frontend build did not produce frontend-dist/index.html.${RESET}"
echo -e " ${DIM}The server will still start, but the dashboard won't load until you build it:${RESET}"
echo -e " ${DIM} cd $INSTALL_DIR/frontend-svelte && npm install && npm run build${RESET}"
echo -e " ${DIM}Build logs: /tmp/pinky-npm-install.log, /tmp/pinky-npm-build.log${RESET}"
fi
fi
# ── 9. Create wrapper script ───────────────────────────────────────────────────
step "Installing pinky command..."
WRAPPER="$HOME/.local/bin/pinky"
mkdir -p "$HOME/.local/bin"
cat > "$WRAPPER" <<SCRIPT
#!/usr/bin/env bash
source "$INSTALL_DIR/.venv/bin/activate"
exec python -m pinky_daemon "\$@"
SCRIPT
chmod +x "$WRAPPER"
# Add to PATH if not already
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
SHELL_RC="$HOME/.bashrc"
[[ -n "$ZSH_VERSION" || "$SHELL" == *zsh* ]] && SHELL_RC="$HOME/.zshrc"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_RC"
export PATH="$HOME/.local/bin:$PATH"
fi
ok "pinky command installed"
# ── 10. Set PINKY_SESSION_SECRET ─────────────────────────────────────────────
DOTENV_FILE="$INSTALL_DIR/.env"
# Generate secret if not already in .env
if ! grep -q "PINKY_SESSION_SECRET" "$DOTENV_FILE" 2>/dev/null; then
PINKY_SESSION_SECRET_VAL=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 32)
echo "PINKY_SESSION_SECRET=\"$PINKY_SESSION_SECRET_VAL\"" >> "$DOTENV_FILE"
export PINKY_SESSION_SECRET="$PINKY_SESSION_SECRET_VAL"
ok "Generated PINKY_SESSION_SECRET and saved to .env"
else
ok "PINKY_SESSION_SECRET already in .env"
fi
# Also export to shell RC for convenience
SHELL_RC="$HOME/.bashrc"
[[ -n "$ZSH_VERSION" || "$SHELL" == *zsh* ]] && SHELL_RC="$HOME/.zshrc"
if ! grep -q "PINKY_SESSION_SECRET" "$SHELL_RC" 2>/dev/null; then
echo "export PINKY_SESSION_SECRET=\"${PINKY_SESSION_SECRET_VAL:-$(grep PINKY_SESSION_SECRET "$DOTENV_FILE" | cut -d'\"' -f2)}\"" >> "$SHELL_RC"
fi
# ── 11. Done ───────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD} PinkyBot installed successfully!${RESET}"
echo ""
echo -e " ${BOLD}1. Start the server:${RESET}"
echo -e " ${YELLOW}pinky --mode api --port 8888${RESET}"
echo ""
echo -e " ${BOLD}2. Open the dashboard:${RESET}"
echo -e " ${YELLOW}http://localhost:8888${RESET} (same machine)"
echo -e " ${YELLOW}http://\$(hostname -I 2>/dev/null | awk '{print \$1}' || echo '<your-ip>'):8888${RESET} (other devices)"
echo ""
echo -e " ${BOLD}3. Complete onboarding:${RESET}"
echo -e " Set a password, create your agent, and connect Telegram."
echo ""
if ! claude auth status 2>/dev/null | grep -q '"loggedIn":true\|loggedIn.*true\|authenticated'; then
echo -e " ${YELLOW}⚠ Claude not authenticated.${RESET} Run one of:"
echo -e " ${YELLOW}claude login${RESET} (Claude Max/Pro subscription)"
echo -e " ${YELLOW}export ANTHROPIC_API_KEY=sk-ant-...${RESET} (API key)"
echo ""
fi
echo -e " ${DIM}Docs & help: https://pinkybot.ai${RESET}"
echo ""