feat(setup): optional Telegram wiring in setup:auto
After cli-agent, prompt the user to connect a messaging app. For now only Telegram is offered; "skip" falls through to the existing CLI flow. setup/add-telegram.sh runs the scriptable half of /add-telegram: fetch the channels branch, copy the adapter + pair-telegram files, append the self-registration import, install @chat-adapter/telegram@4.26.0 (pinned to match the skill), rebuild, collect TELEGRAM_BOT_TOKEN via silent paste, write .env + data/env/env, and kick the service so the new adapter is live. Idempotent throughout. setup:auto then runs the existing `pair-telegram` step with --intent main. The step emits the 4-digit code in its status stream, which is already forwarded to stdout by runStep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
134
setup/add-telegram.sh
Executable file
134
setup/add-telegram.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/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
|
||||
[[ ! -f setup/pair-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
|
||||
|
||||
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 \
|
||||
setup/pair-telegram.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
|
||||
|
||||
# Container reads from data/env/env (the host mounts it).
|
||||
mkdir -p data/env
|
||||
cp .env data/env/env
|
||||
|
||||
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."
|
||||
@@ -11,8 +11,8 @@
|
||||
* interactive prompt before cli-agent. If unset,
|
||||
* the driver asks, defaulting to $USER.
|
||||
* NANOCLAW_SKIP comma-separated step names to skip
|
||||
* (environment|container|onecli|auth|
|
||||
* mounts|service|cli-agent|verify)
|
||||
* (environment|container|onecli|auth|mounts|
|
||||
* service|cli-agent|channel|verify)
|
||||
*
|
||||
* Timezone is not configured here — it defaults to the host system's TZ.
|
||||
* Run `pnpm exec tsx setup/index.ts --step timezone -- --tz <zone>` later
|
||||
@@ -130,6 +130,19 @@ async function askDisplayName(fallback: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
async function askChannelChoice(): Promise<'telegram' | 'skip'> {
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
try {
|
||||
console.log('\nConnect a messaging app so you can chat from your phone?');
|
||||
console.log(' 1) Telegram');
|
||||
console.log(' 2) Skip — just use the CLI for now');
|
||||
const answer = (await rl.question('Choose [1/2]: ')).trim();
|
||||
return answer === '1' ? 'telegram' : 'skip';
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
function runBashScript(relPath: string): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn('bash', [relPath], { stdio: 'inherit' });
|
||||
@@ -257,6 +270,34 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip.has('channel')) {
|
||||
const choice = await askChannelChoice();
|
||||
if (choice === 'telegram') {
|
||||
const installCode = await runBashScript('setup/add-telegram.sh');
|
||||
if (installCode !== 0) {
|
||||
fail(
|
||||
'Telegram install failed.',
|
||||
'Re-run `bash setup/add-telegram.sh`, then `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main`.',
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\n[setup:auto] Pairing Telegram. A 4-digit code will appear below.\n' +
|
||||
' From Telegram, send just those 4 digits to your bot\n' +
|
||||
' (DM the bot for a personal chat, or prefix with your\n' +
|
||||
' bot handle in a group with privacy on).\n',
|
||||
);
|
||||
|
||||
const pair = await runStep('pair-telegram', ['--intent', 'main']);
|
||||
if (!pair.ok) {
|
||||
fail(
|
||||
'Telegram pairing failed.',
|
||||
'Re-run `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main`.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip.has('verify')) {
|
||||
const res = await runStep('verify');
|
||||
if (!res.ok) {
|
||||
|
||||
Reference in New Issue
Block a user