type: custom

customType: model-snapshot

provider: zai

modelApi: openai-completions


type: message

role: user

type: text

text:
[cron:b8f52fef-7c39-45ab-9116-ef6e78d4a79b cannadeals-marketing-todo-check-4am] Cron nudge: Read TODO.md. If there are pending tasks, resume the Build → Test → Log → Decide loop. If all tasks are complete or blocked, report status and sleep.
Current time: Friday, February 27th, 2026 — 9:06 AM (UTC)

Return your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.


role: assistant

type: thinking

thinking:
The user is sending me a cron nudge to check TODO.md. Let me follow my boot sequence first, then check TODO.md for pending tasks.

Boot sequence:
1. Read USER.md → who I am helping
2. Read learnings/LEARNINGS.md → past mistakes to avoid
3. Read memory/YYYY-MM-DD.md (today + yesterday)
4. Main session only: Read MEMORY.md

Then I need to read TODO.md and either resume tasks or report status.

Let me start by reading the necessary files.

read

type: toolCall

api: openai-completions

stopReason: toolUse


role: toolResult

toolName: read

text:
# USER.md - About Your Human

*Learn about the person you're helping. Update this as you go.*

- **Name:**
- **What to call them:**
- **Pronouns:** *(optional)*
- **Timezone:**
- **Notes:**

## Context

*(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)*

---

The more you know, the better you can help. But remember — you're learning about a person, not building a dossier. Respect the difference.


text:
# CannaDeals Learnings Log

Based on: Prompt Engineering for AI Agents - Self-Improvement Pattern

## 2026-02-27: Blog Image Disaster

### What Happened
1. Created 6+ blog posts with missing images (404s)
2. Used same fallback hero image for multiple posts
3. Published test post to production
4. Referenced non-existent image files

### Root Causes
- **Bypassed workflow**: Didn't generate images before publishing
- **No verification**: Didn't check if image URLs returned 200
- **Shortcut mindset**: Reused existing assets instead of creating unique ones
- **Process gap**: No pre-publish checklist

### Permanent Fixes
1. **Image-First Rule**: Generate unique header BEFORE inserting blog post
2. **URL Verification**: Always `curl -I` the image URL before publishing
3. **Never Reuse**: Each post gets its own unique 2K image from Nano Banana Pro
4. **No Test Posts**: Never insert test data to production database

### Code Changes
- Content pipeline doc created with enforcement rules
- Pre-publish checklist added

### Prevention
```
BEFORE ANY BLOG POST:
1. Generate unique 2K image → /tmp/
2. scp image to carl
3. Verify HTTP 200 on image URL
4. THEN insert blog post with image URL
5. Rebuild site
```

---

## 2026-02-26: Hash Cache Poisoning

### What Happened
Scraper showed 0 deals inserted despite 30 images found. Deals dropped from 50+ to 21.

### Root Causes
- `.processed_hashes.json` had 596 stale entries
- No TTL on hash entries → old hashes blocked new deals forever
- No alerting when deal count dropped

### Permanent Fixes
1. Added `hash_cache_ttl_days: 7` to ScraperConfig
2. Updated `_load_processed_hashes()` to filter expired entries
3. Added deal count alerts (<20 = ERROR, <40 = WARNING)

### Prevention
Hashes now auto-expire after 7 days. Scraper self-heals weekly.

---

## Pattern Recognition

### Anti-Patterns to Avoid
1. **"Let me just..."** → Shortcut that bypasses safety checks
2. **Bulk operations** → Always verify first item before batch
3. **Assume it exists** → Verify URLs, files, DB entries before referencing
4. **Skip verification** → HTTP 200 checks are mandatory, not optional

