No description
  • TypeScript 94.1%
  • JavaScript 5.9%
Find a file
2026-07-02 09:48:54 +00:00
.claude/skills pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
bin pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
docs docs: rewrite Mia Notifications guide for nontechnical users 2026-05-24 14:22:17 +00:00
extensions pi-team: load meridian extension for subagents on meridian provider 2026-06-19 18:16:31 +00:00
prompts pi-team: inject customer runtime secrets into previews 2026-05-24 13:16:27 +00:00
src mia: share preview login credentials with the customer 2026-07-02 09:48:32 +00:00
.env.example pi-team: inject customer runtime secrets into previews 2026-05-24 13:16:27 +00:00
.gitignore gitignore: drop accidental /tmp entry 2026-05-08 21:30:17 +00:00
AGENTS.md pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
CLAUDE.md pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
ecosystem.config.cjs liaison: discover chromium dynamically; drop hardcoded baskervil paths 2026-05-09 17:40:09 +00:00
package-lock.json pi-team: inject customer runtime secrets into previews 2026-05-24 13:16:27 +00:00
package.json pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
README.md pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
solarcompany-maj-2026-analyza-zmien.md pi-team: preview cleanup, notifications, and operator tooling 2026-06-23 19:56:10 +00:00
tsconfig.json Initial implementation: kokpit-wa-bridge v0.1.0 2026-05-08 19:54:33 +00:00

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 claude CLI) 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
│   ├── create-notification-token.ts # CLI: create Mia Notifications token
│   └── list-customers.ts       # CLI: dump registry
├── prompts/
│   └── liaison.md              # Claude system prompt (customer-facing voice)
└── data/                       # gitignored runtime state (sqlite db)

Setup (first install)

  1. pnpm install (or npm install)
  2. cp .env.example .env and fill in PAPERCLIP_API_KEY (board token), WA_PHONE_NUMBER. Anthropic auth comes from your local claude CLI — no API key needed.
  3. npm run build
  4. 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_state table where namespace='wa-auth', write each row's value_json to ~/.kokpit-wa-bridge/auth/${state_key with -}.json. The script that did this lives only in git history at commit before 0.1.1.)
  5. Cutover (uninstall old plugin, start new bridge):
    paperclipai plugin uninstall whatsapp-bridge --force --api-base http://127.0.0.1:3100
    pm2 start ecosystem.config.cjs
    pm2 save
    
  6. 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:

  1. Create the Paperclip Project + Goal + channel issue (originKind=plugin:whatsapp-bridge:channel, originId=<jid>) yourself in the dashboard or via API.
  2. Make sure your WhatsApp phone has joined that group already (the bridge doesn't auto-join).
  3. Restart the bridge so the in-memory customer registry rehydrates from SQLite.

Mia Notifications API

The bridge can accept application events and queue them to the customer's WhatsApp group through the same outbox Mia uses.

Customer-facing guide with examples: docs/mia-notifications-zakaznici.md.

Create a customer-scoped integration token:

node dist/bin/create-notification-token.js --slug <customer-slug> --name <integration-name>

Use the returned token as an application secret, usually named MIA_NOTIFICATIONS_TOKEN.

curl -X POST http://127.0.0.1:4112/v1/events \
  -H "Authorization: Bearer $MIA_NOTIFICATIONS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "booking.created",
    "title": "Nová objednávka",
    "priority": "normal",
    "message": "Zákazník: Jana Novakova\nObjednávka vytvorená: 26. 5. 2026, 09:12\nTermín rezervácie: 28. 5. 2026 o 14:00\nSlužba: Konzultácia\nKontakt: +421900123456, jana@example.com",
    "data": {
      "clientName": "Jana Novakova",
      "createdAt": "2026-05-26T07:12:00.000Z",
      "date": "2026-05-28",
      "time": "14:00",
      "serviceType": "Konzultácia",
      "phone": "+421900123456",
      "email": "jana@example.com"
    }
  }'

OpenAPI is available at GET /openapi.json; health check is GET /health.

For customer previews, use the bridge wrapper instead of calling preview-up directly. It injects customer-scoped runtime secrets without printing their values:

node dist/bin/preview-up-customer.js <preview-slug> <preview-id> /home/pk01/.kokpit-wa-bridge/repos/<customer-slug> --customer <customer-slug>

After a successful production deploy, CI/CD should stop all PR preview containers for the deployed app:

node /home/pk01/projects/kokpit-wa-bridge/dist/bin/cleanup-preview-containers.js --slug <preview-slug>

Supported slug aliases include adaia-page -> adaia and kniznica-sc -> kniznica. For SolarCompany use --slug solarcompany.

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:

  1. pm2 restart kokpit-wa-bridge
  2. Open the QR pairing page logged as qr-server pairing page available, or copy the QR string from pm2 logs kokpit-wa-bridge --lines 5.
  3. Open WhatsApp on phone → Linked Devices → Link a device → scan.

License

MIT — Peter Kramarik peter.kramarik@pk01.sk