Commit Graph

77 Commits

Author SHA1 Message Date
Garfield
45cf9cafe6 fix(clients): fall back to env credentials when customer has no per-account creds 2026-06-12 13:14:08 -04:00
Garfield
723cf17869 docs(deploy): mark reviewer account setup complete 2026-06-12 13:09:28 -04:00
Garfield
de6d6ae9de fix(redis): add reconnect strategy to prevent closed client errors 2026-06-12 13:08:36 -04:00
Garfield
a326611806 style(design): apply favicon and nav link fixes to all subpages 2026-06-12 12:57:51 -04:00
Garfield
58a6d0f200 style(design): FINDING-003,004,005,006,007,008,009 — remove emoji, H4→H3, touch targets, favicon, nav link, H1 text-wrap 2026-06-12 12:55:42 -04:00
Garfield
b08d234494 style(design): FINDING-002 — restore nav links on mobile (wrap below logo row) 2026-06-12 12:53:40 -04:00
Garfield
cbdf1a795f style(design): FINDING-001 — replace system UI font stack with IBM Plex Sans 2026-06-12 12:53:25 -04:00
Garfield
9a4ce986be feat(site): add favicon.ico for Google favicon lookup and Claude.ai form 2026-06-12 12:46:10 -04:00
Garfield
8eafb2b05b feat(site): add 512x512 logo for GPT Store and Claude.ai marketplace 2026-06-12 12:35:46 -04:00
Garfield
b08dc2680c docs(deploy): reviewer account setup progress summary 2026-06-12 12:15:02 -04:00
Garfield
663107bfbc deploy: hermes-mcp reviewer enum + squaremcp-site legal update 2026-06-12 12:13:56 -04:00
Garfield
af084133fa legal: update privacy policy and terms for v1 consumer launch 2026-06-12 12:12:46 -04:00
Garfield
13350881e0 feat(manifest): add sqcp_reviewer account to ACCOUNT_PARAM_SCHEMA enum 2026-06-12 12:09:00 -04:00
Garfield
d1ff459b46 chore(k8s): deploy Facebook/Instagram creds from vault (token currently 500) 2026-06-12 09:45:51 -04:00
Garfield
e0623574ec docs(deploy): v1 launch execution summary 2026-06-12 07:23:06 -04:00
Garfield
bc58befd5e feat(ui): v1 launch — consumer hero, onboarding flow, Obsidian app card 2026-06-12 06:39:07 -04:00
Garfield
f74f90a2f0 fix(webhook): preserve raw body for Meta webhook HMAC validation
The global express.json() middleware at line 77 was parsing the body
into a JS object before the route-level express.raw() could capture
the raw Buffer. When WHATSAPP_APP_SECRET is set and a signature is
present, crypto.createHmac().update(req.body) received an Object
instead of Buffer, throwing TypeError and crashing the process.

Fix: register app.use('/webhook/whatsapp', express.raw({ type: '*/*' }))
before app.use(express.json()) so the raw body is preserved for HMAC.

Post-deploy verification: all 7 webhook tests pass, pod 0 restarts.

Related: SquareMCP/2026-06-10-twilio-whatsapp-webhook-deployment.md
2026-06-10 22:37:00 -04:00
Garfield
e5152eef12 feat(webhook): Twilio WhatsApp inbound route + Meta webhook hardening
Add POST /webhook/twilio/whatsapp for the pilot approval loop — Alex
replies 1/2/3 to the Twilio number to approve post drafts. Includes
HMAC-SHA1 signature validation, Redis dedup (wa_msg_seen:MessageSid),
pilot_owner_phone allowlist, staleness check (7d), tracking link
creation, and draft status update.

Also fix two security bugs in the existing Meta webhook handler:
fail-open when WHATSAPP_APP_SECRET unset (now 503), and missing length
guard before timingSafeEqual (was RangeError → 500, now 403).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:30:16 -04:00
Garfield
5effb41af4 feat(tracking): MySQL-backed tracking links + pilot schema
- src/tracking-links.ts — create, get, record click, validate URL, bot detection
- src/tracking-links.test.ts — 20 tests, all passing
- src/db.ts — tracking_links table, post_drafts table, 6 pilot columns on customers
- src/index.ts — public GET /t/:token redirect route (no auth)

