169 lines
5.1 KiB
Bash
Executable File
169 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Runs the patched host (cloudcli/, branch dev/taskwork) against your real
|
|
# environment — the same ~/.claude projects, the same ~/.cloudcli/auth.db, the
|
|
# same ~/.claude-code-ui/plugins as an installed CloudCLI. Nothing is sandboxed,
|
|
# so what you see is what a user would see.
|
|
#
|
|
# ./scripts/dev-host.sh start install the built plugin and start the host
|
|
# ./scripts/dev-host.sh stop stop it
|
|
# ./scripts/dev-host.sh status ports, pid, plugin version
|
|
# ./scripts/dev-host.sh logs follow the log
|
|
#
|
|
# Ports default to 3010/5183, not 3001/5173, so an already installed CloudCLI
|
|
# keeps running untouched. Override with SERVER_PORT / VITE_PORT.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
HOST_DIR="$ROOT/cloudcli"
|
|
LOG_FILE="$ROOT/.dev-host.log"
|
|
PID_FILE="$ROOT/.dev-host.pid"
|
|
|
|
SERVER_PORT="${SERVER_PORT:-3010}"
|
|
VITE_PORT="${VITE_PORT:-5183}"
|
|
READY_TIMEOUT_SECONDS=60
|
|
|
|
running_pid() {
|
|
[ -f "$PID_FILE" ] || return 1
|
|
local pid
|
|
pid="$(cat "$PID_FILE")"
|
|
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && echo "$pid"
|
|
}
|
|
|
|
port_owner() {
|
|
ss -ltnp 2>/dev/null | awk -v port=":$1" '$4 ~ port' | grep -oE 'pid=[0-9]+' | head -1
|
|
}
|
|
|
|
require_environment() {
|
|
[ -d "$HOST_DIR/.git" ] || { echo "cloudcli/ is not initialised — run ./scripts/bootstrap.sh" >&2; exit 1; }
|
|
[ -d "$HOST_DIR/node_modules" ] || { echo "cloudcli/node_modules is missing — run ./scripts/bootstrap.sh" >&2; exit 1; }
|
|
|
|
# The server needs these three compiled; npm >= 12 blocks install scripts by
|
|
# default, which leaves them unbuilt and the server dead on arrival.
|
|
local missing=()
|
|
for module in better-sqlite3 bcrypt node-pty; do
|
|
(cd "$HOST_DIR" && node -e "require('$module')") >/dev/null 2>&1 || missing+=("$module")
|
|
done
|
|
|
|
if [ ${#missing[@]} -gt 0 ]; then
|
|
echo "native modules not built: ${missing[*]}" >&2
|
|
echo "run ./scripts/bootstrap.sh, which builds them" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
warn_about_other_instances() {
|
|
local other
|
|
other="$(port_owner 3001 || true)"
|
|
[ -n "$other" ] || return 0
|
|
|
|
cat >&2 <<EOF
|
|
note: another CloudCLI is listening on :3001 (${other}).
|
|
This instance shares its database (~/.cloudcli/auth.db) and its plugin
|
|
directory. That is intentional here — set DATABASE_PATH to run against a
|
|
separate database instead.
|
|
EOF
|
|
}
|
|
|
|
start_host() {
|
|
local pid
|
|
if pid="$(running_pid)"; then
|
|
echo "already running (pid $pid) — http://localhost:$VITE_PORT"
|
|
exit 0
|
|
fi
|
|
|
|
require_environment
|
|
warn_about_other_instances
|
|
|
|
for port in "$SERVER_PORT" "$VITE_PORT"; do
|
|
if ss -ltn 2>/dev/null | grep -q ":$port "; then
|
|
echo "port $port is already in use — pick another with SERVER_PORT/VITE_PORT" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [ -d "$ROOT/plugin-taskwork/build" ]; then
|
|
# Captured rather than piped into `head -1`: head closes the pipe after the
|
|
# first line, link-plugin.sh dies of SIGPIPE on its next write, and pipefail
|
|
# turns that into a silent exit here.
|
|
local plugin_output
|
|
plugin_output="$("$ROOT/scripts/link-plugin.sh")"
|
|
printf '%s\n' "${plugin_output%%$'\n'*}"
|
|
else
|
|
echo "note: plugin-taskwork/build is missing, the host will start without the plugin"
|
|
fi
|
|
|
|
# setsid detaches from this shell's process group, so the host survives the
|
|
# terminal (or the agent turn) that started it.
|
|
(
|
|
cd "$HOST_DIR"
|
|
SERVER_PORT="$SERVER_PORT" VITE_PORT="$VITE_PORT" \
|
|
setsid nohup npm run dev > "$LOG_FILE" 2>&1 < /dev/null &
|
|
echo $! > "$PID_FILE"
|
|
)
|
|
|
|
printf 'starting'
|
|
for _ in $(seq "$READY_TIMEOUT_SECONDS"); do
|
|
if curl -sf "http://localhost:$SERVER_PORT/api/auth/status" >/dev/null 2>&1; then
|
|
echo
|
|
echo "host http://localhost:$VITE_PORT (api :$SERVER_PORT, pid $(cat "$PID_FILE"))"
|
|
grep -m1 '\[Plugins\] Server started' "$LOG_FILE" 2>/dev/null || echo "note: no plugin server in the log yet"
|
|
echo "log $LOG_FILE"
|
|
exit 0
|
|
fi
|
|
printf '.'
|
|
sleep 1
|
|
done
|
|
|
|
echo
|
|
echo "the host did not answer within ${READY_TIMEOUT_SECONDS}s — see $LOG_FILE" >&2
|
|
tail -5 "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
}
|
|
|
|
stop_host() {
|
|
local pid
|
|
if ! pid="$(running_pid)"; then
|
|
echo "not running"
|
|
rm -f "$PID_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
# npm run dev spawns the server and vite; killing the group takes all of them.
|
|
kill -- "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
|
|
for _ in $(seq 10); do
|
|
kill -0 "$pid" 2>/dev/null || break
|
|
sleep 0.5
|
|
done
|
|
kill -9 -- "-$pid" 2>/dev/null || true
|
|
|
|
rm -f "$PID_FILE"
|
|
echo "stopped"
|
|
}
|
|
|
|
show_status() {
|
|
local pid
|
|
if pid="$(running_pid)"; then
|
|
echo "running pid $pid, http://localhost:$VITE_PORT (api :$SERVER_PORT)"
|
|
else
|
|
echo "stopped"
|
|
fi
|
|
|
|
echo "branch $(git -C "$HOST_DIR" branch --show-current 2>/dev/null || echo '?')"
|
|
|
|
local installed="$HOME/.claude-code-ui/plugins/cloudcli-plugin-taskwork/manifest.json"
|
|
if [ -f "$installed" ]; then
|
|
echo "plugin $(node -p "require('$installed').version") installed in ~/.claude-code-ui/plugins"
|
|
else
|
|
echo "plugin not installed — ./scripts/link-plugin.sh"
|
|
fi
|
|
}
|
|
|
|
case "${1:-start}" in
|
|
start) start_host ;;
|
|
stop) stop_host ;;
|
|
restart) stop_host; start_host ;;
|
|
status) show_status ;;
|
|
logs) tail -f "$LOG_FILE" ;;
|
|
*) echo "usage: $0 [start|stop|restart|status|logs]" >&2; exit 1 ;;
|
|
esac
|