Files
ten31-database/.env.example
T
Keysat 5faa5ae4d6 Email-proposal review over Matrix + a bot role (v0.1.0:89)
The email-capture "proposed grid notes" gain two review surfaces:

1. Inline source email — each proposed-note card on the Email Capture page
   gets a "View email" toggle that lazily fetches the existing
   GET /api/email/detail and shows from/to/cc/date/subject + scrollable body,
   so a reviewer can judge the note against the email it was drafted from.

2. CRM->Matrix review bridge — the CRM (box, stdlib, no matrix-nio) can't post
   to Matrix, so the intake bot (Spark) PULLS: GET /api/intake/email-proposals
   returns to_post/open/to_close work-lists; the bot posts a review card
   (metadata + snippet + draft note) to a dedicated review room
   (MATRIX_EMAIL_REVIEW_ROOM) and relays in-thread yes / no / NL-edit
   (POST .../{id}/decide, note revised via local Qwen). Decisions sync both
   ways: web decide -> bot announces + closes the thread; Matrix decide -> the
   web panel's ~25s poll clears the card. State lives CRM-side in the new
   email_proposal_matrix side row (email-integration migration 0003, additive
   + idempotent CREATE TABLE IF NOT EXISTS), so it survives a bot restart.

Adds a 'bot' role (authenticated, never admin; require_bot_or_admin) to gate
the email-proposal endpoints rather than handing the bot full admin — the
principled base for the coming agentic capabilities. Role controls reach;
the draft->approve gate still controls autonomy (a human approves every write).

Deploy split: endpoints + migration + role + frontend ship in the s9pk; the
bot poll loop + review-room handling ship on the Spark. The bot's CRM user
must be flipped member->bot and joined to the review room (one-time).

Tests: backend/test_email_proposal_matrix.py + matrix_intake/test_email_proposals.py
(30/30 suite green, render-smoke green, migration verified twice on a DB copy).
2026-06-18 09:51:41 -05:00

78 lines
3.9 KiB
Bash

# Ten31 agentic system — environment template.
# Copy to .env (gitignored) and fill in. Secret values NEVER go in .env.example.
# ── Claude (frontier reasoning; Agent SDK uses an API key, not claude.ai login) ──
ANTHROPIC_API_KEY=
# ── Spark Control gateway (local model services; reads + dense embeds) ──
# HTTPS with the Start9 self-signed cert -> clients must skip TLS verification.
SPARK_CONTROL_URL=https://<spark-control-host>:<port>
SPARK_CONTROL_VERIFY_TLS=false
# ── Qdrant (direct, for ingest: create collection + upsert points) ──
# Plain HTTP on the trusted LAN, no auth currently.
QDRANT_URL=http://<spark2-host>:6333
# ── X (Twitter) API for Scout/Analyst enrichment (NOT a CRM key) ──
X_API_KEY=
# ── CRM (ingest opens the SQLite file directly, read-only) ──
CRM_DB_PATH=./data/crm.db
CRM_DEV_DB_PATH=./data/crm_dev.db
# ── Daily activity digest (Phase B) ──
# The daily digest (each team member's activity per investor + a by-investor view,
# summarized LOCALLY on Spark — never Claude) is controlled from Settings → Admin
# (stored in the DB). These env vars only SEED the first-boot default before an
# admin sets it; once the policy row exists, the admin panel wins. The "Send Digest
# Now" button works regardless. Leave blank to default to off / 6 PM.
CRM_DIGEST_ENABLED=
# Local (box-time) hour 0-23. Default 18 (6 PM).
CRM_DIGEST_SEND_HOUR=18
# ── Daily-digest sender ──
# The digest mailer prefers Gmail domain-wide delegation (the service account that
# already powers email capture; its grant includes gmail.compose, which can send) and
# falls back to SMTP below. For the Gmail/DWD path it sends impersonating this domain
# user; if unset, it uses the first active admin's email.
CRM_DIGEST_SENDER=
# ── Daily-digest outbound SMTP fallback (dev override of the per-package mailbox) ──
# On the Start9 box these are set by the "Configure Digest SMTP" action (written
# to /data/secrets/smtp/* and exported by docker_entrypoint.sh). For dev, set them
# here. SMTP_SECURITY is one of: starttls (587) | tls (465) | none.
SMTP_HOST=
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_FROM=
SMTP_USERNAME=
SMTP_PASSWORD=
# ── Matrix intake bot (backend/matrix_intake/, runs as its own process on the Spark) ──
# Parses a typed message in a dedicated Matrix room into a proposed fundraising-grid
# add/edit (local Qwen via Spark Control above), then writes through the CRM API only
# after in-thread human approval. Reuses SPARK_CONTROL_URL / CRM_CHAT_MODEL above.
MATRIX_HOMESERVER=https://<homeserver>
MATRIX_USER=@intake-bot:<homeserver>
MATRIX_ACCESS_TOKEN=
MATRIX_DEVICE_ID=ten31-intake-bot
MATRIX_INTAKE_ROOM=!<roomid>:<homeserver>
# Dedicated room for reviewing CRM-drafted email-activity proposals (the proposed grid notes the
# Email Capture panel shows). The bot posts a review card per pending proposal here and relays the
# in-thread yes/no/edit back to the CRM, in sync with the web panel. Separate from the intake room
# so high-volume email proposals don't drown the conversational intake. Leave empty to disable the
# whole email-review poll loop. The bot must be a member of this room. Needs the server side in the
# s9pk (≥ v0.1.0:89) and the bot's CRM user set to role 'bot' (see docs/guides/matrix-intake.md).
MATRIX_EMAIL_REVIEW_ROOM=!<roomid>:<homeserver>
# CRM write-back: the bot logs in as a DEDICATED service user (admin-created CRM user;
# the CRM has no service-key path, so it uses normal Bearer-JWT auth).
CRM_API_BASE=http://127.0.0.1:8080
CRM_BOT_USERNAME=
CRM_BOT_PASSWORD=
# Set to false only if CRM_API_BASE is https with a self-signed cert.
CRM_API_VERIFY_TLS=true
# Ten31 team-member names (comma-separated), fed to the parser so a teammate's name reads as
# the person DOING outreach, not the prospect ("Jonathan is chatting with Wyoming" → Wyoming).
# Optional; first names as actually used in the room. Leave empty to disable the framing.
INTAKE_TEAM_ROSTER=