Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
201 changes: 201 additions & 0 deletions start_local.sh
Original file line number Diff line number Diff line change
@@ -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."
Loading