What it is
A Cloudflare Worker that puts a private chat UI in front of Anthropic's Claude API, with the entire Tree Care campaign baked in: brand voice, ICP, all 24 LinkedIn posts (hooks + full copy), DM templates, Sperry case study facts, pricing, and the rules for what works (and what doesn't) with tree care owners.
It does what the ChatGPT Project was doing — except this one runs on infrastructure we control. No per-seat caps. No regional throttling. No "we've temporarily limited access to your conversations" errors. We own the IP, the UI, the data, and the model selection.
The numbers
What's in it
Home with team chat list
Welcome screen with quick actions, all team conversations, and the explainer of how the app works.
Score ICP Fit
Upload xlsx/csv, AI scores every contact 1–10 with reasoning, downloadable graded sheet with Score / Fit / Reason / Recommend columns and coach's notes.
Draft outreach
Pre-fills a prompt for 5 LinkedIn connection request variants. Peer voice, no pitch.
Reply to a prospect
Paste their message, get 3 on-brand reply options. Low-friction next step every time.
Look up a post
"What's the hook for Post 7?" — full hook + angle + copy on demand.
Usage view
30-day per-user breakdown: message count, tokens, cost. Daily Slack report at 8am PT to #tree-care-campaign.
Shared chat history
Every chat saves. All three users see all conversations. Memory builds across the team.
Feedback buttons
👍/👎 on every AI response, optional comment on thumbs-down. Feeds an AI-classified review in the daily Slack report.
Admin: source files
Jason + Armen upload/edit/delete knowledge files. Concatenated into the system context with prompt caching.
Admin: instructions
Edit the system prompt directly in the UI. Saves to D1. Applies to every future chat immediately.
Image support
Paste screenshots with Cmd+V or click 📎. Useful for analyzing LinkedIn profiles or prospect replies.
Proactive coaching
System prompt directs the AI to occasionally suggest "💡 Next move" actions when it adds value.
Help chatbot
Floating ? in lower right. Dedicated help assistant that explains the app (not campaign copy).
Build timeline (May 12, 2026)
Companion: the DM Check bookmarklet
Drag-to-bookmark-bar button that grabs your draft text from any LinkedIn DM composer (or any selected text on the page), opens the BT Tree Care Copilot's DM Check pre-filled, and gives you a strict V3 brand-voice review without leaving LinkedIn.
Use case: Lazar composing a reply in LinkedIn → highlights his draft → clicks 📩 BT DM Check in his bookmark bar → instant strict feedback + suggested rewrite. Cuts the "remember to check it" friction to zero.
The voice rules — V3 (current)
Warmth vs. flattery — the line
The most-broken voice rule on this campaign. The distinction:
- Warmth is in scope. Peer-to-peer friendly tone — the way one contractor talks to another. Examples: "Good seeing tree care work get a real spotlight." "Hope the spring rush isn't crushing you." "Appreciate operators who'll talk shop straight." "Wanted to connect."
- Flattery is out of scope. Transactional positive judgments about the prospect's work or business. Examples: "You've built a solid reputation." "Impressive operation." "Your work is solid." "Looks like a company that's been built the hard way."
The test the AI runs on every draft: "Does the line judge their work or business positively? If yes → flattery → cut. Does it acknowledge something without rating it? If yes → warmth → keep."
3-part opener structure
- Observation — one specific, concrete thing from their profile/post/company. Neutral statement. Not a positive judgment.
- Reason for reaching out — one short, transparent line. Why are we in their DMs? Frames us as value-for-them, not asking-for-them. Removes the "what does this stranger want" friction. Research benchmark: senior targets respond best when the message under-promises and reads as honest.
- Low-friction question — open-ended, easy to answer. Not "do you want to chat" — something they actually have an opinion on.
Hard limits: max 2 sentences / ~60 words, zero pitch in first-touch, zero flattery, mandatory specific observation.
Model openers
Pattern A — observed post + reason + question: "Saw your post about [topic]. Working with a tree care owner in Eugene on the business side of the operation — wanted to connect with folks thinking about [that same problem]. How's that showing up in your work?" Pattern B — observed service + reason + question: "Saw [Company] is doing [specific service] in [location]. Working with another tree care owner in OR on the business side. How's [that side of the work] been trending for you this season?" Pattern C — credential + reason + question: "Saw you're [credential, e.g., 'a Certified Arborist in the PNW']. Working with tree care owners on the systems side of the business. Curious how you're handling [seasonal swings / database re-engagement]?"
Daily 6pm PT audit — Cowork scheduled task
A Cowork scheduled task runs every evening at 6pm PT and does the following:
- Fetch current system prompt via the admin endpoint (so the audit grades against whatever rules are in force at the moment of the run)
- Scrape Lazar's LinkedIn activity from the last 24 hours via Chrome MCP (Jason's logged-in BOSSTORQUE Chrome session): sent DMs, comments on posts, posts published
- Grade each artifact using the
/brand-voice:brand-voice-enforcementskill — classify as BROKEN / WEAK / GOOD / EXCELLENT - Suggest rewrites for anything sub-GOOD using
/marketing:draft-contentand/sales:draft-outreach - Identify recurring failure patterns across the day and any new rule or banned phrase that should be added
- Post the report to #tree-care-campaign via bt-notify with quoted examples, recommended rewrites, and the pattern of the day
- Auto-update the system prompt via the admin endpoint if a new banned phrase or rule emerged
- Redeploy this build doc with a fresh timeline entry so the team always has current state
Task ID: bt-tree-care-lazar-eod-audit. Notify-on-completion is on — Jason gets pinged in Cowork after every run.
Architecture
| Layer | Component | Purpose |
|---|---|---|
| Edge runtime | Cloudflare Worker | Auth, routing, Claude API gateway, admin endpoints, feedback endpoints, Slack cron handler |
| Data | D1 (SQLite) | conversations · messages · feedback · config · source_files (5 tables) |
| AI | Anthropic API (Sonnet 4.6 / Haiku 4.5) | Inference. Instructions + source files cached via ephemeral prompt cache (5-min TTL). |
| Frontend | Single HTML page (inline JS/CSS) | Login (with user selector), home view, chat with feedback buttons, sidebar, all modals, help bubble |
| File parsing | SheetJS (CDN) | Client-side xlsx/csv parse + graded sheet generation on download |
| Outbound notifications | bt-notify (channel: tree-care-campaign) | Daily reports route through the BOSSTORQUE Ops Slack app webhook so they push to Jason's iPhone |
| Profile enrichment (workflow) | Chrome MCP via BOSSTORQUE Chrome profile | For one-off list scoring runs: navigate each LinkedIn URL, scrape name/company/title/location, feed enriched data to scorer |
One file, ~3000 lines of code, ~32 KB gzipped. No build step. SheetJS loaded from CDN, otherwise no dependencies.
D1 schema
conversations (id, user_id, title, model, created_at, updated_at)
messages (id, conversation_id, role, content, sent_by,
input_tokens, output_tokens, cache_creation_tokens,
cache_read_tokens, cost_usd, model, created_at)
feedback (id, message_id, conversation_id, user_id, rating, comment,
created_at, ai_classification, ai_recommendation, reviewed_at)
config (key, value, updated_by, updated_at)
source_files (id, filename, content, size_bytes, uploaded_by, created_at)
API endpoints
| Method · Path | Purpose |
|---|---|
POST /auth | Verify password |
GET /api/conversations | List all team conversations (shared workspace) |
GET /api/conversations/:id | Load one conversation with all messages |
DELETE /api/conversations/:id | Delete a conversation + messages |
POST /api/chat | Stream a Claude response, persist user + assistant messages, track usage |
POST /api/icp-score | Score N enriched contacts against ICP via Haiku, return JSON |
POST /api/help-chat | Stream the help chatbot (in-app help) |
GET /api/usage | Per-user usage rollup (messages, tokens, cost) |
POST /api/feedback | Submit 👍/👎 on a message (with optional comment) |
GET /api/feedback/recent | List recent feedback (last N days) |
GET /api/admin/instructions | Get current system instructions (admin only) |
POST /api/admin/instructions | Save updated system instructions (admin only) |
GET /api/admin/sources | List uploaded source files (admin only) |
POST /api/admin/sources | Upload a source file (admin only) |
DELETE /api/admin/sources/:id | Delete a source file (admin only) |
POST /api/cron-test | Manual trigger for the daily Slack report |
CRON 0 15 * * * | Daily 8am PT (15:00 UTC) → usage + AI feedback review → bt-notify → #tree-care-campaign |
The Lazar workflow change
Today's live demo proved the value. Lazar's daily list of 30 LinkedIn URLs was un-scoreable without enrichment — bare slugs only. Using Chrome MCP to scrape each profile, we got:
- 17 strong fits (8–10) — owners of tree care companies in target geo
- 4 maybes (5–7) — borderline (combo landscape, consulting variants)
- 9 no fits (1–4) — clustered in 3 clean patterns:
- 4 wrong title (Arborist / Ops, not owner) — fix: add Job Title filter in Sales Nav
- 2 consulting arborists (off-ICP per Jason) — fix: skip anything with "Consulting" in title or company name
- 3 wrong vertical (Catholic Church, Tribal Council, Commercial Landscape) — fix: verify tree/arborist in headline
Full graded report (deployed as a CF worker, cardless internal reference).
Status checklist
- Worker deployed at bt-tree-care-copilot.jason-8ce.workers.dev
- D1 database created and migrated (5 tables)
- Secrets set: ANTHROPIC_API_KEY · GATEWAY_PASSWORD · BT_NOTIFY_TOKEN
- Daily cron schedule active (0 15 UTC = 8am PT)
- Anthropic auto-reload configured ($10 trigger / $100 refill)
- bt-notify channel
tree-care-campaignregistered with BOSSTORQUE Ops webhook - 3-pass QA completed — functional, edge cases, production readiness all clean
- Admin role enforced (Jason + Armen) — Lazar gets 403 on admin endpoints
- Shared workspace: all users see all team chats
- Feedback persistence + AI review + daily Slack report all working
- Help chatbot deployed (lower-right ? button)
- Lazar + Armen notified in #tree-care-campaign — migration on
- System prompt V3 deployed — warmth/flattery distinction, 3-part opener structure, reason-for-reaching-out rule
- Cowork scheduled task
bt-tree-care-lazar-eod-auditactive — daily 6pm PT audit - ChatGPT workspace cleanup: Jason to remove Armen + Lazar seats today
- Run-now the scheduled task once to pre-approve Chrome MCP + bt-notify permissions so future 6pm runs don't pause
Productized IP angle
This pattern is reusable. Same codebase, different system prompt and knowledge base, ships a custom copilot for any client campaign:
- Sperry Tree Care (this build) — tree care vertical, OR/WA/ID/NV/AZ/CA/BC, 24-post organic campaign
- Next client — clone, replace system prompt + source files in the Admin UI, deploy as
<client>-copilot.jason-8ce.workers.dev, ~30 minutes
Add-on revenue model: bundle "AI Campaign Operations Portal" into client retainers at $500–$2,000/mo. Five clients = $30K–$120K/yr of recurring revenue from an asset that gets built once. The full codebase is sellable IP at exit. The Admin UI in particular makes this productizable — client side can self-manage their voice + knowledge over time without engineering involvement.
Deploy / maintenance
Update the worker
cd /Users/Jason/Library/.../outputs/gateway wrangler deploy
Trigger daily report manually (for testing)
curl -X POST -H "X-Auth: treecare-2026-bt" \ https://bt-tree-care-copilot.jason-8ce.workers.dev/api/cron-test
Send a direct message via bt-notify
TOKEN=$(cat '/Users/Jason/My Drive (jason@bosstorque.ai)/4_Strategy & IP/Entrepreneurial Ideas and Assets/GiftCue/.bt_notify_token')
curl -X POST https://bt-notify.jason-8ce.workers.dev/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel":"tree-care-campaign","text":"...","source":"manual"}'
Query D1 usage
wrangler d1 execute bt-tree-care-copilot --remote \
--command "SELECT user_id, COUNT(*) AS msgs, ROUND(SUM(cost_usd),4) AS cost
FROM messages m JOIN conversations c ON c.id=m.conversation_id
WHERE m.role='assistant' GROUP BY user_id"
Check recent feedback
curl -H "X-Auth: treecare-2026-bt" \ https://bt-tree-care-copilot.jason-8ce.workers.dev/api/feedback/recent?days=7