Files
nanoclaw/setup/add-telegram.sh
T
gavrielc 6e0d742a7f feat(setup): brand setup:auto with @clack/prompts + brand palette
Wraps the scripted setup flow in a branded, friendly UI. Each step runs
under a clack spinner with elapsed time; child stdout/stderr is captured
quietly and dumped only on failure. Interactive children (token paste,
Anthropic OAuth) bypass the spinner and inherit the TTY.

- intro: NanoClaw wordmark + brand-cyan accent chip, truecolor with
  kleur fallback and NO_COLOR / non-TTY awareness
- pair-telegram: emits PAIR_TELEGRAM_CODE / _ATTEMPT status blocks only;
  auto.ts renders clack notes + "received X — doesn't match" checkpoints
- streaming status-block parser handles mid-step events without waiting
  for the child to exit
- terminal-block detection now finds any block with a STATUS field
  (handles MOUNTS emitting CONFIGURE_MOUNTS, etc.) and treats 'skipped'
  as a success variant with an optional friendlier label

Also fixes a latent bash bug where `$VAR…` (unbraced followed by a
multi-byte Unicode character) pulled ellipsis bytes into the variable
name lookup and tripped `set -u`. Braced `${VAR}` in add-telegram.sh
and register-claude-token.sh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:09:26 +03:00

