Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 2 additions & 28 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -128,34 +128,8 @@ TLS_DOMAIN=
TLS_EMAIL=
WEB_BIND=0.0.0.0

# ─── Optional: publish routes to a reverse proxy you already run ─────────────
# With both of these set, a domain added in Admin Settings -> Domain is routed
# automatically β€” no file to paste, no shell. Off unless BOTH are set: a guessed
# directory is either useless or writes into somebody else's proxy, and neither
# failure announces itself.
#
# TRAEFIK_DYNAMIC_DIR path INSIDE this container that the proxy also watches
# PROXY_UPSTREAM how the proxy reaches this install β€” the CONTAINER
# name, not the compose service name. A service alias is
# not unique once two installs share a network, and `web`
# would resolve to whichever answered first.
# PROXY_ROUTES_DIR the host path behind TRAEFIK_DYNAMIC_DIR, used by the
# overlay that mounts it
#
# Generated routes carry priority 1 β€” the lowest Traefik accepts β€” so they can
# never take a hostname another site on that proxy already serves. Full setup,
# including the proxy's own flags: docs/reverse-proxy.md
TRAEFIK_DYNAMIC_DIR=
PROXY_UPSTREAM=
PROXY_ROUTES_DIR=

# Whether THIS install owns ports 80/443, decided by install.sh and read by
# Admin Settings -> Domain. `caddy` means a domain added there gets its
# certificate automatically on first visit. `proxy` means something else on this
# server owns those ports, so the domain is accepted immediately but the
# certificate has to come from that proxy. Unset is treated as `proxy`, which is
# the cautious direction: promising a certificate this install cannot obtain is
# the worse of the two mistakes.
# Set by install.sh: `caddy` when the bundled caddy terminates TLS for
# TLS_DOMAIN, `proxy` when something else (or nothing) does.
TLS_MODE=proxy

# First-run admin. If the password is omitted a strong one is generated and
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,56 @@ Published as multi-architecture images tagged `1.0.1`, `1.0` and `1`.

## [Unreleased]

Brings in two months of work from the internal build. **This is a major release**: it removes
three features and their data, so read the upgrade notes before re-running the installer.

### Upgrade notes

- **Lead Forms, Projects and custom domains are removed β€” code and data.** Migrations
`131_remove_forms_projects` and `134_remove_custom_domains` drop their tables. Export anything you
need from those screens first. A domain that was only ever added in **Admin Settings β†’ Domain**
stops being accepted: put it in `CORS_ORIGIN` (and `TLS_DOMAIN`, comma-separated, if the bundled
caddy serves it) before upgrading.
- **`install.sh --proxy-routes` is gone** with the Domain screen. It is still accepted, and ignored
with a warning. `TRAEFIK_DYNAMIC_DIR`, `PROXY_UPSTREAM` and `PROXY_ROUTES_DIR` are no longer read.
- **Without a domain, the bundled caddy no longer starts.** It used to run with no hostname of its
own so a domain added later in the app needed only DNS. Re-run with `--domain <host>` for HTTPS.
- **Voice notes are transcribed through OpenRouter** (`microsoft/mai-transcribe-2`) rather than
OpenAI Whisper. Add an OpenRouter key in **Admin Settings β†’ AI Models** to keep transcription on.

### Added

- **Workflow Agents** β€” scheduled or triggered LLM agents that read leads, chats and sales, draft or
take actions behind an approvals queue, and keep a per-rep worklist.
- **Chat Analyzer** β€” per-conversation summaries, intent, sentiment, a suggested stage and ready-to-send
follow-ups, with voice notes, images and documents read as well as text.
- **Follow-ups** β€” pin a lead chat, set a follow-up date, and work them from a calendar view (with
optional Google Calendar sync).
- **Payments dashboard**, refunds on the Sales Log, payment status on leads and saved payment filters.
- **Conversions API and Lead Optimisation (CLO)** β€” previously withheld as Coming Soon. Sends mapped
funnel stages back to Meta with the lead's click id. Off by default; turning it on is the operator's
decision because it changes how Meta spends the ad budget.
- **Social** β€” a third header section for Instagram, Facebook, Threads, YouTube and LinkedIn: analytics,
a unified inbox, comment automations, a content library and an MCP of its own. Off until
`SOCIAL_ENCRYPTION_KEY` is set; see [`docs/social-section.md`](./docs/social-section.md).
- **Automation templates**, quick-start AI agent templates, a message-gap pause between flow steps and
per-number display labels.
- **MCP**: dedicated tools for most read-only reporting (no `forgechat_request` round-trip), and the
MCP Tools screen reorganised into Connect / Permissions / Access / Advanced.

### Changed

