#!/bin/sh # shipd chat — terminal-to-terminal with the shipd team. No dashboard, no email. # # curl -fsSL https://shipd.aikaara.com/chat | sh # your thread # curl -fsSL https://shipd.aikaara.com/chat | sh -s -- @bob # team: reply into bob's thread # # Uses ~/.shipd/token (written by `join`) or $SHIPD_TOKEN. Long-polls; ctrl-c to leave. set -u API="${SHIPD_API:-https://api.aikaara.com/api/v1/mcp}" TOKEN="${SHIPD_TOKEN:-$(cat "$HOME/.shipd/token" 2>/dev/null || true)}" [ -n "$TOKEN" ] || { echo "shipd: no token — run: curl -fsSL https://shipd.aikaara.com/join | sh" >&2; exit 1; } TO="${1:-}" command -v python3 >/dev/null 2>&1 || { echo "shipd: needs python3" >&2; exit 1; } AUTH="Authorization: Bearer $TOKEN" CUR=0 first=$(curl -sS "$API/chat?since=0&wait=0" -H "$AUTH") ROLE=$(printf '%s' "$first" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("role",""))' 2>/dev/null) ME=$(printf '%s' "$first" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("me",""))' 2>/dev/null) [ -n "$ROLE" ] || { echo "shipd: $(printf '%s' "$first" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("error","auth failed"))')" >&2; exit 1; } render() { python3 -c ' import sys,json d=json.load(sys.stdin); role=sys.argv[1] for m in d.get("messages",[]): tag = ("@"+m["developer"]+" ") if role=="team" else "" who = m["who"] if m["from"]=="dev" else "team/"+(m["who"] or "") print("\033[2m%s\033[0m %s\033[1m%s\033[0m: %s" % (m["at"][11:16], tag, who, m["body"])) print(d.get("cursor",0), file=sys.stderr) ' "$ROLE" } echo "shipd chat · you are $ME ($ROLE)${TO:+ · replying to $TO} · ctrl-c to leave" >&2 # history CUR=$(printf '%s' "$first" | render 2>&1 >/dev/tty | tail -1) poll() { while :; do r=$(curl -sS "$API/chat?since=$CUR&wait=20" -H "$AUTH") || { sleep 2; continue; } n=$(printf '%s' "$r" | render 2>&1 >/dev/tty | tail -1) [ -n "$n" ] && CUR=$n && echo "$n" > "$HOME/.shipd/.cursor" done } mkdir -p "$HOME/.shipd" # `curl ... | sh` leaves stdin pointing at the script itself, so the input loop # below reads the keyboard explicitly. Without that, the shell's own unread tail # gets posted as your first message and the session ends immediately at EOF. [ -r /dev/tty ] || { echo "shipd: no terminal — run this from an interactive shell" >&2; exit 1; } poll & PP=$! trap 'kill $PP 2>/dev/null; echo; exit 0' INT TERM while IFS= read -r line; do [ -z "$line" ] && continue body=$(python3 -c 'import json,sys; o={"body":sys.argv[1]} if sys.argv[2]: o["developer"]=sys.argv[2].lstrip("@") print(json.dumps(o))' "$line" "$TO") curl -sS -o /dev/null -X POST "$API/chat" -H "$AUTH" -H 'Content-Type: application/json' -d "$body" done /dev/null