From fb0188816921cc8b071edb5c6b259b2f9f2f39f5 Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Fri, 17 Jul 2026 16:24:23 -0700 Subject: [PATCH] feat: Added the start_local.sh quick-start script --- README.md | 19 +++++ start_local.sh | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100755 start_local.sh diff --git a/README.md b/README.md index 30bfd11..a296eaa 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,25 @@ workflow — research, code generation, document building, scheduled automation, - The **`pi`** agent binary on your `PATH` (or set `PI_BIN` to its location) - Access to an LLM proxy for `pi` to call (see `PI_PROXY_URL` / `PI_PROXY_API_KEY` below) +### Quick start + +From a fresh clone to a running shell in one command: + +```bash +OPENAI_API_KEY=sk-... ./start_local.sh # or ANTHROPIC_API_KEY=sk-ant-... +``` + +`start_local.sh` installs dependencies, fetches the `pi` binary if it isn't already on your +`PATH`, seeds the example agent bundles, writes a local `.env`, and starts the UI at +**http://127.0.0.1:9010** (server on port `8787`, Tangle API assumed at +**http://127.0.0.1:8000**). + +LLM access is read from well-known provider variables: set `OPENAI_API_KEY` and/or +`ANHROPIC_API_KEY` (with optional `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`) to call a provider +directly, or set `PI_PROXY_URL` + `PI_PROXY_API_KEY` to route through an LLM gateway.T Ports, +model, and paths are overridable — see the top of the script. Prefer to run the steps yourself? +Use the manual flow below. + ### Install and run ```bash diff --git a/start_local.sh b/start_local.sh new file mode 100755 index 0000000..d3d6329 --- /dev/null +++ b/start_local.sh @@ -0,0 +1,201 @@ +#!/usr/bin/env bash +# +# start_local.sh — configure and run a local Tangent Shell out of the box. +# +# Takes a fresh checkout to a running shell: +# UI http://127.0.0.1:9010 (Vite dev server) +# Server http://127.0.0.1:8787 (orchestrator; proxied by the UI) +# Tangle http://127.0.0.1:8000 (assumed Tangle API base) +# +# LLM access is wired from well-known provider env vars — set OPENAI_API_KEY +# and/or ANTHROPIC_API_KEY (optionally OPENAI_BASE_URL / ANTHROPIC_BASE_URL) to +# talk to a provider directly, or set PI_PROXY_URL + PI_PROXY_API_KEY to route +# through an LLM gateway instead. +# +# Everything is overridable by exporting the variable before running, e.g.: +# WEB_PORT=3000 SERVER_PORT=8888 TANGLE_API_URL=http://127.0.0.1:9000 ./start_local.sh +# PI_MODEL=gpt-4o ANTHROPIC_API_KEY=sk-ant-... ./start_local.sh + +set -euo pipefail + +# --- Resolve repo root (this script lives at the root) ----------------------- +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$REPO_ROOT" + +# --- Tunables ---------------------------------------------------------------- +WEB_HOST="${WEB_HOST:-127.0.0.1}" +WEB_PORT="${WEB_PORT:-9010}" +SERVER_PORT="${SERVER_PORT:-8787}" +TANGLE_API_URL="${TANGLE_API_URL:-http://127.0.0.1:8000}" +TANGLE_BASE_URL="${TANGLE_BASE_URL:-$TANGLE_API_URL}" +DEFAULT_BUNDLE_ID="${VITE_DEFAULT_SESSION_BUNDLE_ID:-tangle-oss}" +PI_PKG="@earendil-works/pi-coding-agent" + +log() { printf '\033[1;36m[start_local]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[start_local] warning:\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31m[start_local] error:\033[0m %s\n' "$*" >&2; exit 1; } + +emit_if_set() { + local name="$1" + local val="${!name:-}" + if [ -n "$val" ]; then printf '%s=%s\n' "$name" "$val"; fi +} + +# --- Preflight --------------------------------------------------------------- +command -v node >/dev/null 2>&1 || die "node is required (v24+; see https://nodejs.org)." +command -v pnpm >/dev/null 2>&1 || die "pnpm is required (npm i -g pnpm@10.28.0; see https://pnpm.io)." + +# --- Provider / model wiring ------------------------------------------------- +# Gateway mode when both PI_PROXY_* are set; otherwise direct from native keys. +PI_PROXY_URL="${PI_PROXY_URL:-}" +PI_PROXY_API_KEY="${PI_PROXY_API_KEY:-}" +PI_PROVIDER="${PI_PROVIDER:-}" +PI_MODEL="${PI_MODEL:-}" + +if [ -n "$PI_PROXY_URL" ] && [ -n "$PI_PROXY_API_KEY" ]; then + : "${PI_PROVIDER:=openai}" + : "${PI_MODEL:=gpt-5.5}" + log "LLM: gateway mode via PI_PROXY_URL (provider=$PI_PROVIDER, model=$PI_MODEL)." +elif [ -n "${OPENAI_API_KEY:-}" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; then + if [ -z "$PI_PROVIDER" ]; then + if [ -n "${OPENAI_API_KEY:-}" ]; then PI_PROVIDER="openai"; else PI_PROVIDER="anthropic"; fi + fi + if [ -z "$PI_MODEL" ]; then + case "$PI_PROVIDER" in + anthropic) PI_MODEL="claude-sonnet-4-6" ;; + *) PI_MODEL="gpt-5.5" ;; + esac + fi + log "LLM: direct mode (provider=$PI_PROVIDER, model=$PI_MODEL). Override with PI_MODEL/PI_PROVIDER." +else + : "${PI_PROVIDER:=openai}" + : "${PI_MODEL:=gpt-5.5}" + warn "No LLM credentials found — the UI will start, but chatting needs one of:" + warn " export OPENAI_API_KEY=... (or ANTHROPIC_API_KEY=...)" + warn " or set PI_PROXY_URL + PI_PROXY_API_KEY to use an LLM gateway." +fi + +# --- Ensure a `pi` binary; export PI_BIN ------------------------------------- +resolve_pi() { + if [ -n "${PI_BIN:-}" ] && command -v "$PI_BIN" >/dev/null 2>&1; then + command -v "$PI_BIN"; return 0 + fi + if command -v pi >/dev/null 2>&1; then command -v pi; return 0; fi + if [ -x "$REPO_ROOT/node_modules/.bin/pi" ]; then + echo "$REPO_ROOT/node_modules/.bin/pi"; return 0 + fi + if [ -x "$REPO_ROOT/.pi/node_modules/.bin/pi" ]; then + echo "$REPO_ROOT/.pi/node_modules/.bin/pi"; return 0 + fi + return 1 +} + +if PI_BIN="$(resolve_pi)"; then + log "pi: $PI_BIN" +else + log "pi not found — installing $PI_PKG into .pi/ ..." + mkdir -p "$REPO_ROOT/.pi" + printf '*\n' > "$REPO_ROOT/.pi/.gitignore" + if [ ! -f "$REPO_ROOT/.pi/package.json" ]; then + printf '{\n "name": "tangent-pi-runtime",\n "private": true\n}\n' > "$REPO_ROOT/.pi/package.json" + fi + ( cd "$REPO_ROOT/.pi" && pnpm add --ignore-workspace "$PI_PKG" ) \ + || die "failed to install $PI_PKG via pnpm." + PI_BIN="$REPO_ROOT/.pi/node_modules/.bin/pi" + [ -x "$PI_BIN" ] || die "pi install finished but $PI_BIN is missing." + log "pi installed: $PI_BIN" +fi +export PI_BIN + +# --- Write .env (only if absent; never clobber the user's) ------------------- +ENV_FILE="$REPO_ROOT/.env" +if [ -f "$ENV_FILE" ]; then + log ".env already exists — leaving it untouched." +else + log "Writing .env ..." + { + echo "# Generated by start_local.sh — local Tangent Shell config." + echo "# Safe to edit; delete to regenerate on the next run." + echo + echo "PORT=$SERVER_PORT" + echo "SESSIONS_ROOT=$REPO_ROOT/.sessions" + echo "SESSIONS_DB=$REPO_ROOT/.sessions/tangent.db" + echo "AGENT_BUNDLES_ROOT=$REPO_ROOT/.agent-bundles" + echo "GLOBAL_MEMORY_DIR=$REPO_ROOT/.memory" + echo + echo "TANGLE_API_URL=$TANGLE_API_URL" + echo "TANGLE_BASE_URL=$TANGLE_BASE_URL" + echo + echo "PI_BIN=$PI_BIN" + echo "PI_PROVIDER=$PI_PROVIDER" + echo "PI_MODEL=$PI_MODEL" + if [ -n "$PI_PROXY_URL" ] && [ -n "$PI_PROXY_API_KEY" ]; then + echo "PI_PROXY_URL=$PI_PROXY_URL" + echo "PI_PROXY_API_KEY=$PI_PROXY_API_KEY" + fi + emit_if_set OPENAI_API_KEY + emit_if_set OPENAI_BASE_URL + emit_if_set ANTHROPIC_API_KEY + emit_if_set ANTHROPIC_BASE_URL + emit_if_set GEMINI_API_KEY + emit_if_set GOOGLE_API_KEY + echo + echo "VITE_DEFAULT_SESSION_BUNDLE_ID=$DEFAULT_BUNDLE_ID" + } > "$ENV_FILE" +fi + +# --- Install deps + seed the example bundles --------------------------------- +log "Installing workspace dependencies (pnpm install) ..." +pnpm install + +log "Seeding example agent bundles (so \"New session\" works) ..." +pnpm seed + +# --- Export runtime config for the child processes --------------------------- +export PORT="$SERVER_PORT" +export SESSIONS_ROOT="$REPO_ROOT/.sessions" +export SESSIONS_DB="$REPO_ROOT/.sessions/tangent.db" +export AGENT_BUNDLES_ROOT="$REPO_ROOT/.agent-bundles" +export GLOBAL_MEMORY_DIR="$REPO_ROOT/.memory" +export TANGLE_API_URL TANGLE_BASE_URL +export PI_PROVIDER PI_MODEL +export API_TARGET="http://localhost:$SERVER_PORT" +export VITE_DEFAULT_SESSION_BUNDLE_ID="$DEFAULT_BUNDLE_ID" +if [ -n "$PI_PROXY_URL" ] && [ -n "$PI_PROXY_API_KEY" ]; then + export PI_PROXY_URL PI_PROXY_API_KEY +fi + +# --- Launch server + web; tear both down on exit ----------------------------- +PIDS="" +cleanup() { + trap - INT TERM EXIT + log "Shutting down ..." + for pid in $PIDS; do kill "$pid" >/dev/null 2>&1 || true; done + sleep 1 + for pid in $PIDS; do kill -9 "$pid" >/dev/null 2>&1 || true; done +} +trap cleanup INT TERM EXIT + +log "Starting orchestrator server on http://127.0.0.1:$SERVER_PORT ..." +pnpm --filter @tangent/server dev & +SERVER_PID=$! +PIDS="$SERVER_PID" + +log "Starting web UI on http://$WEB_HOST:$WEB_PORT ..." +pnpm --filter @tangent/web exec vite --host "$WEB_HOST" --port "$WEB_PORT" --strictPort & +WEB_PID=$! +PIDS="$PIDS $WEB_PID" + +printf '\n' +log "Tangent Shell is starting:" +log " UI: http://127.0.0.1:$WEB_PORT" +log " Server: http://127.0.0.1:$SERVER_PORT" +log " Tangle: $TANGLE_API_URL" +log "Press Ctrl-C to stop." +printf '\n' + +# macOS ships bash 3.2 (no `wait -n`), so poll until either child exits. +while kill -0 "$SERVER_PID" >/dev/null 2>&1 && kill -0 "$WEB_PID" >/dev/null 2>&1; do + sleep 1 +done +warn "A dev server exited — stopping the other."