- **Images build from the lock files** (`npm ci`). A lockless `npm install` had stopped resolving at
all β€” `Cannot read properties of null (reading 'edgesOut')` β€” so source installs and image builds
failed without a line of this repository changing.
- **The webhook parses through `services/metaPayload.js`** β€” the tested copy. `routes/webhook.js`
carried its own inline parser, which had drifted from the one the tests covered.

### Fixed

- **Opening Lead Optimisation β†’ Stages no longer crashes the page** (`navigate` was used without being
passed in).

- **The Claude plugin now downloads with the same address the connector actually authenticates
against.** "What is this install's public URL" was worked out independently in four places β€” the
OAuth issuer, the Streamable HTTP transport's challenge, the **Admin Settings β†’ MCP Tools** panel
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ so a fresh deploy self-heals. **If you change such a table in a migration, chang
### Never introduce a sentinel-value equality check

An unconfigured env var is `''`. Comparing it directly against a record field means `'' === ''`
evaluates true for every record that also lacks the value, silently matching everything. See
`isBridgedNumber()` in `routes/webhook.js`: it returns false when unconfigured, deliberately. Do not
replace it with a bare equality.
evaluates true for every record that also lacks the value, silently matching everything. Check that
the configured value is non-empty first, and treat "unconfigured" as "matches nothing".

### Webhook handling must never throw

Expand All @@ -82,7 +81,8 @@ downloads) goes on a queue rather than running inline.

### Phone numbers are digits-only

Normalised on insert by `normalizePhone()` in `services/metaPayload.js`. `+91…` and `91…` must not
Normalised on insert by `normalizePhone()` in `services/metaPayload.js`, which is also the only
Meta payload parser β€” `routes/webhook.js` imports it rather than carrying a copy. `+91…` and `91…` must not
become two chat threads.

### Capabilities are global, not per-token
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,22 @@ Nothing else. No Node, Postgres, Redis or MinIO on the host β€” it all runs in c
|---|---|
| **Campaigns** | Real spend from the Meta Marketing API, arranged Campaign β†’ Ad Set β†’ Ad |
| **Click-to-WhatsApp** | Per-ad CPL and ROAS, placement breakdown, and the exact creative each person saw |
| **Conversions API & CLO** | Funnel stages sent back to Meta with the click id, so the optimiser learns who pays β€” off by default |
| **Leads** | Pipeline, funnel and list views of one model, with tags, custom fields and CSV/XLSX import |
| **Payments** | Razorpay links stamped with the lead id, so a payment attributes itself |
| **Payments** | Razorpay links stamped with the lead id, so a payment attributes itself, plus a payments dashboard |
| **Sales Log** | Gateway payments deduped and unioned with manually logged sales |
| **Forms** | Shareable lead capture at `/f/<slug>`, prefillable from a WhatsApp send token |
| **Inbox** | 3-pane WhatsApp client with media, voice notes and 24-hour window enforcement |
| **Inbox** | 3-pane WhatsApp client with media, transcribed voice notes and 24-hour window enforcement |
| **Chat Analyzer** | Summary, intent, suggested stage and ready-to-send follow-ups for any conversation |
| **Follow-ups** | Pinned lead chats and a follow-up calendar, optionally mirrored to Google Calendar |
| **Templates** | The full Meta lifecycle β€” submit, sync, edit, quality score, carousels |
| **Broadcasts** | 7 message types, scheduling, per-recipient status, stage and tag filters |
| **Message Costs** | What each send actually owes Meta, derived from your WABA's own pricing |
| **Automations** | A visual flow builder β€” 20 block types, every branch visibly wired |
| **AI Agents** | A no-code LLM agent per number, with tools, memory, handoff and spend limits |
| **Workflow Agents** | Scheduled or triggered agents that work across leads, chats and sales, behind an approvals queue |
| **Social** | Instagram, Facebook, Threads, YouTube and LinkedIn β€” analytics, one inbox, comment automations |
| **Google** | Sheets, Calendar and Gmail, with discovery browsers and automation actions |
| **MCP** | Drive the whole app from Claude β€” 46 tools in 17 categories, all off by default |
| **MCP** | Drive the whole app from Claude β€” every capability off by default |
| **Users & RBAC** | Roles are rows, not an enum, so adding one needs no migration |

Each of these has a "why it works this way" worth knowing β€” **[`docs/features.md`](./docs/features.md)**
Expand Down
6 changes: 4 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ RUN apk add --no-cache ffmpeg

WORKDIR /app

COPY backend/package.json ./
RUN npm install --omit=dev
# From the lock file, for the same reason as frontend/Dockerfile: the image must
# be what CI tested, not whatever the registry resolves on the day it is built.
COPY backend/package.json backend/package-lock.json ./
RUN npm ci --omit=dev --no-audit --no-fund

COPY backend/src/ ./src/
COPY backend/scripts/ ./scripts/
Expand Down
Loading
Loading