### Good Patterns to Follow
1. **Generate → Verify → Publish** (never skip verify step)
2. **Unique assets per item** (no reuse, no placeholders)
3. **Log before executing** (write to memory first)
4. **Fail loudly** (alert immediately, don't silent retry)

---

## 2026-02-27: Skipping Established Workflows

### What Happened
Instead of using the existing image generation workflow (Nano Banana Pro → generate → verify → deploy), I tried to "shortcut" by:
1. Requesting Selena to generate (but she's async)
2. Using fallback images from other posts
3. Publishing without verification

### The User's Feedback
> "Why aren't you generating new images? We have a workflow set up for generating blogs and you're bypassing it"

### Lesson
**Use established workflows. They exist for a reason.** When a workflow is documented and working:
1. Follow it step-by-step
2. Don't improvise shortcuts
3. Don't skip steps because they "take too long"
4. If the workflow is broken, fix it — don't bypass it

### Enforcement
- Before any content creation, check for existing workflow docs
- If workflow exists, follow it exactly
- If workflow is missing/broken, document the gap before improvising


text:
# 2026-02-27 Memory Log

## [02:00 UTC] SEO Competitor Research

**Keywords Analyzed:**
- "weed deals florida" - CannaDealsFL not in top 15
- "mmj deals florida" - CannaDealsFL at #11 (page 2)
- "cannabis deals florida" - CannaDealsFL not in top 15

**Top Competitors:**
1. FLCannabisDeals.org - ranks top 3 for ALL keywords
2. BudDocs.org - flash deals page
3. MMJ Health - "daily deals" targeting
4. Cookies Florida - exact-match brand pages

**Strategy:**
- Professional titles, casual keywords in meta/body
- New blog: "Florida Patient Savings Guide" targeting search terms educationally

---

## [02:05 UTC] Hash Cache Poisoning Fix

**Problem:** Scraper showing 0 deals inserted per run despite 30 images found

**Root Cause:** `.processed_hashes.json` had 596 stale entries blocking new deals

**Fix Applied:**
1. Added `hash_cache_ttl_days: int = 7` to ScraperConfig
2. Updated `_load_processed_hashes()` to filter expired entries
3. Auto-cleans stale hashes on each run

**Result:** Deals went from 21 → 48 active

**Files Modified:**
- `unified-scraper-engine.py` - TTL filtering logic

---

## [02:17 UTC] Blog Post Disaster & Cleanup

**What Went Wrong:**
- Created 6+ duplicate/test posts
- All used same fallback hero image
- Content was low-quality
- Test post leaked to production

**Cleanup Actions:**
1. Deleted all botched posts via Supabase API
2. Created 3 unique, researched posts:
   - HB 719 Telehealth Bill update
   - February 2026 OMMU sales record (3B mg THC)
   - Sunburn Cannabis live rosin spotlight

**Lesson:** Always verify posts before bulk inserting; generate unique images per post

---

## BLOCKERS

| Blocker | Status | Owner |
|---------|--------|-------|
| X/Twitter @cannadealsfl suspended | BLOCKED | Chad must appeal |
| Web search API (401) | BLOCKED | API key issue |

---

## ARTIFACTS

**New Blog Posts:**
- `/blog/florida-hb719-telehealth-mmj-update-2026`
- `/blog/florida-ommu-sales-records-february-2026`
- `/blog/sunburn-cannabis-florida-live-rosin-leader`

**Code Changes:**
- `unified-scraper-engine.py` - Hash cache TTL (7 days)
- `scripts/content_scorer.py` - Content idea deduplication + scoring
- `scripts/blog_generator.py` - Image-first blog workflow
- `data/content_ideas.db` - SQLite content tracker
- `data/bi_metrics.db` - BI Council metrics storage

**New Documentation:**
- `memory/content-pipeline.md` - Content workflow enforcement
- `memory/bi-council.md` - Nightly council architecture
- `learnings/LEARNINGS.md` - Failure patterns + prevention

**New Cron Jobs:**
- `Canna: Nightly BI Council` - 5 AM EST daily

**Deals Status:** 48 active + approved

---

## HANDOVER [02:23 UTC]

- **Done:** SEO research, hash cache fix, blog cleanup, 3 unique posts
- **Next:** Monitor rankings, continue 3x daily blog cadence
- **Blocked:** X account still suspended

---

## [05:20 UTC] SEO Rankings Baseline

**Current Rankings (Feb 27, 2026):**

| Keyword | Rank | Page |
|---------|------|------|
| florida cannabis savings | #7 | Page 1 ✅ |
| dispensary coupons florida | #9 | Page 1 ✅ |
| cannabis deals florida | #19 | Page 2 |
| best dispensary deals florida | #20 | Page 2 |
| mmj deals florida | #25 | Page 3 |
| weed deals florida | NR | Not ranked |
| florida dispensary deals | NR | Not ranked |

**Summary:**
- 5/10 keywords ranked
- 2 on page 1
- 3 close to page 1 (#19, #20, #25)
- 5 need new content

**Tools Created:**
- `scripts/serp_checker.py` - SERP ranking checker
- `scripts/bi_data_collector.py` - BI Council data aggregator

---

## [07:27 UTC] Cron TODO Check

**SERP Check Results (Fresh):**
| Keyword | Rank | Page |
|---------|------|------|
| florida cannabis savings | #10 | Page 1 ✅ |
| florida dispensary deals | #16 | Page 2 |
| best dispensary deals florida | #14 | Page 2 |
| All others | NR | Not ranked |

**Summary:**
- 3/10 keywords ranked (down from 5 earlier)
- 1 on page 1
- 2 close to page 1 (#14, #16)

**Deals Status:** 48 active deals in DB (last updated ~7 hours ago)

**Actionable Tasks:**
1. ✅ Pinterest posting - Script available, profile exists, can run
2. ❌ X engagement - BLOCKED (account suspended)
3. ❌ Reddit engagement - BLOCKED (browser relay not attached)

**Timing:** 2:27 AM EST - Outside engagement window (9AM-10PM EST)

**Verdict:** Limited actions available at this hour. X suspension remains primary blocker. Pinterest could run but not optimal timing

---

## BI COUNCIL TOOLS

**SERP Checker:**
```bash
source ~/projects/inbox-agent/venv-scrapling/bin/activate
python3 ~/cannadealsfl/scripts/serp_checker.py all      # Check all keywords
python3 ~/cannadealsfl/scripts/serp_checker.py report   # Generate report
```

**BI Data Collector:**
```bash
source ~/projects/inbox-agent/venv-scrapling/bin/activate
python3 ~/cannadealsfl/scripts/bi_data_collector.py     # Full data report
```

**Cron:** Nightly BI Council runs at 5 AM EST (job: 65097bc5-5dd6-44a5-9d93-7580fc2a77a8)


text:
# 2026-02-26 Memory Log

## Cron Check (09:07 UTC)

**TODO.md Status Review:**

### ✅ UNBLOCKED
- **skills/ directory** — RESTORED (Feb 22). All posting scripts available again.
- **admin/page.tsx** — FIXED. `npm run build` succeeds on carl.

### 🔴 STILL BLOCKED
- **X/Twitter @cannadealsfl** — SUSPENDED. Confirmed via `post_deal_v2.py x`:
  ```
  ERROR: Authorization: Denied by access control: Missing TwitterUserNotSuspended
  ```
  Needs Chad to check account status and file appeal if suspended.

### 🟢 READY TO EXECUTE
- **Pinterest posting** — `skills/pinterestPoster.js` available
- **Reddit engagement** — `skills/reddit-poster/` available
- **Blog writing** — Always available

### 🟡 In Progress
- Daily X engagement — Blocked by X suspension (can't reply if suspended)

**Verdict:** skills/ restored + build fixed = big progress. X suspension is the main remaining blocker. Can proceed with Pinterest, Reddit, and blog content.

## HANDOVER [09:07 UTC]
- **Discussed:** Cron TODO check, tested blockers
- **Decided:** 2 of 3 blockers resolved. X still suspended.
- **Pending:** X account appeal
- **Next:** Run Pinterest/Reddit engagement, write blog content, await X resolution

---

## Cron: Morning X Deal Post (13:00 UTC / 8 AM EST)

**Status:** ❌ FAILED

**Selected Deal:** GoldFlower - Deep Discounts on Flower, Vapes, and More! (Tier 1)

**Error:**
```
Authorization: Denied by access control: Missing TwitterUserNotSuspended
```

**Action Required:** X account @cannadealsfl remains suspended. Need Chad to file appeal.

---

## Cron: Morning Routine - Post + Engage (13:00 UTC / 8 AM EST)

### 1. POST DEAL ❌
- **Selected:** GoldFlower - Deep Discounts on Flower, Vapes, and More! (Tier 1)
- **Result:** Failed - X authorization denied (account suspended)
- **Error:** `Authorization: Denied by access control: Missing TwitterUserNotSuspended`

### 2. X ENGAGEMENT ❌
- **Result:** Could not complete
- **Blockers:**
  - X account suspended (can't reply/post)
  - Browser relay not attached (no tab connected)
  - Web search API auth error (401)

### 3. NEXT STEPS
- **Pinterest:** Can still post via `skills/pinterestPoster.js`
- **Reddit:** Can still engage via `skills/reddit-poster/`
- **X:** Blocked until account appeal resolved
- **Blog:** Write content for cannadealsfl.com/blog/

**Action Required:** Chad needs to file X account appeal to unblock posting/engagement.

---

## [15:21 UTC] Blog Post Published

**Title:** Best Florida Dispensary Deals This Week: February 2026
**URL:** https://cannadealsfl.com/blog/best-florida-dispensary-deals-week-february-2026
**Word Count:** ~1,100 words
**Header Image:** Generated via Nano Banana Pro (2K resolution)

**Content:**
- Tiered dispensary breakdown (Premium/Mid/Budget)
- Jungle Boys, Sunburn, GoldFlower, Planet 13 deals
- SEO-optimized with keyword targeting

**Social Distribution:** ❌ Blocked - X account suspended

---

## [15:35 UTC] SEO Research Started

**Target Keywords:**
- "weed deals florida"
- "mmj deals florida"
- "cannabis deals florida"

**Current Rankings:** Pages 3-5 (need page 1)

**Blockers Encountered:**
- Web search API down (401 auth error)
- Scrapling not extracting Google SERP results (anti-bot or JS rendering)

**Next:** Try scrapling with different configuration or use browser tool

---

---

## Cron: Afternoon Deep Engagement (19:00 UTC / 2 PM EST)

**Status:** ⏸ BLOCKED - Replies drafted, not posted

### Tasks Completed:
- Fetched r/FLMedicalTrees via old.reddit.com (bypassed age wall)
- Identified 10 recent posts from last 3 hours
- Drafted 5 quality replies using Sarah's tone:
  1. **Peach Fuzz strain question** (Helpful)
  2. **Smoking & Working out** (Relatable)
  3. **True OG Smalls** (Thoughtful)
  4. **Rise live resin cart** (Curious)
  5. **Grapefruit Cookies Badder** (Relatable)

### Drafted Replies Saved To:
- `memory/engagement-drafts-2026-02-26.md`

### Blockers:
- X @cannadealsfl: SUSPENDED (can't reply on Twitter)
- Browser relay: Not attached (can't post to Reddit)
- Pix mobile agent: Not connected
- Web search API: 401 auth error

### Sarah's Tone Applied:
✅ Professional but friendly
✅ No "fr fr", "no cap", "hits different"
✅ Mature language: "totally", "honestly", "actually"
✅ No self-promotion - purely helpful
✅ Each reply invites further conversation

### Next Steps:
1. Attach browser tab → post Reddit replies manually
2. OR connect Pix → post organically from phone
3. OR wait for X appeal → resume Twitter engagement

---

## ARTIFACTS (Feb 26)
- `/blog/best-florida-dispensary-deals-week-february-2026` - New blog post
- `/components/seo/deal-schema.tsx` - Deal schema markup component
- `/components/seo/blog-schema.tsx` - Blog schema markup component
- `/locations/[city]` - 12 programmatic city pages (deployed yesterday)
- `memory/engagement-drafts-2026-02-26.md` - Reddit engagement drafts

## BLOCKERS
1. **X/Twitter @cannadealsfl** - SUSPENDED (needs Chad to appeal)
2. **Web search API** - 401 auth error
3. **Scrapling SERP extraction** - Not extracting results
4. **Browser relay** - Not attached (can't post to Reddit)


text:
# MEMORY.md - Long-Term Memory

## CannaDeals Marketing Stack

### X/Twitter Automation (ALREADY WORKING)

**Deal Posting Engine:** `skills/cdc-social-poster/scripts/post_deal_v2.py`
- Uses `twikit` library with cookies (NOT browser automation)
- Cookies stored at: `skills/cdc-social-poster/scripts/x_cookies.json`
- Tiered dispensary system:
  - **Tier 1 (Priority):** Jungle Boys, The Flowery, Cookies, GoldFlower
  - **Tier 2 (Mid):** Sunburn, Trulieve, MUV, RISE
  - **Tier 3 (Low):** Surterra, Curaleaf, Planet 13, Grow Healthy
  - **SKIP:** AYR, Green Dragon, Cannabist, Sanctuary, One Plant, Eden, Fluent
- Posting ratio: 5:2:1 (Tier1:Tier2:Tier3)
- Auto-verification before recording to DB
- Database: tracks posted deals to prevent duplicates

**Run deal post:**
```bash
cd skills/cdc-social-poster/scripts
python3 post_deal_v2.py x           # Post to X
python3 post_deal_v2.py x --dry-run # Preview without posting
```

**X Engagement Script:** `x_engagement.ts`
- Playwright-based stealth browser automation
- Human-like typing (30-100ms delays)
- Session warmup (scroll, like)
- Search cannabis/FL tweets
- Auto-reply, auto-follow
- Checks notifications
- Screenshots analytics

**Run engagement:**
```bash
npx tsx x_engagement.ts
```

**Multi-Platform Manager:** `skills/reddit-poster/scripts/socialManager.ts`
- Supports: Twitter, Reddit, Pinterest, LinkedIn
- All stealth features (human typing, image hash randomization)
- Persistent browser profile at `~/openclaw_social_browser_data/`

### Pinterest
**Script:** `skills/pinterestPoster.js`
- Image hash randomization
- Human-like typing
- Safe board naming strategy

### Reddit
**Script:** `skills/reddit-poster/scripts/redditPoster.ts`
- Cookies stored at `.reddit_cookies.txt`
- First-comment strategy for links
- Flair handling

---

## Production Access

**Carl (CannaDeals Production):**
- SSH: `ssh carl`
- Path: `~/cannadealsfl/`
- Deploy: `npm run build && pm2 restart cannadeals-web`
- Deals API: `https://www.cannadealsfl.com/api/deals`

---

## X Analytics Baseline (Feb 13, 2025)

- Impressions: 383 (7 days)
- Engagement rate: 9.3%
- Engagements: 36
- Replies: 2
- Profile visits: 10

**Problem:** Too much posting, not enough engagement
**Solution:** 70/30 split — 70% replies/engagement, 30% posts

## X Engagement Automation (Feb 13, 2025)

**Working Script:** `headed_engagement.py` in Docker container `canna`
- Patchright stealth browser (bypasses Error 226)
- Ctrl+Enter for reply submission
- Textarea-cleared detection for success
- LLM replies via Z.ai API (sassy voice profile)

**Docker Setup:**
```bash
docker exec -it canna bash
cd /workspace/scripts
python3 headed_engagement.py
```

**VNC Access:** `vnc://localhost:5999` (password: canna123)

**Cron:** 15-min engagement runs, 9AM-10PM EST (job ID: e56b8835-8122-45b9-ade5-a5ea652e1fc8)

**APIs:**
- Z.ai: `3887e2ed1a0a42ada0f9dc04461d3dce.O9dPzq1s2KQB2uMm`
- Gemini: `AIzaSyBSGlKZewG3RfqKJsPJr-ZR_BTJdLoxpqg`

---

## Blog Image Workflow (CRITICAL)

**Problem:** Blog posts with missing images = broken 404s cached by Cloudflare

**Solution:** ALWAYS generate images BEFORE publishing blog posts

### Workflow for New Blog Posts

1. **Generate image FIRST:**
   ```bash
   node scripts/generate-blog-image.mjs "Post Title" "post-slug" "theme"
   ```
   - Returns working image URL on success
   - Falls back to existing hero image if generation fails
   - NEVER publish without a working image

2. **THEN publish blog post with that image URL**

### Image Locations
- Production path: `/images/blog/headers/` on carl
- URL format: `https://cannadealsfl.com/images/blog/headers/{slug}.png`
- Fallback images: `florida-dispensary-hero.png`, `01-florida-dispensary-deals.png`

### Cron Job Publishing
The "Cannabis News Research + Blog" cron job MUST:
1. Generate header image via `generate-blog-image.mjs`
2. Wait for image to be confirmed on carl
3. Only THEN insert blog post with featured_image_url

---

## Account Info

- X Handle: @cannadealsfl
- Website: cannadealsfl.com

---

## Content Assets (Feb 21, 2026)

**Educational Condition Images:** `content-images/`
- `condition-ptsd.png` — Purple/blue, calming therapeutic
- `condition-chronic-pain.png` — Warm amber/cream, relief
- `condition-anxiety.png` — Soft green/blue, peaceful
- `condition-cancer.png` — Hopeful pink/white, compassionate
- `condition-epilepsy.png` — Medical blue/teal, neuroscience

All compliance-safe (no visible cannabis), 2K resolution. Ready for blog headers + social.

---

## Stealth Engagement Solution (Feb 13, 2025)

**Problem:** twikit API returns Error 226 for replies (automation detected)

**Solution Found:** Patchright + Cookie Injection
- `patchright` = undetected Playwright fork (passes Cloudflare, Datadome, etc.)
- Inject twikit cookies into browser session
- Human-like typing (50-120ms per char, random pauses)

**Working Script:** `skills/cdc-social-poster/scripts/x_stealth_engagement.py`

**Engagement Strategy:**
- Likes/Follows: Use twikit (fast, API-based) ✅
- Replies: Use Patchright stealth (browser-based) ✅
- Posts: Use twikit with `post_deal_v2.py` (working)

**Cookie Extraction (REMEMBER THIS!):**
- Docker container `canna` has working cookies at `/workspace/scripts/x_cookies.json`
- Uses `rookiepy` to extract from Chrome profile
- Refresh: `docker exec canna python3 /workspace/scripts/extract_x_cookies.py`
- Sync to workspace: `docker exec canna cat /workspace/scripts/x_cookies.json > skills/cdc-social-poster/scripts/x_cookies.json`

**ALTERNATE: Extract via Desktop2025 Browser Extension:**
1. User attaches X tab to OpenClaw Chrome extension on Desktop2025
2. I run: `browser action=act profile=chrome node=Desktop2025 request={kind:evaluate, fn:"() => document.cookie"}`
3. Get ct0, twid from that output
4. Ask user for `auth_token` from DevTools (Application > Cookies > x.com) - it's httpOnly
5. Combine and save to `skills/cdc-social-poster/scripts/x_cookies.json`

---

## Scraper Resilience Fixes (Feb 21, 2026)

**Critical Issue:** Scraper silently failed — only 20 deals in DB (should be 50+). User noticed before I did. Unacceptable.

### Root Causes
1. **Hash cache poisoning** — `.processed_hashes.json` never expired, blocking valid deals forever
2. **Missing dispensary** — "Eden" extracted but not in DB, deals rejected silently
3. **No failure alerts** — 0 inserted deals triggered no notifications
4. **Missing "Knack"** — New dispensary not mapped

### Permanent Fixes Applied

**1. Hash Cache TTL (unified-scraper-engine.py)**
```python
hash_cache_ttl_days: int = 7  # Auto-expire after 7 days
```
- `_load_processed_hashes()` filters expired entries on every run
- Expired hashes logged: `🧹 Expired N old hash entries`

**2. Unknown Dispensary Detection**
- Tracks unknowns in `stats['unknown_dispensaries']`
- ERROR log: `🚨 UNKNOWN DISPENSARY DETECTED: 'X'`
- Summary at end: `🚨 SCRAPER ALERT: Unknown dispensaries detected: [...]`

**3. Minimum Deal Count Validation**
```python
MINIMUM_DEALS_THRESHOLD = 5
if deals_inserted == 0: logger.error("🚨 SCRAPER ALERT: No deals inserted!")
elif deals_inserted < 5: logger.error("🚨 SCRAPER ALERT: Only N deals inserted...")
```

**4. Dispensary Database Updates**
- Added "Eden" to DB + mapping (was blocking 14 extracted deals)
- Added "Knack" to DB + mapping (was showing as unknown)
- Active dispensaries: 22 → 23

**5. Admin Tailscale Bypass (middleware.ts)**
```typescript
const isTailscaleIP = clientIP.startsWith('100.') || clientIP.startsWith('fd7a:115c:a1e0:')
if (isTailscaleIP) return NextResponse.next()  // Skip auth
```

### Quick Diagnostics
```bash
# Check deal count
curl -s 'https://www.cannadealsfl.com/api/deals?limit=1' | grep -oP '"total":\d+'

# Check for scraper alerts
grep -E 'SCRAPER ALERT|UNKNOWN DISPENSARY' ~/cannadealsfl/scraper-engine.log

# Check unknown dispensaries
grep 'No match found for dispensary' ~/cannadealsfl/scraper-engine.log | tail -5

# Manual hash cache clear (if needed)
rm ~/cannadealsfl/deal_images/.processed_hashes.json
```

### Monitoring Checklist
- [ ] Deal count > 40 at all times
- [ ] No "UNKNOWN DISPENSARY" errors in logs
- [ ] Scraper runs every 30 minutes (PM2 status)
- [ ] Hash cache auto-expiring (check timestamps in .processed_hashes.json)

### SEO Rankings Baseline (Feb 27, 2026)

| Keyword | Rank | Status |
|---------|------|--------|
| florida cannabis savings | #7 | Page 1 ✅ |
| dispensary coupons florida | #9 | Page 1 ✅ |
| cannabis deals florida | #19 | Page 2 - close! |
| best dispensary deals florida | #20 | Page 2 - close! |
| mmj deals florida | #25 | Page 3 |
| weed deals florida | NR | Needs content |
| florida dispensary deals | NR | Needs content |

**Goal:** Get "weed deals florida", "mmj deals florida", "cannabis deals florida" to page 1

---

## BI Council Tools

**SERP Checker:** `scripts/serp_checker.py`
- Check all keywords: `python3 serp_checker.py all`
- Generate report: `python3 serp_checker.py report`
- Requires: `source ~/projects/inbox-agent/venv-scrapling/bin/activate`

**BI Data Collector:** `scripts/bi_data_collector.py`
- Aggregates deals, content, SEO, scraper health
- Used by nightly BI Council (5 AM EST)

**Content Scorer:** `scripts/content_scorer.py`
- Deduplicates content ideas
- Scores 0-100 based on SEO value, patient value, originality

---

## Pixelbot / Pix — Mobile Posting Agent (Added 2026-02-22)
- **What:** OpenClaw agent running on Chad's Pixel 7 Pro via Termux
- **Telegram:** @pixelbot87_bot
- **Name:** Pix
- **Role:** Posts social media content FROM the phone so it looks organic, not bot-generated
- **Capabilities:** Can open LinkedIn, Reddit, Twitter/X, Chrome — tap, type, post, screenshot
- **How to send content to Pix for posting:**
  ```bash
  # From Fred, use the send script:
  ~/send_to_pixel.sh "POST TO LINKEDIN: [your caption here] URL: [link]"

  # Or via gateway API (real-time):
  ID=$(uuidgen)
  openclaw gateway call agent --url ws://100.94.190.4:18789 \
    --token 03152272e26e5001bf7539c722dcfa0799f7b602c91f5c38 \
    --params "{\"message\":\"...\",\"idempotencyKey\":\"$ID\",\"sessionKey\":\"agent:main:main\"}" \
    --timeout 10000 --json
  ```
- **Workflow:**
  1. You generate the content (caption, image, blog link)
  2. Send it to Pix with platform + instructions
  3. Pix opens the app on the phone and posts it manually
  4. Pix screenshots the post as proof
  5. Pix reports back
- **Platforms Pix can post to:** LinkedIn, Reddit, Twitter/X (appeal pending), Chrome/web
- **Why Pix:** Posting from a real phone with a real browser looks organic to platform algorithms vs API posting


text:
# TODO.md - Canna Task Queue

Last updated: 2026-02-27 07:27 UTC

---

## 🔴 BLOCKED — Waiting on Chad

These are STUCK. I've reported them. Chad needs to unblock.

- [x] ~~**Restore skills/ directory**~~ — ✅ RESTORED (Feb 22)
- [ ] **X/Twitter @cannadealsfl suspension** — Confirmed suspended. Error: "Missing TwitterUserNotSuspended". Needs login check + appeal.
- [x] ~~**Fix admin/page.tsx syntax error**~~ — ✅ FIXED (npm build succeeds now)

---

## 🟡 IN PROGRESS / ONGOING

- [ ] **Daily X engagement** — Run headed_engagement.py (Docker `canna`) during 9AM-10PM EST window. 2-3 replies per run to dispensary accounts. Working.
- [ ] **Fix sarah_engagement.py DOM selector** — Headless browser returns 0 tweets. `[data-testid="tweet"]` selector not matching. Needs debug session.
- [x] **Write daily/weekly blog post** — ✅ DONE Feb 21. Weekend Deals Roundup published.
  - URL: https://cannadealsfl.com/blog/weekend-deals-roundup-february-21-23-2026

---

## 🟢 READY TO EXECUTE (when skills/ restored)

- [ ] **Resume deal posting** — `post_deal_v2.py x` — Tier 1/2/3 dispensary deals on X
- [ ] **Reddit engagement** — Danny persona. r/FLMedicalTrees, r/FLMedical community replies
- [ ] **Pinterest deal posts** — Blog content bridge pages

---

## ✅ DONE RECENTLY

- [x] Blog post published Feb 17: "Florida Court Upholds THC Limits on Hemp: What It Means for MMJ Patients"
  - URL: https://cannadealsfl.com/blog/florida-court-upholds-thc-limits-hemp-mmj-patients-2026
- [x] Carl SSH restored (was down 60+ hours, Feb 14-17)
- [x] headed_engagement.py — confirmed working, 2 replies posted Feb 17


thinking:
Alright, let me assess the TODO.md status:

**BLOCKED:**
1. X/Twitter @cannadealsfl suspension — Still blocked, needs Chad to appeal

**IN PROGRESS:**
1. Daily X engagement — Working via Docker `canna`, but needs to be during 9AM-10PM EST window
2. Fix sarah_engagement.py DOM selector — Headless browser returns 0 tweets, needs debug
3. Blog post done (Feb 21)

**READY TO EXECUTE:**
1. Resume deal posting — Blocked by X suspension
2. Reddit engagement — Requires browser relay or Pix
3. Pinterest deal posts — Can run anytime

**Current time:** Friday, February 27th, 2026 — 9:06 AM (UTC) = 4:06 AM EST

**Timing considerations:**
- 4:06 AM EST is outside the engagement window (9AM-10PM EST)
- X engagement script should only run during 9AM-10PM EST
- Pinterest could run but not optimal at 4 AM
- Reddit could run but also not optimal at 4 AM

**What I can do:**
1. Check deal count in database to ensure scraper is healthy
2. Check SEO rankings (SERP check)
3. Run BI data collector
4. Blog content preparation

Let me check the current deal count and system health first, then report status.

exec

command: curl -s 'https://www.cannadealsfl.com/api/deals?limit=1' 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"Total deals: {d.get('total', 'N/A')}\")" 2>/dev/null || echo "API check failed"


toolName: exec

text: Total deals: 49

status: completed

aggregated: Total deals: 49


thinking:
Good, deals are healthy (49 deals, above the minimum threshold of 40).

Current time: 9:06 AM UTC = 4:06 AM EST

This is outside the engagement window (9AM-10PM EST). X engagement, Reddit engagement, and Pinterest posting are all suboptimal at this hour.

Let me check the SERP rankings to provide a status update.

command: source ~/projects/inbox-agent/venv-scrapling/bin/activate && python3 ~/cannadealsfl/scripts/serp_checker.py all 2>/dev/null | tail -20


text: Command still running (session keen-wharf, pid 3219910). Use process (list/poll/log/write/kill/clear/remove) for follow-up.

status: running

sessionId: keen-wharf


thinking: The SERP checker is running. Let me wait for it to complete.

process

action: poll


toolName: process

text:
Checking: medical marijuana deals florida
  ⚠ Not in top 30

Checking: dispensary coupons florida
  ✅ Rank #24: https://cannadealsfl.com/

Checking: florida cannabis savings
  ✅ Rank #13: https://cannadealsfl.com/

Checking: best dispensary deals florida
  ✅ Rank #18: https://www.cannadealsfl.com/dispensaries/curaleaf

Checking: cheap weed florida
  ⚠ Not in top 30

==================================================
SUMMARY: 5/10 keywords ranked
Page 1: 2
==================================================

Process exited with code 0.


thinking: Got the SERP results. Let me also check if there are any scraper errors or alerts.

exec

command: ssh carl "grep -E 'SCRAPER ALERT|UNKNOWN DISPENSARY' ~/cannadealsfl/scraper-engine.log 2>/dev/null | tail -5" 2>/dev/null || echo "SSH/scraper check failed"


text: (no output)


thinking:
No scraper alerts — that's good. System is healthy.

Now let me compile the status report:

**System Status:**
- Deals: 49 active (healthy, above threshold of 40)
- Scraper: No alerts, running normally
- SEO Rankings: 5/10 keywords ranked, 2 on page 1

**Blockers:**
- X/Twitter @cannadealsfl: Still suspended (needs Chad to appeal)
- sarah_engagement.py: DOM selector issue (headless returns 0 tweets)

**Timing:**
- Current time: 4:06 AM EST
- Engagement window: 9AM-10PM EST
- Outside engagement window — no X/Reddit/Pinterest posting advisable

**What's Ready:**
- Pinterest posting (but not optimal timing)
- Reddit engagement (requires browser relay or Pix)
- Blog content creation
- Deal posting (blocked by X suspension)

Since it's 4 AM EST, the best action is to report status and wait. The BI Council cron runs at 5 AM EST (10 AM UTC) and will do the heavy analysis.

I should report this as plain text for the cron delivery.

text:
**Cron TODO Check — Friday, Feb 27, 2026 at 9:06 AM UTC (4:06 AM EST)**

---

## System Health ✅
- **Deals:** 49 active (healthy — above 40 threshold)
- **Scraper:** No alerts, running normally

## SEO Rankings (Fresh)
| Keyword | Rank | Status |
|---------|------|--------|
| florida cannabis savings | #13 | Page 2 |
| dispensary coupons florida | #24 | Page 3 |
| best dispensary deals florida | #18 | Page 2 |

**Summary:** 5/10 keywords ranked, 2 on page 1

---

## Blockers 🔴
1. **X/Twitter @cannadealsfl** — Still suspended. Awaiting Chad's appeal.
2. **sarah_engagement.py** — DOM selector issue (headless returns 0 tweets)

---

## Task Status
| Task | Status | Notes |
|------|--------|-------|
| Daily X engagement | ⏸ PAUSED | Outside window (9AM-10PM EST) |
| Reddit engagement | ⏸ PAUSED | Need browser relay or Pix |
| Pinterest posting | 🟢 READY | Not optimal at 4 AM |
| Blog content | 🟢 READY | Can prepare anytime |
| Deal posting | 🔴 BLOCKED | X suspended |

---

## Verdict

**4:06 AM EST** — Outside engagement window. No actions advisable at this hour.

BI Council cron runs at 10 AM UTC (5 AM EST) and will do full analysis.

**Sleeping until next nudge.**

stopReason: stop