No description
- TypeScript 95.7%
- JavaScript 4.3%
Peto asked "ukaz nahlad" expecting a clickable link he could open on his
phone; Mia replied with screenshots only and refused to share a URL. That
was technically correct under rule 1 ("no hosting/URL technicalities") but
operationally wrong — the customer's preview URL (<slug>.preview.pk01.sk)
is a clean, customer-friendly subdomain on Peter's own infrastructure, not
an internal API path. Add an explicit carve-out in rule 1 so Mia can send
both the screenshot AND the URL when the customer asks for "nahlad" or
"link" or "preview", while still forbidding pr-N preview URLs, /api/...
paths, git links, and admin URLs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| bin | ||
| prompts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| ecosystem.config.cjs | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
kokpit-wa-bridge
Standalone WhatsApp ↔ Paperclip ↔ Claude bridge for PK01.
Replaces the old paperclip-whatsapp-bridge plugin (retired 2026-05-08 after multipart/permission issues — see ~/.claude/plans/ten-7-je-asi-playful-dahl.md for context).
What it does
- Connects to WhatsApp via Baileys 7.x with on-disk auth state in
~/.kokpit-wa-bridge/auth/. - For each registered customer (= one WhatsApp group), forwards every inbound message into a fixed Paperclip "channel issue" as a comment, with media uploaded as attachments.
- Asks Claude (via the local
claudeCLI) for a customer-facing reply in the Liaison persona, posts it as a Paperclip comment. - Polls Paperclip every 5 s for new comments on each channel issue and forwards customer-facing ones to WhatsApp. Comments containing
<!-- internal -->stay internal; comments shaped**Name (+phone):** …(our own inbound echoes) are filtered out as anti-loop.
Architecture
Single Node 22+ process under PM2. SQLite on disk for customer registry, dedup, polling cursor. No plugin SDK, no embedded postgres dependency at runtime (only used once for auth-state migration). Multipart uploads to Paperclip via axios + form-data — the combination that the multer middleware actually accepts (Node native FormData + Blob does not work).
~/projects/kokpit-wa-bridge/
├── src/
│ ├── index.ts # entry, wires everything
│ ├── config.ts # env vars, customer seed
│ ├── db.ts # SQLite schema + seed
│ ├── customers.ts # registry CRUD
│ ├── dedup.ts # waMsgId tracking, outbound cursor
│ ├── format.ts # canonical comment + media-tag formatting
│ ├── wa-client.ts # Baileys socket wrapper
│ ├── paperclip.ts # axios HTTP client
│ ├── inbound.ts # WA → Paperclip pipeline
│ ├── liaison.ts # claude CLI → reply comment
│ ├── outbound.ts # polling loop, Paperclip → WA
│ └── logger.ts # pino
├── bin/
│ ├── migrate-baileys-auth.ts # one-shot, paperclip postgres → fs
│ ├── add-customer.ts # CLI: add to registry
│ └── list-customers.ts # CLI: dump registry
├── prompts/
│ └── liaison.md # Claude system prompt (customer-facing voice)
└── data/ # gitignored runtime state (sqlite db)
Setup (first install)
pnpm install(ornpm install)cp .env.example .envand fill inPAPERCLIP_API_KEY(board token),WA_PHONE_NUMBER. Anthropic auth comes from your localclaudeCLI — no API key needed.npm run build- Migrate Baileys auth state from the old paperclip plugin so you don't re-pair via QR. (One-shot — only needed during the original 2026-05-08 cutover. Already done; keep notes for the next time we need to extract Baileys creds: query the
plugin_statetable wherenamespace='wa-auth', write each row'svalue_jsonto~/.kokpit-wa-bridge/auth/${state_key with -}.json. The script that did this lives only in git history at commit before 0.1.1.) - Cutover (uninstall old plugin, start new bridge):
paperclipai plugin uninstall whatsapp-bridge --force --api-base https://sitecraft.sk pm2 start ecosystem.config.cjs pm2 save pm2 logs kokpit-wa-bridge --lines 20— verify "WhatsApp paired" appears.
Add a customer
node dist/bin/add-customer.js \
--slug new-customer \
--jid 1203...@g.us \
--channel-issue <paperclip-issue-uuid> \
--project <paperclip-project-uuid> \
--goal <paperclip-goal-uuid> \
--git ssh://git@git.kokpit.sk:2222/forge-admin/<repo>.git
pm2 restart kokpit-wa-bridge
You'll need to:
- Create the Paperclip Project + Goal + channel issue (
originKind=plugin:whatsapp-bridge:channel,originId=<jid>) yourself in the dashboard or via API. - Make sure your WhatsApp phone has joined that group already (the bridge doesn't auto-join).
- Restart the bridge so the in-memory customer registry rehydrates from SQLite.
Operations
pm2 logs kokpit-wa-bridge --lines 50
pm2 restart kokpit-wa-bridge
pm2 stop kokpit-wa-bridge
sqlite3 ~/.kokpit-wa-bridge/kokpit.sqlite "SELECT slug, wa_group_jid FROM customers;"
Re-pair (when WhatsApp logs out)
If pm2 logs shows kind: "logged_out" or you see a fresh QR string in the logs:
pm2 restart kokpit-wa-bridgepm2 logs kokpit-wa-bridge --lines 5— copy the QR string out of the log.- Open WhatsApp on phone → Linked Devices → Link a device → scan.
License
MIT — Peter Kramarik peter.kramarik@pk01.sk