Analytics (click count) is fire-and-forget after redirect.
MySQL is source of truth; redirect never depends on Redis.

Related: SquareMCP/2026-06-09-tracking-links-deployment.md
2026-06-09 14:22:58 -04:00
garfieldheron
95b4138f87 docs(spv): add marketing intelligence specs and lodge-first launch design
- spv/marketing-intelligence-features.md — full 7-feature technical spec
  with build order, dependency map, and tool signatures
- spv/lodge-first-launch-design.md — approved office hours design doc;
  Phase A→C→B sequencing targeting Alex/lodge July demo and CMG Opal
  replacement; includes pull analysis and success criteria

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:17:26 -04:00
Garfield
2bb1a2db60 chore: add broker demo video, exclude .runner from git
- product/site/squaremcp-broker-demo.mp4 — 45s mortgage broker demo video
  served at squaremcp.com/squaremcp-broker-demo.mp4, sent to Ferrari Lending
- .gitignore: add .runner (Gitea Actions runner token, should not be committed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:18:21 -04:00
Garfield
7ddb6d48b4 fix(email): route sqcp SMTP through Azure ACS relay
Replaces direct Poste.io SMTP (mail.squaremcp.com:30587, port 25 blocked)
with Azure Communication Services relay (smtp.azurecomm.net:587).
All sqcp_* accounts share a single ACS credential; FROM addresses unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:22:21 -04:00
Garfield
da4058483a fix(auth): switch to K8s Redis, add claude.ai/chatgpt CORS origins
- REDIS_URL → K8s ClusterIP with auth (fixes silent hang on host Redis)
- Socket timeouts (connectTimeout 3s, socketTimeout 5s) on Redis client
- Add claude.ai, chatgpt.com, chat.openai.com to CORS allowlist
- Update hermes-mcp image SHA (includes above changes)
- Add squaremcp-broker-demo.mp4 to site Dockerfile; bump site image SHA

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 05:39:48 -04:00
Garfield
34983c44e2 security: fix reset token leak, add rate limiting, body limits, webhook HMAC, JWT_SECRET
- CRITICAL: forgot-password no longer returns token in response; sends email via info@squaremcp.com instead
- Rate limit: login 10/15min, forgot-password 5/hr, chat 30/hr (Redis, per IP)
- express.json() capped at 100kb
- WhatsApp webhook HMAC verification (activates when WHATSAPP_APP_SECRET is set)
- JWT_SECRET now explicitly set in K8s (was falling back to CREDENTIAL_ENCRYPTION_KEY)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:14:08 -04:00
Garfield
423dc89c94 perf(oauth): Redis-cache getTokenCustomer to eliminate uncached DB hit on every ChatGPT API call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:02:02 -04:00
Garfield
4746c4ee1c fix(whatsapp): fall back to default Twilio account when customer has no WhatsApp creds
Customers who haven't connected WhatsApp in the dashboard now
automatically use the SquareMCP default number (+19547385805 via Twilio)
instead of throwing 'WhatsApp not connected'. Routing flag usingDefault
replaces the !customer check so OAuth sessions get the same Twilio path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 00:46:21 -04:00
Garfield
c270f8f74b fix(oauth): allow wildcard ChatGPT callback URI pattern
ChatGPT regenerates its GPT ID (and callback URL) every time the GPT
is saved, making exact redirect_uri matching impossible. Added support
for the registered URI pattern https://chat.openai.com/aip/*/oauth/callback
which matches any valid ChatGPT GPT callback via regex.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 00:15:46 -04:00
Garfield
c6504ec60f feat: /openapi-chatgpt.json — 29-op spec for ChatGPT Custom GPT limit
Drops obsidian (5 ops) and whatsapp/templates list (1 op) to stay
under ChatGPT's 30-operation cap. Served at /openapi-chatgpt.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 23:39:02 -04:00
Garfield
b67146dfc8 feat(whatsapp): Twilio send path for default account + API v21
The SquareMCP number (+19547385805) is registered under Twilio's BSP,
so direct Meta API sends were blocked with #200 permission errors.
Default account now routes through Twilio's REST API; customer-owned
accounts still use Meta's Cloud API directly.

- twilioSend(): POST to api.twilio.com with Basic auth
- resolveTemplateText(): fetches template body from Meta, substitutes
  {{1}}/{{}} parameters, so templates render correctly via Twilio
- Bumped WHATSAPP_API_VERSION to v21.0
- Added TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_NUMBER,
  WHATSAPP_DEFAULT_BUSINESS_ACCOUNT_ID to K8s deployment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:34:42 -04:00
Garfield
dcc1c39754 chore: add WhatsApp credentials for SquareMCP number +19547385805
WHATSAPP_DEFAULT_PHONE_NUMBER_ID, WHATSAPP_DEFAULT_ACCESS_TOKEN,
and WA_VERIFY_TOKEN (hermes-wa-2026) added to K8s deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 13:15:24 -04:00
Garfield
d89df87a6c feat: Twilio voice webhook to reject calls on WhatsApp number
POST /webhook/twilio/voice returns TwiML that says the number doesn't
accept voice calls and hangs up — needed for Twilio number config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:31:09 -04:00
Garfield
adebe29ca0 feat: Slack alerts for chat widget escalations + support@ email polling
- notifications/slack.ts: added sendChatEscalationAlert (fires when visitor
  asks about pricing, demo, help, or a human) and sendSupportEmailAlert
- email-poller.ts: polls support@squaremcp.com every 5 min via IMAP,
  deduplicates with Redis (support📧alerted_uids), fires Slack alert
  for each new unseen message
- index.ts: detectEscalation() scans last user message for trigger phrases;
  chat endpoint fires alert fire-and-forget after responding; startEmailPoller()
  called on server boot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 21:47:43 -04:00
Garfield
d613119d55 chore: add SLACK_DEFAULT_BOT_TOKEN to K8s deployment
Enables Slack demo tools in the chat widget agent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 19:29:47 -04:00
Garfield
18b838c268 feat: ChatGPT Custom GPT support in chat bot + sqcp SMTP routing fix
- chat.ts: system prompt now includes step-by-step ChatGPT Custom GPT
  setup (openapi.json import + OAuth), Claude/Cursor/Windsurf config,
  and mortgage broker guidance — bot no longer incorrectly says ChatGPT
  is unsupported
- smtp.ts: all sqcp_* accounts now route to mail.squaremcp.com (SQCP_SMTP_HOST)
  instead of the fetcherpay server
- tools.ts: ACCOUNT_PARAM description now lists all 14 mailboxes including
  the 7 squaremcp.com accounts so Claude picks the right one without guessing
- package.json: postinstall hook runs imapflow patch script after npm install
- hermes-k8s.yaml: updated image digest to current production build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:53:42 -04:00
Garfield
1f8d97b6bd fix: serve chat-widget.js correctly in production
- Copy product/ into Docker image (was missing from final stage)
- Fix static file path: ../../product → ../product (wrong depth from dist/index.js)
- Add ANTHROPIC_API_KEY to K8s manifest (chat endpoint was returning 500)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:45:43 -04:00
Garfield
c7eae2c735 fix(imap): robust error logging + parser-instance.js null guards
- Move client.connect() inside try/catch in withClient
- Add logImapError() writing full stack to /vaults/imap-errors.log for diagnosis
- Extend patch-imapflow.cjs to guard this.remainder.trim() in parser-instance.js
- Root cause of reported crash was undefined args.q (callers passing 'query' not 'q')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:13:26 -04:00
Garfield
0714d2d6d6 fix(docs): correct signup link to app.squaremcp.com, deploy updated image
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 12:39:12 -04:00
Garfield
38e367ea58 feat(email): add all 7 squaremcp.com email accounts
- New accounts: sqcp_garfield, sqcp_info, sqcp_sales, sqcp_support,
  sqcp_founder, sqcp_contact, sqcp_admin
- All use same poste.io mail server as fetcherpay.com (IMAP 30993, SMTP 30587)
- Password: onelove for all accounts
- Updated: imap.ts, smtp.ts, tools.ts, manifest.ts, hermes-k8s.yaml
2026-05-15 11:34:56 -04:00
Garfield
be1a14f783 feat(chat): upgrade to full agentic demo bot (Option B)
Chat widget now runs a live tool-use loop via Claude Haiku. Exposes
slack, discord, and telegram demo tools — bot can actually send messages
and read channels to prove the platform works in real time. Widget shows
a purple pill with tool names when the agent calls a live platform.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 10:49:18 -04:00
Garfield
4bf93d6763 feat: Slack platform + Claude-powered chat support widget
- Add Slack as customer-facing messaging platform (client, 4 MCP tools, dashboard card)
- Add /api/chat endpoint powered by Claude Haiku with SquareMCP system prompt
- Add embeddable chat-widget.js injected into all 3 sites (docs, app, www)
- Add ANTHROPIC_API_KEY, serve product/ as static files
- Update Platform type to include slack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 10:44:24 -04:00
Garfield
05b4a30759 feat(remotion): mortgage broker demo video for Alex Ferrari
- Add SquareMCPBrokerDemo composition (1920x1080, 45s)
- 5 scenes: title, email inbox (47 unread → 5 replies), rate blast (200 prospects),
  WhatsApp auto-response, Facebook page reply
- Outro: SquareMCP logo + tagline
- Uses existing brand colors and spring animations
2026-05-15 06:19:28 -04:00
Garfield
fd7ea2fee5 feat(remotion): WhatsApp template management demo video for Meta app review
16s landscape (1920x1080): shows SquareMCP chat prompt triggering an
animated cURL call to the Business Management API creating a message
template (HEADER/BODY/FOOTER components with variables), then the right
panel renders a WhatsApp phone UI previewing the template bubble with
variable placeholders highlighted and a PENDING status badge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 20:38:35 -04:00
Garfield
195ad0b3d1 feat(remotion): WhatsApp Cloud API demo video for Meta app review
15s landscape (1920x1080) split-screen: left shows SquareMCP chat
prompt + animated cURL command + 200 response with wamid; right shows
a rendered WhatsApp phone UI with the message bubble appearing and blue
double-checkmarks. Also adds transparent-background logo PNG for Meta
Tech Provider icon upload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 20:02:22 -04:00
Garfield
d7c55cb82b test(notifications): full coverage for Slack alert module
23 tests covering sendSlackAlert (graceful degradation, Block Kit payload,
HTTP errors, network failures, fallback text) and notifyNewPilotRequest
(test submission filtering, source/IP header resolution, fire-and-forget
error containment).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:24:53 -04:00
Garfield
1742a2f599 feat(notifications): Slack alerts for new pilot requests
- Add src/notifications/slack.ts — Slack webhook integration with rich blocks
- Add src/notifications/index.ts — dispatcher with test-submission filtering
- Wire notifyNewPilotRequest() into POST /api/pilot-request (fire-and-forget)
- Filter out test submissions (@example.com, E2E, Smoke Test, QA Test, Browser Test)
- Skip alert gracefully when SLACK_PILOT_WEBHOOK_URL is not set
- Update .gitignore to exclude .playwright-mcp/ artifacts
2026-05-14 18:22:52 -04:00
Garfield
1ddc1aab19 ci: add Gitea Actions workflow for build + test on push/PR
Runs on every push to main and every PR:
  1. npm ci       — install deps
  2. npm run build — TypeScript type check
  3. npm test      — vitest run (216 tests, all mocked, no DB/Redis needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:00:11 -04:00
Garfield
d6302a673d test: add OAuth login route test suite (22 cases)
Guards the browser OAuth popup flow used by claude.ai and ChatGPT:
- GET /login: return_to URL validation, XSS escaping, error display
- POST /login: first-party cookie properties (httpOnly/secure/lax/domain),
  open redirect blocking, credential rejection paths
- GET /oauth/authorize: must redirect to /login (never app.squaremcp.com),
  return_to encoding, valid session bypasses redirect

Also exports `app` from index.ts and guards main() with NODE_ENV !== 'test'
so the Express app can be imported by supertest without triggering DB init.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:57:29 -04:00
Garfield
02398258a5 feat: native OAuth login page, architecture docs, docs site update
- Add GET/POST /login to hermes for first-party cookie during OAuth popup
  (fixes browser CHIPS cookie partitioning that broke claude.ai connection)
- Add role column to all findCustomer* SQL queries in src/auth.ts
- Add claude.ai tab to docs/getting-started.html with OAuth flow steps
- Add ARCHITECTURE.md with system diagrams, data flow, and key invariants
- Rewrite README.md and DEPLOY.md to reflect actual MicroK8s deployment
- Deploy updated docs site (squaremcp-docs sha256 updated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:48:01 -04:00
Garfield
61dab40585 feat(saas): SquareMCP v2 — multi-tenant MCP platform complete
Steps 0–10 of the v2 plan, 194 tests passing.

Core infrastructure
- Shared Redis client (src/redis.ts); all four Redis consumers migrated
- Vitest test harness with vitest.config.ts and npm test/test:watch scripts

Billing & invoicing (Steps 1–2)
- Monthly invoice generation with idempotency (MySQL uq_customer_period unique key)
- Cron job with Redis distributed lock (Lua compare-delete, 1-hr TTL)
- Invoice emailer via nodemailer (FETCHERPAY SMTP)
- Billing middleware: checkLimit gate in handleToolCall; platform attribution fix

Email multi-tenancy (Step 3)
- EmailCtx = Account | EmailCredentials; imap.ts + smtp.ts accept both
- resolveEmailCtx helper in tools.ts; all email tools use customer credentials

Analytics + platform health (Steps 4–5)
- Chart.js bar charts for platform breakdown and daily activity
- Token expiry check in getCredential with dynamic import refresh
- platform-health.ts: per-platform health probe with 10-min Redis cache
- GET /api/health/platforms; "Token expired" amber badge in dashboard

Tool schema filtering (Step 6)
- stripAccountParam deep-clones tool schemas; multi-tenant sessions never
  see the internal account enum

OAuth hardening (Step 7)
- Atomic auth code consumption: UPDATE SET used=TRUE, check affectedRows
- customer_id threaded through oauth_auth_codes → oauth_tokens
- getTokenCustomer(); requireAuth resolves req.customer from Bearer token
- Consent page requires authenticated session; redirect_uri validated
  against registered URIs; http://localhost:* loopback wildcard

DCR browser flow (Step 8)
- ensureOAuthAppRegistered() upserts pre-registered SquareMCP OAuth app
  on startup with redirect URIs for mcp-callback, localhost:*, claude-desktop,
  opencode
- GET /oauth/connect-mcp → server-side redirect (client_id off frontend)
- GET /oauth/mcp-callback → exchanges code, renders config snippet page
  with copy buttons for Claude Desktop and Codex CLI

Webhooks (Step 9)
- webhook_url + webhook_secret columns on customers
- deliverWebhook(): HMAC-SHA256 signing, 3× exponential retry (1s/4s/16s),
  Redis DLQ with 7-day TTL on total failure
- isValidWebhookUrl(): SSRF protection (blocks RFC-1918, localhost, .local)
- POST /api/webhooks/config (secret returned once), GET, DELETE
- GET /api/admin/webhooks/dlq/:customerId
- WhatsApp POST route uses express.raw() for raw body preservation
- Dashboard Webhooks tab with secret-once display and copy button

Developer docs (Step 10)
- docs/ static HTML site (GitHub Pages, no build pipeline)
- index.html: landing page with client + platform overview
- getting-started.html: tabbed MCP config for Claude Desktop, Codex CLI, opencode
- platforms.html: LinkedIn, TikTok, WhatsApp, Instagram, Twitter, Telegram guides
- agent-tutorial.html: complete Node.js agent (Anthropic SDK + MCP SDK),
  LinkedIn posting loop, extensions for multi-platform + inbound webhook reaction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:43:56 -04:00
Garfield
d4bc899b31 chore: add gstack skill routing rules to CLAUDE.md 2026-05-13 23:43:56 -04:00