WhatsApp (community/Baileys) joins the setup:auto channel picker, with
the same clack-native UX discipline as Telegram and Discord:
- setup/channels/whatsapp.ts — driver. Collects auth method (QR terminal
or pairing code), runs the auth step, renders QR blocks in-place with
ANSI cursor-rewind on rotation so the terminal doesn't fill up with
stale codes, reads creds.me.id for the bot phone, restarts the service,
asks for the operator's personal phone (defaulting to the authed
number), writes ASSISTANT_HAS_OWN_NUMBER=true when they differ
(dedicated mode), and hands off to init-first-agent.
- setup/whatsapp-auth.ts — forked standalone auth step. Channels-branch
version had a browser-QR path with an HTTP server + <canvas> QR
renderer; stripped entirely (headless/SSH users hit dead ends too
often, and the extra deps complicate install). The remaining terminal
QR emits raw QR strings in WHATSAPP_AUTH_QR blocks so the parent
driver owns the rendering. Pairing-code path retained. Status blocks
now use the runner's vocabulary (success/skipped/failed) so spawnStep
sets ok correctly; WhatsApp-specific UI text ("WhatsApp linked", "You
chat") lives in the driver.
- setup/add-whatsapp.sh — non-interactive installer, mirror of
add-telegram.sh. Fetches the adapter + groups step from the channels
branch (whatsapp-auth.ts stays local, pair-telegram.ts pattern),
installs pinned baileys/qrcode/pino, registers the steps in
setup/index.ts's STEPS map. No service restart (adapter factory
returns null until creds exist).
Cross-channel fixes bundled:
- scripts/init-first-agent.ts: always addMember(user, agentGroup) for
the target user so subsequent wirings (not the first) pass the access
gate. Telegram wiring first → Discord/WhatsApp second was dropping
every inbound with accessReason='not_member' because only the first
user gets owner. namespacedPlatformId also passes through JID-format
raws (contains '@') so WhatsApp's bare <phone>@s.whatsapp.net matches
what the adapter stores.
- setup/service.ts: launchctl unload-then-load instead of bare load (bare
load errors 'already loaded' when a prior plist was cached, keeping
launchd on the OLD ProgramArguments even after the file on disk
changed). systemctl start → restart (start is a no-op on an active
unit, swallowing unit-file edits).
- setup/add-telegram.sh: removed the in-script open "tg://resolve"
block. The driver (setup/channels/telegram.ts) now owns the deep-link,
gated on a p.confirm so the browser can't steal focus unexpectedly.
- setup/channels/discord.ts + setup/channels/telegram.ts: every browser
open goes through confirmThenOpen (new shared helper in
setup/lib/browser.ts) — operator presses Enter before their browser
takes focus. Telegram switched from tg://resolve?domain= to
https://t.me/<bot> which works everywhere.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.9 KiB
Bash
Executable File
115 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Install the native WhatsApp (Baileys) adapter and its whatsapp-auth + groups
|
|
# setup steps. No credentials in env — WhatsApp uses linked-device auth, run
|
|
# by the whatsapp-auth step as a separate process. The adapter's factory
|
|
# returns null until store/auth/creds.json exists, so it's safe to install
|
|
# this before auth runs; the driver restarts the service *after* auth
|
|
# succeeds.
|
|
#
|
|
# Emits exactly one status block on stdout (ADD_WHATSAPP) at the end. All
|
|
# chatty progress messages go to stderr so setup:auto's raw-log capture sees
|
|
# the full story without cluttering the final block for the parser.
|
|
set -euo pipefail
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Keep in sync with .claude/skills/add-whatsapp/SKILL.md.
|
|
BAILEYS_VERSION="@whiskeysockets/baileys@6.17.16"
|
|
QRCODE_VERSION="qrcode@1.5.4"
|
|
QRCODE_TYPES_VERSION="@types/qrcode@1.5.6"
|
|
PINO_VERSION="pino@9.6.0"
|
|
CHANNELS_BRANCH="origin/channels"
|
|
|
|
emit_status() {
|
|
local status=$1 error=${2:-}
|
|
local already=${ADAPTER_ALREADY_INSTALLED:-false}
|
|
echo "=== NANOCLAW SETUP: ADD_WHATSAPP ==="
|
|
echo "STATUS: ${status}"
|
|
echo "ADAPTER_ALREADY_INSTALLED: ${already}"
|
|
[ -n "$error" ] && echo "ERROR: ${error}"
|
|
echo "=== END ==="
|
|
}
|
|
|
|
log() { echo "[add-whatsapp] $*" >&2; }
|
|
|
|
need_install() {
|
|
[ ! -f src/channels/whatsapp.ts ] && return 0
|
|
[ ! -f setup/groups.ts ] && return 0
|
|
! grep -q "^import './whatsapp.js';" src/channels/index.ts 2>/dev/null && return 0
|
|
! grep -q "'whatsapp-auth':" setup/index.ts 2>/dev/null && return 0
|
|
! grep -q "^ groups:" setup/index.ts 2>/dev/null && return 0
|
|
return 1
|
|
}
|
|
|
|
ADAPTER_ALREADY_INSTALLED=true
|
|
if need_install; then
|
|
ADAPTER_ALREADY_INSTALLED=false
|
|
log "Fetching channels branch…"
|
|
git fetch origin channels >&2 2>/dev/null || {
|
|
emit_status failed "git fetch origin channels failed"
|
|
exit 1
|
|
}
|
|
|
|
# whatsapp-auth.ts is maintained in this branch (setup-auto) — do not copy
|
|
# from channels. Matches the pair-telegram.ts pattern.
|
|
log "Copying adapter + group step from ${CHANNELS_BRANCH}…"
|
|
git show "${CHANNELS_BRANCH}:src/channels/whatsapp.ts" > src/channels/whatsapp.ts
|
|
git show "${CHANNELS_BRANCH}:setup/groups.ts" > setup/groups.ts
|
|
|
|
# Append self-registration import if missing.
|
|
if ! grep -q "^import './whatsapp.js';" src/channels/index.ts; then
|
|
echo "import './whatsapp.js';" >> src/channels/index.ts
|
|
fi
|
|
|
|
# Register the setup steps in setup/index.ts's STEPS map. node (not sed) —
|
|
# 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");
|
|
let changed = false;
|
|
if (!s.includes("\047whatsapp-auth\047:")) {
|
|
s = s.replace(
|
|
/(register: \(\) => import\(\x27\.\/register\.js\x27\),)/,
|
|
"$1\n \x27whatsapp-auth\x27: () => import(\x27./whatsapp-auth.js\x27),"
|
|
);
|
|
changed = true;
|
|
}
|
|
if (!/^\s*groups:\s/m.test(s)) {
|
|
s = s.replace(
|
|
/(register: \(\) => import\(\x27\.\/register\.js\x27\),)/,
|
|
"$1\n groups: () => import(\x27./groups.js\x27),"
|
|
);
|
|
changed = true;
|
|
}
|
|
if (changed) fs.writeFileSync(p, s);
|
|
'
|
|
|
|
log "Installing Baileys + QR + pino (pinned)…"
|
|
pnpm install \
|
|
"${BAILEYS_VERSION}" \
|
|
"${QRCODE_VERSION}" \
|
|
"${QRCODE_TYPES_VERSION}" \
|
|
"${PINO_VERSION}" \
|
|
>&2 2>/dev/null || {
|
|
emit_status failed "pnpm install failed"
|
|
exit 1
|
|
}
|
|
|
|
log "Building…"
|
|
pnpm run build >&2 2>/dev/null || {
|
|
emit_status failed "pnpm run build failed"
|
|
exit 1
|
|
}
|
|
else
|
|
log "Adapter + setup steps already installed — skipping install phase."
|
|
fi
|
|
|
|
# No service restart here — the adapter factory returns null without
|
|
# store/auth/creds.json, so restarting now would no-op. The driver restarts
|
|
# the service AFTER whatsapp-auth completes so the adapter picks up creds.
|
|
|
|
emit_status success
|