174 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Install the Telegram adapter (Phase A of the /add-telegram skill), collect
# the bot token, write .env + data/env/env, and restart the service so the
# new adapter is live. Idempotent.
#
# Pair-telegram (the interactive code-sending step) is run separately by the
# caller (setup/auto.ts) so it can stream status blocks to the user.
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"
# Keep in sync with .claude/skills/add-telegram/SKILL.md.
ADAPTER_VERSION="@chat-adapter/telegram@4.26.0"
CHANNELS_BRANCH="origin/channels"
need_install() {
[[ ! -f src/channels/telegram.ts ]] && return 0
! grep -q "^import './telegram.js';" src/channels/index.ts 2>/dev/null && return 0
return 1
}
if need_install; then
echo "[add-telegram] Fetching channels branch…"
git fetch origin channels >/dev/null 2>&1
# pair-telegram.ts is maintained in this branch (setup-auto), so it's NOT
# in this list — do not overwrite the local version with the channels copy.
echo "[add-telegram] Copying adapter files from ${CHANNELS_BRANCH}"
for f in \
src/channels/telegram.ts \
src/channels/telegram-pairing.ts \
src/channels/telegram-pairing.test.ts \
src/channels/telegram-markdown-sanitize.ts \
src/channels/telegram-markdown-sanitize.test.ts
do
git show "$CHANNELS_BRANCH:$f" > "$f"
done
# Append self-registration import if missing.
if ! grep -q "^import './telegram.js';" src/channels/index.ts; then
echo "import './telegram.js';" >> src/channels/index.ts
fi
# Register pair-telegram step if not already in the STEPS map.
# Uses node (not sed) since sed's in-place + escape semantics differ
# between BSD (macOS) and GNU.
node -e '
const fs = require("fs");
const p = "setup/index.ts";
let s = fs.readFileSync(p, "utf-8");
if (!s.includes("\047pair-telegram\047")) {
s = s.replace(
/(register: \(\) => import\(\x27\.\/register\.js\x27\),)/,
"$1\n \x27pair-telegram\x27: () => import(\x27./pair-telegram.js\x27),"
);
fs.writeFileSync(p, s);
}
'
echo "[add-telegram] Installing ${ADAPTER_VERSION}"
pnpm install "$ADAPTER_VERSION"
echo "[add-telegram] Building…"
pnpm run build >/dev/null
else
echo "[add-telegram] Adapter files already installed — skipping install phase."
fi
# Token collection.
if grep -q '^TELEGRAM_BOT_TOKEN=.' .env 2>/dev/null; then
echo "[add-telegram] TELEGRAM_BOT_TOKEN already set in .env — skipping token prompt."
else
cat <<'EOF'
── Create a Telegram bot ──────────────────────────────────────
1. Open Telegram and message @BotFather
2. Send: /newbot
3. Follow the prompts (bot name, username ending in "bot")
4. Copy the token it gives you (format: <digits>:<chars>)
Optional but recommended for groups:
5. @BotFather → /mybots → your bot → Bot Settings → Group Privacy → OFF
EOF
echo "Paste your TELEGRAM_BOT_TOKEN and press Enter."
echo "Nothing will appear on the screen as you paste — that's intentional."
echo "Paste once, then just press Enter to submit."
read -r -s -p "> " TOKEN </dev/tty
echo
if [[ -z "$TOKEN" ]]; then
echo "[add-telegram] No token entered. Aborting." >&2
exit 1
fi
# Telegram bot tokens: <digits>:<35+ base64url-ish chars>.
if [[ ! "$TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]{35,}$ ]]; then
echo "[add-telegram] Token format looks wrong (expected <digits>:<chars>). Aborting." >&2
exit 1
fi
touch .env
if grep -q '^TELEGRAM_BOT_TOKEN=' .env; then
awk -v tok="$TOKEN" '/^TELEGRAM_BOT_TOKEN=/{print "TELEGRAM_BOT_TOKEN=" tok; next} {print}' \
.env > .env.tmp && mv .env.tmp .env
else
echo "TELEGRAM_BOT_TOKEN=$TOKEN" >> .env
fi
fi
# Validate the token via getMe so a typo surfaces before we restart the
# service, and capture the bot's username for the deep link.
TELEGRAM_BOT_TOKEN_VALUE="$(grep '^TELEGRAM_BOT_TOKEN=' .env | head -1 | cut -d= -f2-)"
BOT_USERNAME=""
if [[ -n "$TELEGRAM_BOT_TOKEN_VALUE" ]]; then
INFO=$(curl -fsS --max-time 8 \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN_VALUE}/getMe" 2>/dev/null || true)
if echo "$INFO" | grep -q '"ok":true'; then
# Crude JSON parse — the response is always a flat object here.
BOT_USERNAME=$(echo "$INFO" | sed -nE 's/.*"username":"([^"]+)".*/\1/p')
if [[ -n "$BOT_USERNAME" ]]; then
echo "[add-telegram] Token validated — bot is @${BOT_USERNAME}."
fi
else
echo "[add-telegram] Warning: getMe did not return ok. Continuing, but the token may be wrong."
fi
fi
# Container reads from data/env/env (the host mounts it).
mkdir -p data/env
cp .env data/env/env
# Deep-link into the bot's chat in the installed Telegram app so the user
# is already on the right screen when pair-telegram prints the code. Also
# always print the URL so headless / remote-SSH users can open it manually.
if [[ -n "$BOT_USERNAME" ]]; then
BOT_URL="https://t.me/${BOT_USERNAME}"
case "$(uname -s)" in
Darwin)
open "tg://resolve?domain=${BOT_USERNAME}" >/dev/null 2>&1 \
|| open "$BOT_URL" >/dev/null 2>&1 \
|| true
;;
Linux)
xdg-open "tg://resolve?domain=${BOT_USERNAME}" >/dev/null 2>&1 \
|| xdg-open "$BOT_URL" >/dev/null 2>&1 \
|| true
;;
esac
echo "[add-telegram] Bot chat: ${BOT_URL}"
echo "[add-telegram] (If Telegram didn't open automatically, click the link above.)"
fi
echo "[add-telegram] Restarting service so the new adapter picks up the token…"
case "$(uname -s)" in
Darwin)
launchctl kickstart -k "gui/$(id -u)/com.nanoclaw" >/dev/null 2>&1 || true
;;
Linux)
systemctl --user restart nanoclaw >/dev/null 2>&1 \
|| sudo systemctl restart nanoclaw >/dev/null 2>&1 \
|| true
;;
esac
# Give the Telegram adapter a moment to finish starting before pair-telegram
# begins polling for the user's code message.
sleep 5
echo "[add-telegram] Install + credentials complete."