2ded310472
- Add a dedicated plan-approval bottom sheet (interactive-prompt) driven by ExitPlanMode's now-4-option layout (Claude Code v2.1.177), with approve/YOLO/reject/feedback wired through respondPlan(requestId). - PermissionManager: stop auto-expiring AskUserQuestion/plan prompts and stop releasing pending prompts on client disconnect — they reflect real CLI state and must survive reconnects. - Detect server-rejected tokens (401/403 or failed WS handshake) and bounce back to the login screen via a new AUTH_EXPIRED_EVENT. - Add `/cert` endpoint + Settings entry so phones can download/trust the self-signed HTTPS cert directly; fix cert IP detection on non-macOS hosts. - Add `clawtap reset-login` command and `/api/auth/reset-attempts` endpoint to clear login rate-limit lockouts. - Codex adapter: only forward recognized keystrokes/option indices to tmux, never the synthetic "deny" dismissal signal. - PWA: actively poll for service-worker updates (iOS standalone apps don't reliably check on navigation). - update-service.sh: install tmux if missing, prompt to create the env file instead of failing, add --skip-firewall, and improve logging.
191 lines
6.0 KiB
Bash
Executable File
191 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
log() { echo "[$(date '+%H:%M:%S')] $*"; }
|
|
die() { echo "[$(date '+%H:%M:%S')] ERROR: $*" >&2; exit 1; }
|
|
|
|
SKIP_FIREWALL=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--skip-firewall) SKIP_FIREWALL=1 ;;
|
|
*) die "Unknown argument: $arg" ;;
|
|
esac
|
|
done
|
|
|
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
SERVICE_NAME="clawtap.service"
|
|
UNIT_DIR="$HOME/.config/systemd/user"
|
|
ENV_FILE="$HOME/.clawtap/env"
|
|
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME"
|
|
|
|
log "Repo: $REPO_DIR"
|
|
log "Service unit: $UNIT_FILE"
|
|
|
|
# --- Install systemd unit if missing ---
|
|
if [ ! -f "$UNIT_FILE" ]; then
|
|
log "Installing $SERVICE_NAME..."
|
|
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
log "$ENV_FILE not found — creating it now."
|
|
mkdir -p "$HOME/.clawtap"
|
|
read -rsp "Enter CLAWTAP_PASSWORD: " _pw
|
|
echo
|
|
echo "CLAWTAP_PASSWORD=$_pw" > "$ENV_FILE"
|
|
log "Created $ENV_FILE"
|
|
fi
|
|
|
|
mkdir -p "$UNIT_DIR"
|
|
cat > "$UNIT_FILE" <<EOF
|
|
[Unit]
|
|
Description=ClawTap — mobile UI for AI coding sessions
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=$REPO_DIR
|
|
Environment=HOME=$HOME
|
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
EnvironmentFile=$ENV_FILE
|
|
ExecStart=$REPO_DIR/node_modules/.bin/tsx $REPO_DIR/server/index.ts
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
StandardOutput=append:$HOME/.clawtap/server.log
|
|
StandardError=append:$HOME/.clawtap/server.log
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable "$SERVICE_NAME"
|
|
log "Service installed and enabled."
|
|
else
|
|
log "Service unit already exists, skipping install."
|
|
fi
|
|
|
|
# --- Check dependencies ---
|
|
log "Checking for tmux..."
|
|
if command -v tmux &>/dev/null; then
|
|
log "tmux found: $(tmux -V)"
|
|
else
|
|
log "tmux not found — installing..."
|
|
if command -v pacman &>/dev/null; then
|
|
sudo pacman -S --noconfirm tmux
|
|
elif command -v apt-get &>/dev/null; then
|
|
sudo apt-get install -y tmux
|
|
elif command -v brew &>/dev/null; then
|
|
brew install tmux
|
|
else
|
|
die "Cannot install tmux: no supported package manager found (pacman/apt/brew). Install it manually and re-run."
|
|
fi
|
|
log "tmux installed: $(tmux -V)"
|
|
fi
|
|
|
|
# --- Open firewall port ---
|
|
if [ "$SKIP_FIREWALL" = "1" ]; then
|
|
log "Skipping firewall rule (--skip-firewall)."
|
|
elif command -v ufw &>/dev/null; then
|
|
_port="$(grep -E '^PORT=' "$ENV_FILE" 2>/dev/null | cut -d= -f2 || true)"
|
|
_port="${_port:-3456}"
|
|
log "Opening port $_port for local network..."
|
|
sudo ufw allow from 192.168.0.0/16 to any port "$_port" comment 'ClawTap'
|
|
else
|
|
log "ufw not found, skipping firewall rule."
|
|
fi
|
|
|
|
# --- Install dependencies (skip if package files haven't changed) ---
|
|
cd "$REPO_DIR"
|
|
_pkg_hash="$(cat package.json package-lock.json 2>/dev/null | sha256sum | awk '{print $1}')"
|
|
_hash_file="$REPO_DIR/node_modules/.clawtap-install-hash"
|
|
if [ -d node_modules ] && [ -f "$_hash_file" ] && [ "$(cat "$_hash_file")" = "$_pkg_hash" ]; then
|
|
log "package.json/package-lock.json unchanged, skipping npm install."
|
|
else
|
|
log "Running npm install..."
|
|
npm install
|
|
|
|
# Interactively approve/deny any packages with pending install scripts
|
|
log "Checking for pending install scripts..."
|
|
_pending=$(npm approve-scripts --allow-scripts-pending 2>&1 | grep -E '^\s+\S+@' | awk '{print $1}' || true)
|
|
if [ -n "$_pending" ]; then
|
|
log "The following packages have pending install scripts:"
|
|
echo "$_pending"
|
|
echo
|
|
for _pkg in $_pending; do
|
|
read -rp " Approve install scripts for $_pkg? [y/N] " _ans
|
|
if [[ "$_ans" =~ ^[Yy]$ ]]; then
|
|
npm approve-scripts "$_pkg"
|
|
log "Approved $_pkg"
|
|
else
|
|
npm deny-scripts "$_pkg"
|
|
log "Denied $_pkg"
|
|
fi
|
|
done
|
|
log "Re-running npm install after approvals..."
|
|
npm install
|
|
else
|
|
log "No pending install scripts."
|
|
fi
|
|
|
|
echo "$_pkg_hash" > "$_hash_file"
|
|
fi
|
|
|
|
# --- Build frontend ---
|
|
log "Building frontend..."
|
|
npm run build
|
|
log "Frontend built successfully."
|
|
|
|
# --- Install CLI binary ---
|
|
log "Installing CLI binary to ~/.local/bin/clawtap..."
|
|
mkdir -p "$HOME/.local/bin"
|
|
cp "$REPO_DIR/bin/clawtap" "$HOME/.local/bin/clawtap"
|
|
chmod +x "$HOME/.local/bin/clawtap"
|
|
log "Binary installed."
|
|
|
|
# Ensure ~/.local/bin is in PATH for future shells
|
|
_fish_config="$HOME/.config/fish/config.fish"
|
|
if [ -f "$_fish_config" ] && ! grep -q '\.local/bin' "$_fish_config"; then
|
|
echo 'fish_add_path $HOME/.local/bin' >> "$_fish_config"
|
|
log "Added ~/.local/bin to PATH in $_fish_config"
|
|
elif ! grep -q '\.local/bin' "$HOME/.bashrc" 2>/dev/null && ! grep -q '\.local/bin' "$HOME/.profile" 2>/dev/null; then
|
|
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.profile"
|
|
log "Added ~/.local/bin to PATH in ~/.profile"
|
|
else
|
|
log "~/.local/bin already in PATH config, skipping."
|
|
fi
|
|
|
|
# --- HTTPS certificate ---
|
|
_cert="$HOME/.clawtap/cert.pem"
|
|
_key="$HOME/.clawtap/key.pem"
|
|
if [ -f "$_cert" ] && [ -f "$_key" ]; then
|
|
log "HTTPS certificate already exists, skipping generation."
|
|
else
|
|
read -rp "[$(date '+%H:%M:%S')] Generate self-signed HTTPS certificate? (recommended for PWA/push) [Y/n] " _ans
|
|
if [[ ! "$_ans" =~ ^[Nn]$ ]]; then
|
|
_lan_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || echo '0.0.0.0')"
|
|
log "Generating certificate for IP $_lan_ip..."
|
|
openssl req -x509 -newkey rsa:2048 -nodes \
|
|
-keyout "$_key" \
|
|
-out "$_cert" \
|
|
-days 365 \
|
|
-subj "/CN=ClawTap" \
|
|
-addext "subjectAltName=IP:$_lan_ip" \
|
|
2>/dev/null
|
|
chmod 600 "$_key"
|
|
log "Certificate generated at ~/.clawtap/{cert,key}.pem"
|
|
echo ""
|
|
echo " To trust on Android: send cert.pem to your phone → Settings → Security → Install certificate → CA certificate"
|
|
echo " To trust on iOS: send cert.pem to your phone → open it → Install Profile → Settings → General → About → Certificate Trust Settings → enable ClawTap"
|
|
echo ""
|
|
else
|
|
log "Skipping certificate generation."
|
|
fi
|
|
fi
|
|
|
|
# --- Restart ---
|
|
log "Restarting $SERVICE_NAME..."
|
|
systemctl --user restart "$SERVICE_NAME"
|
|
|
|
log "Status:"
|
|
systemctl --user status "$SERVICE_NAME" --no-pager || true
|