Sync docs, audit artifacts, project notes, and VerdictTank proposal docs
- audit/phase-one + phase-two: security audit briefs, findings, credential-rotation plan, Docker-USER hardening scripts, rollback refs - disaster-recovery/restore-test-log.md + backup-dr-audit-2026-08-10.md - clients/ (modelortho SEO audit, ai-biz-dev competitive landscape), notes/ (tiktok strategy) - projects/: front-desk-voice-agent, seo-visibility-checker product plan, hotnow-savannah HTML, resend-transactional-email, backup-dashboard-enhancements, code-review-graph, seo-ci-architecture - proposals/verdicttank/: architecture v4.0, methodology, judge-pool review, consolidation reasoning, cross-check review - docs/super-search/firecrawl-provider-strategy.md - updates: CHANGELOG, model-chain, projects-master-readme, intelsight.io - .gitignore: exclude nested standalone repos (seo-tool, venturebuilt)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Backup Dashboard Enhancements (Queued)
|
||||
|
||||
## Problem
|
||||
The Backups tab tells you WHAT is wrong but not enough to investigate or fix it.
|
||||
|
||||
## Plan
|
||||
|
||||
### 1. Per-item expandability
|
||||
Click any critical/warning row to expand:
|
||||
- Server (which box)
|
||||
- Service name
|
||||
- Last OK timestamp + days stale
|
||||
- Actual error message from logs
|
||||
- Suggested fix based on error pattern
|
||||
|
||||
### 2. Server context tags
|
||||
Every health check item tagged with origin server. Cross-reference with live server status from Uptime Kuma.
|
||||
|
||||
### 3. Issue age / staleness
|
||||
Show "3 days ago" vs "2 weeks ago" — stale issues are different from fresh ones.
|
||||
|
||||
### 4. One-click investigation links
|
||||
- Open the exact log file for that backup job
|
||||
- Server's Uptime Kuma status page
|
||||
- The backup script itself (read-only view)
|
||||
|
||||
### 5. Recurrence tracking
|
||||
Track failure history per check. Flag: "⚠️ 3rd failure in 14 days → escalating"
|
||||
|
||||
## Implementation
|
||||
All additive — same JSON, richer fields. Health monitor script needs to collect more metadata per check.
|
||||
|
||||
## Status
|
||||
Queued 2026-08-10. Not scheduled.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Code-Review Graph (Structural Code Knowledge) — Future Project
|
||||
|
||||
**Status:** Future Projects — Internal Tooling Adoption
|
||||
**Saved:** 2026-08-16
|
||||
**Category:** How Sho'Nuff & Germaine work (internal agent tooling)
|
||||
**Owner:** IT Pro Partner (Sho'Nuff)
|
||||
**Source:** https://github.com/rpmalouin/deepseek-harness (fork of deepseek-ai/deepseek-harness)
|
||||
|
||||
---
|
||||
|
||||
## What it is
|
||||
|
||||
`deepseek-harness` (`dsh`) is DeepSeek AI's open-source agent harness: "everything is a plugin" on top of Cordis, a plugin framework. TypeScript/Node, MIT, developer preview.
|
||||
|
||||
The `rpmalouin` fork layers three additions on top of stock upstream:
|
||||
1. `code-review-graph` — a knowledge graph of a repo's code communities wired into every coding-agent surface.
|
||||
2. OpenRouter LLM routing (headless one-shot tasks).
|
||||
3. Hermes/agent delegation — designed to be driven as a local coding sub-agent from Hermes.
|
||||
|
||||
## Why it matters
|
||||
|
||||
Today Sho'Nuff navigates Germaine's repos (itpp-infrastructure, homelab, scripts, project folders) via ripgrep + read_file. That is linear scanning: token-heavy, slow, and it misses callers and dependents that grep never surfaces.
|
||||
|
||||
A structural code graph flips that: understand the code through its dependency graph first, then read the files you actually need.
|
||||
|
||||
## What to adopt
|
||||
|
||||
1. **code-review-graph pattern** — build a queryable structural graph per repo (code communities, callers, dependents, module boundaries). Query the graph before touching files. Start with itpp-infrastructure and homelab.
|
||||
2. **"Everything is a plugin" discipline** — tighten the skill system so each skill declares its effects (what it adds, what it can reverse), making per-task composition deliberate rather than implicit.
|
||||
|
||||
## What NOT to adopt
|
||||
|
||||
- **Do NOT swap Hermes for `dsh`.** Hermes already covers roughly 80% of the operating model:
|
||||
- skills = the plugin system
|
||||
- session history + memory = the session log / source of truth
|
||||
- `delegate_task` = the headless one-shot contract
|
||||
- toolset scoping = scoped tools per agent
|
||||
- **`dsh` as a 4th coding backend** (next to Codex, Claude Code, OpenCode) is marginal. Skip unless one of the three fails Germaine.
|
||||
|
||||
## Reference notes
|
||||
|
||||
- The fork's `FORK.md` documents the code-review-graph pattern and the OpenRouter routing overlay mechanism (the live patch file and keys are local-only, gitignored, and never shipped).
|
||||
- The `docs/architecture.md` describes the Cordis plugin tree, capability seams (Service Definition / Provider / Consumer), and the "model-visible means logged" session-log invariant.
|
||||
- The upstream is `deepseek-ai/deepseek-harness` (MIT); the fork is 4 commits ahead and adds only scaffolding, no upstream behavior changes.
|
||||
|
||||
## Source
|
||||
|
||||
- https://github.com/rpmalouin/deepseek-harness (fork of deepseek-ai/deepseek-harness)
|
||||
- Files reviewed: README, FORK.md, docs/architecture.md
|
||||
- Retrieved: 2026-08-16
|
||||
@@ -0,0 +1,875 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert HotNow Savannah v2 markdown to Anita/DM Serif HTML."""
|
||||
|
||||
import re
|
||||
import html as html_mod
|
||||
|
||||
def read_md(path):
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
def clean_text(text):
|
||||
"""Strip markdown bold/italic but keep content, handle inline code."""
|
||||
# Bold
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
||||
# Italic
|
||||
text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text)
|
||||
# Inline code
|
||||
text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
|
||||
# Links
|
||||
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', text)
|
||||
return text
|
||||
|
||||
def escape_for_html(text):
|
||||
"""Escape HTML entities but preserve our strong/em/code/a tags."""
|
||||
# Protect tags we want to keep
|
||||
text = text.replace('<strong>', '\x00S\x00')
|
||||
text = text.replace('</strong>', '\x00/S\x00')
|
||||
text = text.replace('<em>', '\x00E\x00')
|
||||
text = text.replace('</em>', '\x00/E\x00')
|
||||
text = text.replace('<code>', '\x00C\x00')
|
||||
text = text.replace('</code>', '\x00/C\x00')
|
||||
text = text.replace('<a href=', '\x00A\x00')
|
||||
text = text.replace('</a>', '\x00/A\x00')
|
||||
text = text.replace('">', '\x00Q\x00')
|
||||
# Escape
|
||||
text = html_mod.escape(text, quote=False)
|
||||
# Restore
|
||||
text = text.replace('\x00S\x00', '<strong>')
|
||||
text = text.replace('\x00/S\x00', '</strong>')
|
||||
text = text.replace('\x00E\x00', '<em>')
|
||||
text = text.replace('\x00/E\x00', '</em>')
|
||||
text = text.replace('\x00C\x00', '<code>')
|
||||
text = text.replace('\x00/C\x00', '</code>')
|
||||
text = text.replace('\x00A\x00', '<a href=')
|
||||
text = text.replace('\x00/A\x00', '</a>')
|
||||
text = text.replace('\x00Q\x00', '">')
|
||||
return text
|
||||
|
||||
def parse_table(lines, start_idx):
|
||||
"""Parse a markdown table starting at start_idx. Returns (html_rows, next_idx)."""
|
||||
i = start_idx
|
||||
# Find header row
|
||||
if i >= len(lines) or not lines[i].strip().startswith('|'):
|
||||
return None, start_idx
|
||||
|
||||
header_line = lines[i].strip()
|
||||
header_cells = [c.strip() for c in header_line.split('|')[1:-1]]
|
||||
i += 1
|
||||
|
||||
# Skip separator line
|
||||
if i < len(lines) and re.match(r'^\|[\s\-:|]+\|$', lines[i].strip()):
|
||||
i += 1
|
||||
|
||||
# Parse data rows
|
||||
data_rows = []
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if not line.startswith('|'):
|
||||
break
|
||||
cells = [c.strip() for c in line.split('|')[1:-1]]
|
||||
data_rows.append(cells)
|
||||
i += 1
|
||||
|
||||
# Build HTML
|
||||
html = '<table>\n<thead>\n<tr>\n'
|
||||
for cell in header_cells:
|
||||
html += f'<th>{escape_for_html(clean_text(cell))}</th>\n'
|
||||
html += '</tr>\n</thead>\n<tbody>\n'
|
||||
for row in data_rows:
|
||||
html += '<tr>\n'
|
||||
for cell in row:
|
||||
html += f'<td>{escape_for_html(clean_text(cell))}</td>\n'
|
||||
html += '</tr>\n'
|
||||
html += '</tbody>\n</table>\n'
|
||||
|
||||
return html, i
|
||||
|
||||
def parse_code_block(lines, start_idx):
|
||||
"""Parse a code block. Returns (html, next_idx)."""
|
||||
i = start_idx
|
||||
if not lines[i].strip().startswith('```'):
|
||||
return None, start_idx
|
||||
i += 1
|
||||
content_lines = []
|
||||
while i < len(lines):
|
||||
if lines[i].strip().startswith('```'):
|
||||
i += 1
|
||||
break
|
||||
content_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
content = ''.join(content_lines)
|
||||
content = escape_for_html(content)
|
||||
html = f'<pre><code>{content}</code></pre>\n'
|
||||
return html, i
|
||||
|
||||
def parse_list(lines, start_idx):
|
||||
"""Parse a markdown list (ordered or unordered). Returns (html, next_idx)."""
|
||||
i = start_idx
|
||||
items = []
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
# Ordered list
|
||||
m = re.match(r'^(\d+)\.\s+(.+)$', line)
|
||||
if m:
|
||||
items.append(('ol', escape_for_html(clean_text(m.group(2)))))
|
||||
i += 1
|
||||
continue
|
||||
# Unordered list
|
||||
m = re.match(r'^[-*]\s+(.+)$', line)
|
||||
if m:
|
||||
items.append(('ul', escape_for_html(clean_text(m.group(1)))))
|
||||
i += 1
|
||||
continue
|
||||
break
|
||||
|
||||
if not items:
|
||||
return None, start_idx
|
||||
|
||||
list_type = items[0][0]
|
||||
html = f'<{list_type}>\n'
|
||||
for lt, item_text in items:
|
||||
html += f'<li>{item_text}</li>\n'
|
||||
html += f'</{list_type}>\n'
|
||||
return html, i
|
||||
|
||||
def parse_blockquote(lines, start_idx):
|
||||
"""Parse blockquote lines. Returns (html, next_idx)."""
|
||||
i = start_idx
|
||||
content_lines = []
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('>'):
|
||||
content_lines.append(line[1:].strip())
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if not content_lines:
|
||||
return None, start_idx
|
||||
|
||||
content = ' '.join(content_lines)
|
||||
content = escape_for_html(clean_text(content))
|
||||
html = f'<blockquote><p>{content}</p></blockquote>\n'
|
||||
return html, i
|
||||
|
||||
def build_toc(sections):
|
||||
"""Build TOC HTML from section list."""
|
||||
toc_html = '<nav class="toc">\n'
|
||||
for sec_id, sec_title in sections:
|
||||
toc_html += f'<a href="#{sec_id}">{sec_title}</a>\n'
|
||||
toc_html += '</nav>\n'
|
||||
return toc_html
|
||||
|
||||
def convert_md_to_html(md_text):
|
||||
"""Main conversion function."""
|
||||
lines = md_text.split('\n')
|
||||
|
||||
sections = [] # (id, title)
|
||||
html_body = ''
|
||||
current_section_id = None
|
||||
extra_sections = [] # appendices
|
||||
|
||||
i = 0
|
||||
|
||||
# Skip first line (# title) and metadata
|
||||
# We'll handle hero separately
|
||||
while i < len(lines) and not lines[i].startswith('## Table of Contents'):
|
||||
i += 1
|
||||
|
||||
# Skip TOC and horizontal rule
|
||||
while i < len(lines) and not lines[i].startswith('## 1. Executive Summary'):
|
||||
i += 1
|
||||
|
||||
# Now process each ## section
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
# Check for ## section header
|
||||
m = re.match(r'^##\s+(.+)$', line)
|
||||
if m:
|
||||
section_title = m.group(1).strip()
|
||||
# Generate ID
|
||||
sec_id = section_title.lower()
|
||||
# Extract number prefix
|
||||
num_match = re.match(r'^(\d+)\.\s+(.+)$', section_title)
|
||||
if num_match:
|
||||
num = num_match.group(1)
|
||||
sec_name = num_match.group(2)
|
||||
sec_id = f'section-{num}'
|
||||
sections.append((sec_id, f'{num}. {sec_name}'))
|
||||
elif section_title.startswith('Appendix'):
|
||||
sec_id = section_title.lower().replace(' ', '-').replace(':', '').replace('(', '').replace(')', '')
|
||||
extra_sections.append(sec_id)
|
||||
sections.append((sec_id, section_title))
|
||||
else:
|
||||
sec_id = section_title.lower().replace(' ', '-').replace('.', '')
|
||||
sections.append((sec_id, section_title))
|
||||
|
||||
current_section_id = sec_id
|
||||
|
||||
# Decide if this is in the appendix (outside main card section)
|
||||
is_appendix = section_title.startswith('Appendix')
|
||||
|
||||
if not is_appendix:
|
||||
html_body += f'<section class="section-card" id="{sec_id}">\n'
|
||||
html_body += f'<h2>{escape_for_html(clean_text(section_title))}</h2>\n'
|
||||
else:
|
||||
html_body += f'<section class="appendix-section" id="{sec_id}">\n'
|
||||
html_body += f'<h2>{escape_for_html(clean_text(section_title))}</h2>\n'
|
||||
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for ### sub-section
|
||||
m = re.match(r'^###\s+(.+)$', line)
|
||||
if m:
|
||||
sub_title = m.group(1).strip()
|
||||
html_body += f'<h3>{escape_for_html(clean_text(sub_title))}</h3>\n'
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Horizontal rule
|
||||
if line.startswith('---'):
|
||||
if current_section_id and not current_section_id.startswith('appendix'):
|
||||
html_body += '</section>\n'
|
||||
current_section_id = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Code block
|
||||
if line.startswith('```'):
|
||||
code_html, i = parse_code_block(lines, i)
|
||||
if code_html:
|
||||
html_body += code_html
|
||||
continue
|
||||
|
||||
# Table
|
||||
if line.startswith('|'):
|
||||
table_html, i = parse_table(lines, i)
|
||||
if table_html:
|
||||
html_body += table_html
|
||||
continue
|
||||
|
||||
# Blockquote
|
||||
if line.startswith('>'):
|
||||
bq_html, i = parse_blockquote(lines, i)
|
||||
if bq_html:
|
||||
html_body += bq_html
|
||||
continue
|
||||
|
||||
# List
|
||||
list_html, new_i = parse_list(lines, i)
|
||||
if list_html:
|
||||
html_body += list_html
|
||||
i = new_i
|
||||
continue
|
||||
|
||||
# Regular paragraph (non-empty)
|
||||
if line:
|
||||
# Check for bold-only short lines (like TL;DR)
|
||||
para = escape_for_html(clean_text(line))
|
||||
html_body += f'<p>{para}</p>\n'
|
||||
|
||||
i += 1
|
||||
|
||||
# Close last section if open
|
||||
if current_section_id:
|
||||
html_body += '</section>\n'
|
||||
|
||||
return sections, html_body
|
||||
|
||||
# CSS template
|
||||
CSS = ''' :root {
|
||||
--accent: #2563eb;
|
||||
--accent-dark: #1d4ed8;
|
||||
--accent-light: #eff6ff;
|
||||
--accent-glow: rgba(37,99,235,0.15);
|
||||
--navy: #0f172a;
|
||||
--navy-light: #1e293b;
|
||||
--bg: #f8f9fb;
|
||||
--card: #ffffff;
|
||||
--border: #e5e7eb;
|
||||
--text: #1a1a2e;
|
||||
--text-secondary: #6b7280;
|
||||
--text-muted: #9ca3af;
|
||||
--green: #10b981;
|
||||
--green-bg: #ecfdf5;
|
||||
--amber: #d97706;
|
||||
--amber-bg: #fffbeb;
|
||||
--red: #dc2626;
|
||||
--red-bg: #fef2f2;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h1, h2, h3, .price, .stat-value {
|
||||
font-family: 'DM Serif Display', Georgia, 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #0f172a, #1e293b 40%, #1e3a5f 70%, #1d4ed8);
|
||||
color: #ffffff;
|
||||
padding: 80px 24px 70px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -25%;
|
||||
width: 150%;
|
||||
height: 200%;
|
||||
background: radial-gradient(ellipse at 30% 50%, var(--accent-glow) 0%, transparent 60%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
padding: 6px 18px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
background: rgba(255,255,255,0.12);
|
||||
color: rgba(255,255,255,0.9);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 52px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.hero .subtitle {
|
||||
font-size: 20px;
|
||||
color: rgba(255,255,255,0.75);
|
||||
max-width: 700px;
|
||||
margin: 0 auto 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.hero .meta {
|
||||
font-size: 14px;
|
||||
color: rgba(255,255,255,0.55);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc-wrap {
|
||||
max-width: 960px;
|
||||
margin: -28px auto 0;
|
||||
padding: 0 24px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
display: inline-block;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: var(--accent-light);
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.toc a:hover {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Section Cards */
|
||||
.sections {
|
||||
max-width: 960px;
|
||||
margin: 32px auto 0;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 40px;
|
||||
margin-bottom: 24px;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.section-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.section-card h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: var(--navy);
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 2px solid var(--accent-light);
|
||||
}
|
||||
|
||||
.section-card h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: var(--text);
|
||||
margin: 28px 0 12px;
|
||||
}
|
||||
|
||||
.section-card h3:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.section-card p {
|
||||
margin-bottom: 16px;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.section-card p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0 28px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-dark);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: var(--accent-light);
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 20px 24px;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin: 16px 0 24px;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-dark);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
blockquote {
|
||||
border-left: 4px solid var(--accent);
|
||||
background: var(--accent-light);
|
||||
padding: 16px 20px;
|
||||
margin: 16px 0 24px;
|
||||
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
margin: 0 !important;
|
||||
color: var(--navy-light);
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
ul, ol {
|
||||
margin: 12px 0 20px 24px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
ul li, ol li {
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Pricing grid */
|
||||
.pricing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
margin: 20px 0 28px;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.pricing-card.featured {
|
||||
border: 2px solid var(--accent);
|
||||
background: var(--accent-light);
|
||||
}
|
||||
|
||||
.pricing-card h4 {
|
||||
font-family: 'DM Serif Display', Georgia, serif;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 4px;
|
||||
color: var(--navy);
|
||||
}
|
||||
|
||||
.pricing-card .price {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: var(--accent);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pricing-card ul {
|
||||
margin: 0 0 16px 18px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.pricing-card ul li {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pricing-card ul li::before {
|
||||
content: '+';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--green);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.tag-critical { background: var(--red-bg); color: var(--red); }
|
||||
.tag-high { background: #fef3c7; color: #b45309; }
|
||||
.tag-medium { background: var(--amber-bg); color: var(--amber); }
|
||||
.tag-low { background: var(--green-bg); color: #059669; }
|
||||
.tag-phase { background: var(--accent-light); color: var(--accent); }
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* VerdictTank */
|
||||
.verdicttank {
|
||||
background: var(--red-bg);
|
||||
border: 1px solid #fecaca;
|
||||
border-left: 4px solid var(--red);
|
||||
border-radius: var(--radius);
|
||||
padding: 28px 32px;
|
||||
margin: 24px auto 0;
|
||||
max-width: 960px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.verdicttank h2 {
|
||||
font-family: 'DM Serif Display', Georgia, serif;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: var(--red);
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.verdicttank p {
|
||||
color: #991b1b;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.verdicttank a {
|
||||
color: var(--red);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--navy);
|
||||
color: rgba(255,255,255,0.8);
|
||||
text-align: center;
|
||||
padding: 40px 24px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
.footer .title {
|
||||
font-family: 'DM Serif Display', Georgia, serif;
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.footer .links {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.footer .links a {
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.footer .links a:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.footer .links a.current {
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Appendices */
|
||||
.appendix-section {
|
||||
max-width: 960px;
|
||||
margin: 0 auto 24px;
|
||||
padding: 32px 40px;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
border-left: 3px solid var(--border);
|
||||
}
|
||||
|
||||
.appendix-section h2 {
|
||||
font-family: 'DM Serif Display', Georgia, serif;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.appendix-section table {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
body { background: #fff; }
|
||||
.hero { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
||||
.section-card { box-shadow: none; break-inside: avoid; }
|
||||
.toc-wrap { display: none; }
|
||||
.footer { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 640px) {
|
||||
.hero { padding: 60px 20px 50px; }
|
||||
.hero h1 { font-size: 36px; }
|
||||
.hero .subtitle { font-size: 16px; }
|
||||
.section-card { padding: 24px 20px; }
|
||||
.section-card h2 { font-size: 22px; }
|
||||
table { font-size: 12px; }
|
||||
thead th, tbody td { padding: 8px 10px; }
|
||||
.pricing-grid { grid-template-columns: 1fr; }
|
||||
.toc { padding: 14px 16px; }
|
||||
.toc a { font-size: 12px; padding: 5px 10px; }
|
||||
}
|
||||
'''
|
||||
|
||||
def build_full_html(sections, body_html):
|
||||
"""Build the complete HTML document."""
|
||||
toc_html = build_toc(sections)
|
||||
|
||||
html = f'''<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HotNow Savannah - Business Proposal v2.0</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;1,9..40,400&family=DM+Serif+Display&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
{CSS}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Hero -->
|
||||
<header class="hero">
|
||||
<div class="badge">IT Pro Partner - Product Division</div>
|
||||
<h1>HotNow Savannah</h1>
|
||||
<p class="subtitle">Real-Time Local Discovery Engine - Savannah, GA Launch</p>
|
||||
<p class="meta">Confidential - Version 2.0 - August 11, 2026</p>
|
||||
</header>
|
||||
|
||||
<!-- TOC -->
|
||||
<div class="toc-wrap">
|
||||
{toc_html}
|
||||
</div>
|
||||
|
||||
<!-- Sections -->
|
||||
<div class="sections">
|
||||
{body_html}
|
||||
</div>
|
||||
|
||||
<!-- VerdictTank -->
|
||||
<div class="verdicttank">
|
||||
<h2>VerdictTank Reviewed</h2>
|
||||
<p>This proposal has not yet been reviewed by VerdictTank. Schedule a review at <a href="https://verdicttank.com">verdicttank.com</a>.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<p class="title">HotNow Savannah - Confidential Business Proposal</p>
|
||||
<p>Prepared by IT Pro Partner - Product Division · August 11, 2026</p>
|
||||
<div class="links">
|
||||
<a href="/hotnow/">Original Proposal (v1)</a>
|
||||
<a href="#">Pipeline Verdict (pending)</a>
|
||||
<a href="#" class="current">Post-Review Proposal (v2)</a>
|
||||
<a href="https://itpropartner.com">IT Pro Partner</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>'''
|
||||
return html
|
||||
|
||||
def main():
|
||||
md_path = '/root/projects/itpp-infrastructure/projects/hotnow-savannah-v2.md'
|
||||
out_path = '/root/projects/itpp-infrastructure/projects/hotnow-savannah-v2.html'
|
||||
|
||||
md_text = read_md(md_path)
|
||||
sections, body_html = convert_md_to_html(md_text)
|
||||
|
||||
full_html = build_full_html(sections, body_html)
|
||||
|
||||
# Post-process: strip any remaining em dashes, en dashes, smart quotes
|
||||
full_html = full_html.replace('\u2014', '-') # em dash
|
||||
full_html = full_html.replace('\u2013', '-') # en dash
|
||||
full_html = full_html.replace('\u201c', '"') # left smart quote
|
||||
full_html = full_html.replace('\u201d', '"') # right smart quote
|
||||
full_html = full_html.replace('\u2018', "'") # left smart apostrophe
|
||||
full_html = full_html.replace('\u2019', "'") # right smart apostrophe
|
||||
|
||||
# Replace any -- with " - " but ONLY outside <style> tags and HTML comments
|
||||
def fix_double_hyphens(text):
|
||||
# Protect <style> blocks and HTML comments
|
||||
protected = {}
|
||||
counter = [0]
|
||||
def protect_style(m):
|
||||
key = f'\x00STYLE{counter[0]}\x00'
|
||||
counter[0] += 1
|
||||
protected[key] = m.group(0)
|
||||
return key
|
||||
def protect_comment(m):
|
||||
key = f'\x00COMMENT{counter[0]}\x00'
|
||||
counter[0] += 1
|
||||
protected[key] = m.group(0)
|
||||
return key
|
||||
|
||||
text = re.sub(r'<style>.*?</style>', protect_style, text, flags=re.DOTALL)
|
||||
text = re.sub(r'<!--.*?-->', protect_comment, text, flags=re.DOTALL)
|
||||
|
||||
# Now safe to replace -- in remaining content
|
||||
text = re.sub(r'--', ' - ', text)
|
||||
|
||||
# Restore protected blocks
|
||||
for key, value in protected.items():
|
||||
text = text.replace(key, value)
|
||||
|
||||
return text
|
||||
|
||||
full_html = fix_double_hyphens(full_html)
|
||||
|
||||
with open(out_path, 'w') as f:
|
||||
f.write(full_html)
|
||||
|
||||
print(f"Written {len(full_html)} chars to {out_path}")
|
||||
print(f"Sections: {len(sections)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,161 @@
|
||||
# Sho'Nuff Front-Desk — AI Voice Lead Qualification & Booking
|
||||
|
||||
**Saved:** 2026-08-24
|
||||
**Status:** Future Project — Competitive Teardown + Build Path
|
||||
**Category:** SaaS Product / Revenue
|
||||
**Owner:** IT Pro Partner (Sho'Nuff)
|
||||
**Inspiration:** Qexo.ai competitive teardown (2026-08-24)
|
||||
|
||||
---
|
||||
|
||||
## The Competitive Trigger
|
||||
|
||||
Qexo.ai is AI voice agents for **lead qualification + appointment booking**, aimed at home-services / SMB verticals (solar, HVAC, home services, real estate, insurance, clinics). It is the third tool in a row we've teardown'd (after tranx.io and dozier.io) shaped as a single-purpose vertical tool on generic AI plumbing: narrow SMB wedge + clean before/after loop + evidence/confidence. The pattern is now a signal, not a coincidence.
|
||||
|
||||
**Qexo's four-step loop:**
|
||||
|
||||
```
|
||||
1. Capture — web/form intake catches the lead while intent is hot
|
||||
message + contact + consent attached to ONE record
|
||||
2. Understand — "lead intelligence" scores intent / urgency / service-fit
|
||||
0–100 qualification score, flags missing details
|
||||
3. Call & Book — with consent, voice agent calls OUT, gets context,
|
||||
books against live calendar, confirms address/access notes
|
||||
4. Manage — unified lead workspace (inquiry + transcript +
|
||||
qualification + appointment), human override
|
||||
```
|
||||
|
||||
**Why Qexo is clever (and what to steal):**
|
||||
- **Full-context handoff** — the outbound call inherits the web intake, never starts over. The caller already knows what the lead asked for.
|
||||
- **Transparent AI + consent** — kills the FTC/legal objection up front. Voice agents calling out is the single most legally exposed move in this space; Qexo neutralizes it by design.
|
||||
- **"Not another inbox"** — explicitly positioned as NOT a CRM. A unified lead workspace, not a login you'll ignore.
|
||||
|
||||
---
|
||||
|
||||
## The Honest Read: We Already Own 90% of This
|
||||
|
||||
The gap is **productization, not capability.** We have every component running today:
|
||||
|
||||
| Component | Existing Asset | Status |
|
||||
|---|---|---|
|
||||
| Outbound voice calling | `shonuff-voice-caller`, `twilio-voice-calling` skills (Twilio + ElevenLabs) | ✅ Live |
|
||||
| Inbound call handling | `ai-receptionist.md` (VoIPSimplicity Concierge) | 📐 Designed, not built |
|
||||
| Missed-call capture | `missed-call-lead-recovery.md` (Twilio SMS text-back) | 📐 Designed, not built |
|
||||
| Voice stack (STT→LLM→TTS) | `voice-agent-deployment` skill — Hermes Voice (xAI realtime) + Kokoro/faster-whisper open-source | ✅ Live |
|
||||
| Orchestration brain | Hermes Agent (personality, memory, tool routing) | ✅ Live |
|
||||
| Calendar | Rally family calendar + booking patterns | ✅ Live |
|
||||
|
||||
**The missing 10%** — the part that makes it a *product* rather than a demo:
|
||||
1. **A lead-record model that survives web → voice → calendar** — one object holding intake message, contact, consent, qualification score, transcript, and appointment, all keyed to the same lead. This is Qexo's actual moat, and it's a schema problem, not an AI problem.
|
||||
2. **A qualification scorer** — 0–100 intent/urgency/service-fit score with visible evidence (the lead's own words).
|
||||
|
||||
Neither is hard. Both are a weekend build on the infra we already run.
|
||||
|
||||
---
|
||||
|
||||
## Build Path
|
||||
|
||||
### Phase 1 — The Lead Record (the real moat)
|
||||
|
||||
Postgres table (single source of truth):
|
||||
|
||||
```
|
||||
leads
|
||||
id, tenant_id, source (web/form/call/missed-call)
|
||||
contact_name, phone, email
|
||||
message (original inquiry, verbatim)
|
||||
consent_at (timestamp), consent_medium (form checkbox / verbal / none)
|
||||
qual_score (0-100), qual_evidence (json: flagged signals)
|
||||
service_fit, urgency, intent
|
||||
transcript (json, appended on call)
|
||||
appointment_id, appointment_status
|
||||
created_at, updated_at
|
||||
```
|
||||
|
||||
Every downstream step (scorer, outbound call, calendar write) reads and writes **this same record**. The lead never starts over. This is the "full-context handoff" Qexo sells, reduced to a schema.
|
||||
|
||||
### Phase 2 — Qualification Scorer
|
||||
|
||||
DeepSeek (via admin-ai) classifies the lead record into a 0–100 score with visible evidence:
|
||||
|
||||
- **Intent** — did they ask for a specific service, or just browse?
|
||||
- **Urgency** — "as soon as possible" / "this week" / "just looking"
|
||||
- **Service-fit** — does the inquiry match any offered service?
|
||||
- **Missing detail flags** — no address, no timeframe, no budget signal
|
||||
|
||||
Output: a score + the exact phrases that drove it. The evidence layer is what makes it defensible against "AI made that up" — the customer sees the lead's own words backing the score.
|
||||
|
||||
### Phase 3 — Outbound Call & Book
|
||||
|
||||
With consent on record, the voice agent calls out:
|
||||
1. Pulls the lead record (never re-asks what the form already captured)
|
||||
2. Confirms interest, fills the gaps the scorer flagged
|
||||
3. Books against live calendar
|
||||
4. Confirms address/access notes
|
||||
5. Appends transcript + appointment to the lead record
|
||||
|
||||
Reuse `shonuff-voice-caller` (ElevenLabs professional male voice) + Twilio outbound. The open-source Kokoro/faster-whisper stack from `voice-agent-deployment` is the $0-cost alternative for beta.
|
||||
|
||||
### Phase 4 — Unified Workspace ("Not another inbox")
|
||||
|
||||
Single view per lead: original inquiry + qualification score + transcript + appointment, human override everywhere. Not a CRM — a workspace where a lead either gets booked or gets a reason why not.
|
||||
|
||||
---
|
||||
|
||||
## Positioning vs. What We Already Have
|
||||
|
||||
| Product | Inbound | Outbound | Qualifies | Books | Key Differentiator |
|
||||
|---|---|---|---|---|---|
|
||||
| **VoIPSimplicity Concierge** (`ai-receptionist.md`) | ✅ answers calls | ❌ | ⚠️ basic | ✅ | Replaces IVR for existing VoIP customers |
|
||||
| **Missed-Call Recovery** (`missed-call-lead-recovery.md`) | ⚠️ missed calls | ❌ | ❌ | ❌ | SMS text-back within seconds |
|
||||
| **Sho'Nuff Front-Desk** (this) | ✅ | ✅ **calls out** | ✅ **scores** | ✅ | **Full-context handoff: web → score → outbound → calendar** |
|
||||
| **Qexo.ai** (competitor) | ✅ | ✅ | ✅ | ✅ | The benchmark we're matching |
|
||||
|
||||
The Front-Desk is the top of the funnel the other two feed into. Missed-call recovery captures the lead; the Front-Desk qualifies and books it. These are three products on one voice stack, not three competing ideas.
|
||||
|
||||
---
|
||||
|
||||
## Pricing (Premium, Value-Based — Never Undercut)
|
||||
|
||||
Modeled on Qexo's SMB wedge but priced like we own the infrastructure (we do):
|
||||
|
||||
| Tier | Price/mo | Included |
|
||||
|---|---|---|
|
||||
| **Solo** | $99 | 1 voice number, web intake + scorer, 50 outbound calls, calendar booking |
|
||||
| **Pro** | $299 | 3 numbers, 200 calls, multi-location, transcript archive, human-override console |
|
||||
| **Managed** | $599+ | White-label, agency resell, custom qualification rules, SLA |
|
||||
|
||||
Undercut Qexo on **unit economics**, not headline price. Our marginal cost is near-zero (self-hosted voice stack, admin-ai tokens at cost). Qexo pays per-call infrastructure margins we don't.
|
||||
|
||||
---
|
||||
|
||||
## Risks
|
||||
|
||||
1. **FTC / TCPA on outbound AI calls** — the single biggest exposure. Qexo's consent-first design is correct and mandatory. We mirror it: no outbound call without a recorded consent timestamp. `debt-recovery-compliance` skill already documents the TCPA/consent discipline; reuse it.
|
||||
2. **Voice quality at scale** — Kokoro is good but not ElevenLabs. Start managed-tier on ElevenLabs, offer Kokoro for beta cost control.
|
||||
3. **Calendar write integrity** — a wrong booking is a lost customer. The lead record must be the single writer; no side-channel calendar edits.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **First vertical** — solar/HVAC (Qexo's beachhead) vs. our existing warm markets (Debt Recovery Experts intake, VoIPSimplicity customers, Forefront Wireless)?
|
||||
2. **Calendar backend** — Rally, or a dedicated booking calendar per tenant?
|
||||
3. **Consent capture** — form checkbox (SMS/web) vs. recorded verbal consent (call). Both need a timestamped, auditable record.
|
||||
4. **Tenant model** — multi-tenant from day one (agencies reselling to clients), or single-tenant until 3 paying customers?
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Decide first vertical** (open question 1) — this shapes every downstream choice
|
||||
2. **Build the lead-record schema** (Phase 1) — the moat, and a pure schema task
|
||||
3. **Weekend spike**: qualification scorer on 10 sample leads, verify evidence layer
|
||||
4. **Wire outbound call** via existing `shonuff-voice-caller` + Twilio
|
||||
5. **Beta** with 1–2 friendly businesses before any pricing commitment
|
||||
|
||||
---
|
||||
|
||||
## Meta-Signal (worth remembering)
|
||||
|
||||
Three teardowns in a row — tranx.io, dozier.io, qexo.ai — are all **single-purpose vertical tools on generic AI plumbing**, each with the same shape: narrow SMB wedge, clean before/after loop, evidence/confidence layer. The pattern means the plumbing is commoditizing. The defensible layer is not the AI — it's the **data model and the compliance posture** (lead record + consent + calendar integrity). We already own the plumbing. The win is in the schema, the scorer evidence, and the consent design — not in out-building the AI.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VerdictTank Review - HotNow Savannah v2</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root { --bg: #0d0d0d; --card: #1a1a1a; --text: #e0e0e0; --muted: #888; --accent: #e74c3c; --green: #2ecc71; --amber: #f39c12; --border: #2a2a2a; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; line-height: 1.6; padding: 2rem; }
|
||||
.container { max-width: 900px; margin: 0 auto; }
|
||||
h1 { font-family: 'DM Serif Display', serif; font-size: 2.2rem; margin-bottom: 0.5rem; }
|
||||
h2 { font-family: 'DM Serif Display', serif; font-size: 1.4rem; margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border); }
|
||||
h3 { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.verdict-badge { display: inline-flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1.5rem; border-radius: 8px; font-size: 1.4rem; font-weight: 600; margin: 1rem 0 2rem; }
|
||||
.verdict-badge.conditional { background: rgba(243,156,18,0.15); border: 1px solid var(--amber); color: var(--amber); }
|
||||
.verdict-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--amber); }
|
||||
.card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||
th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid var(--border); }
|
||||
th { color: var(--muted); font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.tag { display: inline-block; padding: 0.15rem 0.6rem; border-radius: 4px; font-size: 0.8rem; font-weight: 600; }
|
||||
.tag-go { background: rgba(46,204,113,0.15); color: var(--green); }
|
||||
.tag-nogo { background: rgba(231,76,60,0.15); color: var(--accent); }
|
||||
.severity-high { color: var(--accent); font-weight: 600; }
|
||||
.severity-medium { color: var(--amber); }
|
||||
.dissent-box { background: rgba(231,76,60,0.08); border-left: 3px solid var(--accent); padding: 1rem 1.25rem; border-radius: 0 6px 6px 0; margin: 1rem 0; }
|
||||
.dissent-box strong { color: var(--accent); }
|
||||
ol { padding-left: 1.5rem; }
|
||||
li { margin-bottom: 0.6rem; }
|
||||
.back-link { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }
|
||||
.back-link a { color: var(--muted); text-decoration: none; }
|
||||
.back-link a:hover { color: var(--text); }
|
||||
.scorecard { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
|
||||
@media (max-width: 600px) { .scorecard { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>VerdictTank Review</h1>
|
||||
<p style="color:var(--muted)">HotNow Savannah v2 - August 11, 2026</p>
|
||||
|
||||
<div class="verdict-badge conditional">
|
||||
<span class="verdict-dot"></span>
|
||||
CONDITIONAL GO
|
||||
</div>
|
||||
<p>3-1 majority. Conditions must be met before capital deployment or stakeholder commitment.</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Panel Scorecard</h2>
|
||||
<table>
|
||||
<tr><th>Judge</th><th>Model</th><th>Verdict</th><th>Key Position</th></tr>
|
||||
<tr>
|
||||
<td>Sonnet 5</td><td>Claude</td><td><span class="tag tag-go">Conditional Go</span></td>
|
||||
<td>7 fixable conditions. Proposal is salvageable with honest re-audit of competitor table and code reuse estimate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Opus 4.8</td><td>Claude</td><td><span class="tag tag-go">Conditional Go</span></td>
|
||||
<td>Concurred. Added 3 demand-side omissions: cold-start, CAC realism, willingness-to-pay unvalidated.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gemini 2.5 Pro</td><td>Google</td><td><span class="tag tag-nogo">No-Go</span></td>
|
||||
<td>IQHub/TownIQ claim is a founder integrity issue, not a correctable condition. Financial model broken. Requires teardown and restart.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GPT-5</td><td>OpenAI</td><td><span class="tag tag-go">Conditional Go</span></td>
|
||||
<td>Sonnet directionally right but overconfident. Proposed 12 hard gates with reframed scope: 10-12 weeks, $25-35K.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="dissent-box">
|
||||
<strong>Dissent noted:</strong> Gemini 2.5 Pro issued a firm No-Go, arguing that the IQHub/TownIQ competitor misrepresentation constitutes a founder integrity failure that invalidates the proposal regardless of corrections to other numbers. The majority (3/4) disagreed, classifying it as a mischaracterization from sloppy research methodology rather than deliberate fabrication, and therefore correctable. This dissent is preserved in full.
|
||||
</div>
|
||||
|
||||
<h2>Unanimous Findings</h2>
|
||||
|
||||
<div class="card">
|
||||
<table>
|
||||
<tr><th>Severity</th><th>Finding</th><th>Remediation</th></tr>
|
||||
<tr>
|
||||
<td class="severity-high">High</td>
|
||||
<td><strong>IQHub/TownIQ competitor mischaracterized.</strong> TownIQ is civic/HOA software. IQHub is unrelated. No combined local discovery platform exists under that name.</td>
|
||||
<td>Re-derive the entire competitive landscape table from primary sources. Do not present to stakeholders until re-verified.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-high">High</td>
|
||||
<td><strong>"70% exists" claim is overstated.</strong> Super Search v2 is a web search aggregator, not an event discovery engine. Event connectors, social signal ingestion, and real-time ranking are all unbuilt.</td>
|
||||
<td>Independent code-level reuse audit. Real estimate: 25-30%. Rebuild timeline, budget, and capital ask from that number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-high">High</td>
|
||||
<td><strong>3-4 week MVP timeline is not credible.</strong> All 4 judges agreed it's too short. Consensus range: 6-12 weeks for a functional pilot.</td>
|
||||
<td>Adopt GPT-5's scope-freeze pilot plan: events-first, 5-6 sources, 10-12 weeks.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>Visitor numbers overstated.</strong> Proposal says 15M+; Visit Savannah's official 2024 figure is 12.9M.</td>
|
||||
<td>Use 12.9M. Publically verifiable numbers must be correct.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>TAM stacking is aggressive.</strong> $100B+ from overlapping, paywalled, and unverifiable reports. Classic double-counting.</td>
|
||||
<td>Rebuild TAM/SAM/SOM bottom-up from Savannah-specific numbers. Show the math.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>"Competitive vacuum" is misleading.</strong> Yelp, Google Maps, TikTok, Instagram, Facebook Groups, Eventbrite, Meetup, and hotel concierge channels all serve Savannah users. No city-specific app exists, but incumbents own default behavior.</td>
|
||||
<td>Reframe as incumbent displacement, not vacuum. State the specific behavioral wedge.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-high">High</td>
|
||||
<td><strong>Two-sided cold-start unaddressed.</strong> The model needs businesses to attract users, and users to justify $97/mo to businesses. Neither exists at launch. No sequencing plan.</td>
|
||||
<td>Supply a two-sided launch-sequencing plan. Which side gets subsidized first?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-high">High</td>
|
||||
<td><strong>GTM zero-CAC assumption is the real fragility.</strong> Reddit + SCAD ambassadors with zero paid acquisition for 6 months is a prayer, not a plan. The entire net-loss figure depends on this holding.</td>
|
||||
<td>Allocate $3-5K for GTM. Require signed MOUs with SCAD orgs, hotels, and anchor venues before launch.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>Consumer $4.99/mo WTP is unvalidated.</strong> Local discovery is a category users expect free. 200-500 paying subscribers with free substitutes available is optimistic.</td>
|
||||
<td>Run a 2-week landing page test. If paid conversion is below 2%, default to free consumer tier and monetize SMB-first.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>MCP server is not a moat.</strong> Easily replicated integration surface. Differentiation must come from proprietary data, curation, and partnerships.</td>
|
||||
<td>Reclassify as speculative optionality, not defensibility. Do not build until consumer traction exists.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="severity-medium">Medium</td>
|
||||
<td><strong>Capital ask is too low.</strong> $7.5-13.5K understates data ops, moderation, QA, GTM, and contingency even at corrected timeline.</td>
|
||||
<td>Rebudget at $25-35K with explicit runway and ops allocation.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Reframed Scope (GPT-5 Consensus Gate)</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Pilot Parameters</h3>
|
||||
<table>
|
||||
<tr><th>Dimension</th><th>Proposal v2</th><th>VerdictTank Consensus</th></tr>
|
||||
<tr><td>MVP timeline</td><td>3-4 weeks</td><td>10-12 weeks</td></tr>
|
||||
<tr><td>Capital required</td><td>$7.5-13.5K</td><td>$25-35K</td></tr>
|
||||
<tr><td>Scope</td><td>Full consumer + business platform</td><td>Events-first pilot: 5-6 sources, dedupe, venue pages, save/share</td></tr>
|
||||
<tr><td>GTM budget</td><td>$0 (organic only)</td><td>$3-5K (ambassadors, collabs, local ads)</td></tr>
|
||||
<tr><td>Y1 ARR target</td><td>$42K-$102K</td><td>Deferred: hit pilot gates first</td></tr>
|
||||
<tr><td>Consumer monetization</td><td>$4.99/mo from launch</td><td>Landing page test first; default to free if <2% conversion</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Launch Gates</h3>
|
||||
<ol>
|
||||
<li>1,000+ weekly active users</li>
|
||||
<li>30%+ week-4 retention</li>
|
||||
<li>10%+ venue click-through rate</li>
|
||||
<li>3%+ SMB lead conversion</li>
|
||||
<li>Under 10% content error rate</li>
|
||||
</ol>
|
||||
|
||||
<h3>GTM Prerequisites</h3>
|
||||
<ol>
|
||||
<li>Signed MOUs with 2-3 SCAD organizations</li>
|
||||
<li>Signed MOUs with 3+ hotels/concierges</li>
|
||||
<li>10-15 anchor venues committed to list or approve listings</li>
|
||||
<li>Pre-sell 15 SMBs on a 3-month $50-100/mo pilot</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2>Consensus Verdict</h2>
|
||||
<div class="card">
|
||||
<p><strong>Majority (3/4): CONDITIONAL GO</strong></p>
|
||||
<p>The core business concept has merit. Savannah's student population, tourism density, and competitive dynamics (no city-specific aggregator) create a real opportunity. The proposal's flaws are in execution details, not concept viability: competitor research needs primary sourcing, build estimates need code-level audit, and GTM needs signed partnerships before launch.</p>
|
||||
<p style="margin-top:1rem">With the reframed scope (10-12 weeks, $25-35K, events-first pilot, hard launch gates), the project is worth attempting. Without these conditions, it's a No-Go.</p>
|
||||
<p style="margin-top:1rem; color:var(--muted); font-size:0.9rem">Review conducted August 11, 2026. Pipeline: Research (Phase 1) → Conductor Review (Sonnet 5, Phase 2) → Validation (Opus 4.8) → Cross-Check (Qwen failed, replaced with GPT-5) → Cross-Check (Gemini 2.5 Pro). Cost: ~$0.55.</p>
|
||||
</div>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="./">Back to HotNow Savannah v2 Proposal</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
# Resend — Transactional Email Platform (Future Project)
|
||||
|
||||
**Status:** PLACEHOLDER — not_started
|
||||
**Prepared for:** Germaine Brown / IT Pro Partner
|
||||
**Date:** August 14, 2026
|
||||
**Trigger:** Projects that email END USERS (confirmations, receipts, notifications) need per-domain `From:` with SPF/DKIM. The shared relay (SiteGround / MXroute) can only send as its own hosted domains — it can't send "as" arbitrary client domains.
|
||||
|
||||
---
|
||||
|
||||
## Why this exists (and what it is NOT)
|
||||
|
||||
Two distinct email needs, don't conflate them:
|
||||
|
||||
| Need | Recipient | Correct pattern | Status |
|
||||
|---|---|---|---|
|
||||
| Contact-form notification | Site owner | One trusted sender + per-domain routing + Reply-To | ✅ MSP Form Handler already does this |
|
||||
| End-user email (confirm/receipt) | The submitter / customer | **Per-domain `From`** with SPF/DKIM | ❌ Needs Resend |
|
||||
|
||||
The MSP Form Handler (`forms.itpropartner.com`) notifies **site owners** only — `noreply@itpropartner.com`, per-domain recipient mapping in `config/domains.yaml`, Reply-To = submitter. That is the correct architecture and does not change.
|
||||
|
||||
But some projects email **end users** directly — WordPress contact-form auto-replies, appointment confirmations, receipts, welcome emails. Those should come **from the client's own domain** (`hello@client.com`) so they pass SPF/DKIM and avoid spam. The shared relay can't do that. **Resend can.**
|
||||
|
||||
## Which projects need it
|
||||
|
||||
- Current WordPress sites that send confirmation/auto-reply email to end users (enumerate at activation).
|
||||
- Future projects with receipts / confirmations / notifications.
|
||||
- MSP Form Handler — only if/when it gains submitter auto-acknowledgements.
|
||||
|
||||
## What Resend gives us
|
||||
|
||||
- **Per-domain verification:** add SPF + DKIM (optional DMARC) to each client domain, then send "as" that domain.
|
||||
- **One account, many domains**, domain-scoped API keys.
|
||||
- Dedicated IP reputation + bounce/complaint tracking — better deliverability than a shared-hosting relay.
|
||||
- **SMTP + REST API.** WordPress via WP Mail SMTP / FluentSMTP or a lightweight Resend plugin; custom apps via the REST API.
|
||||
|
||||
## Cost (verify current pricing at activation)
|
||||
|
||||
- Free: ~3,000 emails/mo, 100/day, single domain.
|
||||
- Pro: ~$20/mo — ~50k/mo, unlimited domains, dedicated IP add-on.
|
||||
- For multi-client domain needs, Pro (unlimited domains) is the likely tier.
|
||||
|
||||
## Architecture (planned)
|
||||
|
||||
- One Resend account under `g@germainebrown.com`.
|
||||
- Per client needing it: verify their domain (SPF + DKIM in their DNS — we control most via Cloudflare), create a domain-scoped API key.
|
||||
- WordPress: WP Mail SMTP pointed at Resend with the client's domain as sender.
|
||||
- Custom apps (FastAPI / Node): Resend REST API, per-domain sender.
|
||||
- API keys live in Vaultwarden — never in plaintext or committed config.
|
||||
|
||||
## Activation checklist
|
||||
|
||||
- [ ] Create Resend account.
|
||||
- [ ] Decide Free vs Pro (Pro for unlimited domains).
|
||||
- [ ] Verify first client domain (SPF + DKIM records).
|
||||
- [ ] Wire first project (WordPress plugin or API).
|
||||
- [ ] Set DMARC on sending domains (`p=none` → `quarantine` as volume grows).
|
||||
- [ ] Store API key in Vaultwarden.
|
||||
- [ ] Add to backup/DR inventory if it becomes critical path.
|
||||
|
||||
## Decision points (open)
|
||||
|
||||
- Resend vs AWS SES vs Postmark (Resend = default pick; simplest DX, cheap).
|
||||
- Single account with per-domain keys vs per-client accounts.
|
||||
- Sending subdomain convention: `mail.client.com` (recommended) vs apex `client.com` — subdomain keeps SPF/DKIM/DMARC clean and isolated.
|
||||
|
||||
## Related
|
||||
|
||||
- `smtp-relay-configuration` skill — netcup outbound only allows 2525; shared relay rejects non-hosted MAIL FROM.
|
||||
- MSP Form Handler lives at `/var/www/msp-forms` on app3 (FastAPI, `config/domains.yaml`, `config/settings.yaml`).
|
||||
@@ -0,0 +1,311 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SEO Tool + IntelSight Architecture</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
background: #020617;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
.container { max-width: 1100px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2rem; }
|
||||
.header-row { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem; }
|
||||
.pulse-dot { width: 12px; height: 12px; background: #22d3ee; border-radius: 50%; animation: pulse 2s infinite; }
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||
h1 { font-size: 1.4rem; font-weight: 700; }
|
||||
.subtitle { color: #94a3b8; font-size: 0.8rem; margin-left: 1.75rem; }
|
||||
.diagram-container {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid #1e293b;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
svg { width: 100%; min-width: 950px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 2rem; }
|
||||
.card { background: rgba(15, 23, 42, 0.5); border-radius: 0.75rem; border: 1px solid #1e293b; padding: 1.25rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
.card-dot { width: 8px; height: 8px; border-radius: 50%; }
|
||||
.card-dot.cyan { background: #22d3ee; }
|
||||
.card-dot.emerald { background: #34d399; }
|
||||
.card-dot.violet { background: #a78bfa; }
|
||||
.card-dot.amber { background: #fbbf24; }
|
||||
.card-dot.rose { background: #fb7185; }
|
||||
.card h3 { font-size: 0.8rem; font-weight: 600; }
|
||||
.card ul { list-style: none; color: #94a3b8; font-size: 0.72rem; }
|
||||
.card li { margin-bottom: 0.3rem; }
|
||||
.footer { text-align: center; margin-top: 1.5rem; color: #475569; font-size: 0.72rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-row">
|
||||
<div class="pulse-dot"></div>
|
||||
<h1>SEO Audit Tool + IntelSight — Shared Architecture</h1>
|
||||
</div>
|
||||
<p class="subtitle">Two products. One infrastructure. Zero product merge.</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1050 720">
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
|
||||
</marker>
|
||||
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#22d3ee" />
|
||||
</marker>
|
||||
<marker id="arrow-emerald" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#34d399" />
|
||||
</marker>
|
||||
<marker id="arrow-violet" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#a78bfa" />
|
||||
</marker>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
<!-- ============ USERS (left side) ============ -->
|
||||
<rect x="20" y="120" width="110" height="55" rx="6" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="75" y="142" fill="white" font-size="10" font-weight="600" text-anchor="middle">ITPP Hosting</text>
|
||||
<text x="75" y="158" fill="#94a3b8" font-size="8" text-anchor="middle">Clients</text>
|
||||
|
||||
<rect x="20" y="220" width="110" height="55" rx="6" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="75" y="242" fill="white" font-size="10" font-weight="600" text-anchor="middle">CI Buyers</text>
|
||||
<text x="75" y="258" fill="#94a3b8" font-size="8" text-anchor="middle">SMB/Mid-Market</text>
|
||||
|
||||
<!-- ============ PRODUCT LAYER ============ -->
|
||||
|
||||
<!-- SEO Audit Tool box -->
|
||||
<rect x="180" y="80" width="350" height="160" rx="8" fill="rgba(8,51,68,0.25)" stroke="#22d3ee" stroke-width="1.5"/>
|
||||
<text x="192" y="98" fill="#22d3ee" font-size="10" font-weight="600">SEO Audit Tool</text>
|
||||
<text x="192" y="112" fill="#64748b" font-size="8">Free / $49/mo Pro</text>
|
||||
|
||||
<!-- SEO features -->
|
||||
<rect x="195" y="125" width="155" height="48" rx="5" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1"/>
|
||||
<text x="205" y="142" fill="white" font-size="9" font-weight="600">Launch Audit</text>
|
||||
<text x="205" y="156" fill="#94a3b8" font-size="7">Auto-runs on deploy</text>
|
||||
<text x="205" y="167" fill="#94a3b8" font-size="7">50+ checks, fix-it snippets</text>
|
||||
|
||||
<rect x="360" y="125" width="155" height="48" rx="5" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1"/>
|
||||
<text x="370" y="142" fill="white" font-size="9" font-weight="600">Monthly Crawl</text>
|
||||
<text x="370" y="156" fill="#94a3b8" font-size="7">Trend tracking 0-100</text>
|
||||
<text x="370" y="167" fill="#94a3b8" font-size="7">Score history per site</text>
|
||||
|
||||
<rect x="195" y="180" width="155" height="48" rx="5" fill="rgba(34,211,238,0.12)" stroke="#22d3ee" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="205" y="197" fill="#22d3ee" font-size="8" font-weight="600">✦ AI Fix Prompts</text>
|
||||
<text x="205" y="211" fill="#94a3b8" font-size="7">Ready-made Claude/Cursor prompts</text>
|
||||
<text x="205" y="222" fill="#94a3b8" font-size="7">per finding — copy, paste, done</text>
|
||||
|
||||
<rect x="360" y="180" width="155" height="48" rx="5" fill="rgba(34,211,238,0.12)" stroke="#22d3ee" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="370" y="197" fill="#22d3ee" font-size="8" font-weight="600">✦ GEO Visibility</text>
|
||||
<text x="370" y="211" fill="#94a3b8" font-size="7">How visible in ChatGPT/</text>
|
||||
<text x="370" y="222" fill="#94a3b8" font-size="7">Gemini/Claude/Perplexity?</text>
|
||||
|
||||
<!-- IntelSight box -->
|
||||
<rect x="580" y="80" width="420" height="160" rx="8" fill="rgba(6,78,59,0.15)" stroke="#34d399" stroke-width="1.5"/>
|
||||
<text x="592" y="98" fill="#34d399" font-size="10" font-weight="600">IntelSight</text>
|
||||
<text x="592" y="112" fill="#64748b" font-size="8">$199 / $499 / $1,499/mo</text>
|
||||
|
||||
<rect x="595" y="125" width="190" height="48" rx="5" fill="rgba(6,78,59,0.4)" stroke="#34d399" stroke-width="1"/>
|
||||
<text x="605" y="142" fill="white" font-size="9" font-weight="600">Competitor Intelligence</text>
|
||||
<text x="605" y="156" fill="#94a3b8" font-size="7">Crunchbase + Hunter.io + OSINT</text>
|
||||
<text x="605" y="167" fill="#94a3b8" font-size="7">Funding, team, product alerts</text>
|
||||
|
||||
<rect x="795" y="125" width="190" height="48" rx="5" fill="rgba(6,78,59,0.4)" stroke="#34d399" stroke-width="1"/>
|
||||
<text x="805" y="142" fill="white" font-size="9" font-weight="600">Market Analysis</text>
|
||||
<text x="805" y="156" fill="#94a3b8" font-size="7">Multi-provider search</text>
|
||||
<text x="805" y="167" fill="#94a3b8" font-size="7">LLM synthesis & reports</text>
|
||||
|
||||
<rect x="595" y="180" width="190" height="48" rx="5" fill="rgba(52,211,153,0.12)" stroke="#34d399" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="605" y="197" fill="#34d399" font-size="8" font-weight="600">✦ Competitive SEO Scan</text>
|
||||
<text x="605" y="211" fill="#94a3b8" font-size="7">Top 5 competitors SEO health</text>
|
||||
<text x="605" y="222" fill="#94a3b8" font-size="7">side-by-side with yours</text>
|
||||
|
||||
<rect x="795" y="180" width="190" height="48" rx="5" fill="rgba(52,211,153,0.12)" stroke="#34d399" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="805" y="197" fill="#34d399" font-size="8" font-weight="600">✦ AI Visibility Matrix</text>
|
||||
<text x="805" y="211" fill="#94a3b8" font-size="7">Which competitors get cited</text>
|
||||
<text x="805" y="222" fill="#94a3b8" font-size="7">in ChatGPT/Gemini/Claude?</text>
|
||||
|
||||
<!-- Arrows: users → products -->
|
||||
<line x1="130" y1="147" x2="178" y2="147" stroke="#22d3ee" stroke-width="1.2" marker-end="url(#arrow-cyan)"/>
|
||||
<text x="154" y="141" fill="#64748b" font-size="7" text-anchor="middle">deploy</text>
|
||||
<line x1="130" y1="247" x2="578" y2="195" stroke="#34d399" stroke-width="1.2" marker-end="url(#arrow-emerald)"/>
|
||||
<text x="350" y="226" fill="#64748b" font-size="7">subscribe</text>
|
||||
|
||||
<!-- ============ DATA FLOW BOUNDARY ============ -->
|
||||
<rect x="160" y="280" width="860" height="70" rx="8" fill="transparent" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
|
||||
<text x="172" y="298" fill="#fbbf24" font-size="9" font-weight="600">Cross-Product Data Flow (aggregated, anonymous only)</text>
|
||||
|
||||
<text x="350" y="320" fill="#94a3b8" font-size="8">Client site crawl → Competitive SEO baseline</text>
|
||||
<text x="350" y="335" fill="#94a3b8" font-size="8">Client SEO scores → Industry benchmarks · IntelSight alerts → Audit comparisons</text>
|
||||
|
||||
<!-- Arrows in data flow boundary -->
|
||||
<line x1="355" y1="145" x2="355" y2="290" stroke="#22d3ee" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<line x1="690" y1="145" x2="690" y2="290" stroke="#34d399" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
|
||||
<!-- ============ SHARED INFRASTRUCTURE ============ -->
|
||||
<rect x="160" y="385" width="860" height="130" rx="8" fill="rgba(251,191,36,0.04)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="172" y="403" fill="#fbbf24" font-size="10" font-weight="600">Shared Infrastructure Layer</text>
|
||||
|
||||
<!-- Crawler Engine -->
|
||||
<rect x="180" y="418" width="185" height="55" rx="6" fill="rgba(120,53,15,0.2)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="272" y="440" fill="white" font-size="10" font-weight="600" text-anchor="middle">Crawler Engine</text>
|
||||
<text x="272" y="456" fill="#94a3b8" font-size="8" text-anchor="middle">Python/requests · HTTP fetch</text>
|
||||
<text x="272" y="468" fill="#94a3b8" font-size="7" text-anchor="middle">Config depth · Rate limit · Polite</text>
|
||||
|
||||
<!-- Audit Checkers -->
|
||||
<rect x="385" y="418" width="185" height="55" rx="6" fill="rgba(120,53,15,0.2)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="477" y="440" fill="white" font-size="10" font-weight="600" text-anchor="middle">Audit Checker Library</text>
|
||||
<text x="477" y="456" fill="#94a3b8" font-size="8" text-anchor="middle">50+ rules · pass/fail/warn</text>
|
||||
<text x="477" y="468" fill="#94a3b8" font-size="7" text-anchor="middle">Impact×Effort · Fix snippets</text>
|
||||
|
||||
<!-- Report Renderer -->
|
||||
<rect x="590" y="418" width="185" height="55" rx="6" fill="rgba(120,53,15,0.2)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="682" y="440" fill="white" font-size="10" font-weight="600" text-anchor="middle">Report Renderer</text>
|
||||
<text x="682" y="456" fill="#94a3b8" font-size="8" text-anchor="middle">PDF · HTML · Email</text>
|
||||
<text x="682" y="468" fill="#94a3b8" font-size="7" text-anchor="middle">Branded per product</text>
|
||||
|
||||
<!-- Super Search -->
|
||||
<rect x="795" y="418" width="210" height="55" rx="6" fill="rgba(120,53,15,0.2)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="900" y="440" fill="white" font-size="10" font-weight="600" text-anchor="middle">Super Search v2</text>
|
||||
<text x="900" y="456" fill="#94a3b8" font-size="8" text-anchor="middle">7 providers · Cache · Circuit break</text>
|
||||
<text x="900" y="468" fill="#94a3b8" font-size="7" text-anchor="middle">Port :8899 · Production-hardened</text>
|
||||
|
||||
<!-- API Gateway -->
|
||||
<rect x="350" y="488" width="480" height="20" rx="4" fill="rgba(6,78,59,0.3)" stroke="#34d399" stroke-width="1"/>
|
||||
<text x="590" y="502" fill="#34d399" font-size="8" font-weight="600" text-anchor="middle">API Gateway (FastAPI) — single entry point for both products</text>
|
||||
|
||||
<!-- Arrows: infra components → API Gateway -->
|
||||
<line x1="272" y1="473" x2="470" y2="486" stroke="#fbbf24" stroke-width="1"/>
|
||||
<line x1="477" y1="473" x2="530" y2="486" stroke="#fbbf24" stroke-width="1"/>
|
||||
<line x1="682" y1="473" x2="590" y2="486" stroke="#fbbf24" stroke-width="1"/>
|
||||
<line x1="900" y1="473" x2="710" y2="486" stroke="#fbbf24" stroke-width="1"/>
|
||||
|
||||
<!-- ============ DATABASES ============ -->
|
||||
<rect x="250" y="555" width="240" height="55" rx="6" fill="rgba(76,29,149,0.25)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<text x="370" y="577" fill="white" font-size="10" font-weight="600" text-anchor="middle">SEO Audit DB</text>
|
||||
<text x="370" y="593" fill="#94a3b8" font-size="8" text-anchor="middle">Site audits · Scores · Trends · Client sites</text>
|
||||
<text x="370" y="605" fill="#a78bfa" font-size="7" text-anchor="middle">ITPP hosting tenant scope</text>
|
||||
|
||||
<rect x="560" y="555" width="240" height="55" rx="6" fill="rgba(76,29,149,0.25)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<text x="680" y="577" fill="white" font-size="10" font-weight="600" text-anchor="middle">IntelSight DB</text>
|
||||
<text x="680" y="593" fill="#94a3b8" font-size="8" text-anchor="middle">Competitor profiles · CI data · Alerts</text>
|
||||
<text x="680" y="605" fill="#a78bfa" font-size="7" text-anchor="middle">Multi-tenant SaaS scope</text>
|
||||
|
||||
<!-- Arrows: API Gateway → Databases -->
|
||||
<line x1="410" y1="508" x2="370" y2="553" stroke="#a78bfa" stroke-width="1.2" marker-end="url(#arrow-violet)"/>
|
||||
<line x1="680" y1="508" x2="680" y2="553" stroke="#a78bfa" stroke-width="1.2" marker-end="url(#arrow-violet)"/>
|
||||
|
||||
<!-- ============ EXTERNAL APIs (right side) ============ -->
|
||||
<rect x="920" y="120" width="110" height="40" rx="5" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1"/>
|
||||
<text x="975" y="138" fill="white" font-size="9" font-weight="600" text-anchor="middle">Crunchbase</text>
|
||||
<text x="975" y="151" fill="#94a3b8" font-size="7" text-anchor="middle">API · $49/mo</text>
|
||||
|
||||
<rect x="920" y="175" width="110" height="40" rx="5" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1"/>
|
||||
<text x="975" y="193" fill="white" font-size="9" font-weight="600" text-anchor="middle">Hunter.io</text>
|
||||
<text x="975" y="206" fill="#94a3b8" font-size="7" text-anchor="middle">Email intel · $34/mo</text>
|
||||
|
||||
<rect x="920" y="230" width="110" height="40" rx="5" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1"/>
|
||||
<text x="975" y="248" fill="white" font-size="9" font-weight="600" text-anchor="middle">GSC / GA</text>
|
||||
<text x="975" y="261" fill="#94a3b8" font-size="7" text-anchor="middle">SEO data</text>
|
||||
|
||||
<rect x="920" y="285" width="110" height="40" rx="5" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1"/>
|
||||
<text x="975" y="303" fill="white" font-size="9" font-weight="600" text-anchor="middle">AI Engines</text>
|
||||
<text x="975" y="316" fill="#94a3b8" font-size="7" text-anchor="middle">ChatGPT · Gemini</text>
|
||||
|
||||
<!-- Arrows: external APIs → products -->
|
||||
<line x1="918" y1="140" x2="1002" y2="140" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<line x1="918" y1="195" x2="1002" y2="195" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<line x1="918" y1="250" x2="530" y2="175" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<line x1="918" y1="305" x2="900" y2="440" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
|
||||
<!-- ============ LEGEND ============ -->
|
||||
<text x="30" y="660" fill="white" font-size="9" font-weight="600">Legend</text>
|
||||
|
||||
<rect x="30" y="672" width="14" height="9" rx="2" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1"/>
|
||||
<text x="50" y="680" fill="#94a3b8" font-size="7">SEO Audit Tool</text>
|
||||
|
||||
<rect x="170" y="672" width="14" height="9" rx="2" fill="rgba(6,78,59,0.4)" stroke="#34d399" stroke-width="1"/>
|
||||
<text x="190" y="680" fill="#94a3b8" font-size="7">IntelSight</text>
|
||||
|
||||
<rect x="290" y="672" width="14" height="9" rx="2" fill="rgba(120,53,15,0.2)" stroke="#fbbf24" stroke-width="1"/>
|
||||
<text x="310" y="680" fill="#94a3b8" font-size="7">Shared Infra</text>
|
||||
|
||||
<rect x="410" y="672" width="14" height="9" rx="2" fill="rgba(76,29,149,0.3)" stroke="#a78bfa" stroke-width="1"/>
|
||||
<text x="430" y="680" fill="#94a3b8" font-size="7">Database</text>
|
||||
|
||||
<rect x="530" y="672" width="14" height="9" rx="2" fill="rgba(30,41,59,0.5)" stroke="#94a3b8" stroke-width="1"/>
|
||||
<text x="550" y="680" fill="#94a3b8" font-size="7">External API</text>
|
||||
|
||||
<line x1="640" y1="677" x2="658" y2="677" stroke="#22d3ee" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="664" y="680" fill="#94a3b8" font-size="7">New Feature (✦)</text>
|
||||
|
||||
<line x1="790" y1="677" x2="808" y2="677" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
|
||||
<text x="814" y="680" fill="#94a3b8" font-size="7">Data Boundary</text>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards -->
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot cyan"></div>
|
||||
<h3>SEO Tool — New Features</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• AI Fix Prompt Generation — Claude/Cursor ready-made prompts per finding</li>
|
||||
<li>• llms.txt Checking — detect missing or malformed AI crawler instructions</li>
|
||||
<li>• GEO/AEO Visibility Scan — how visible is your site in AI search engines?</li>
|
||||
<li>• Impact × Effort Matrix — ranked fixes, not just severity lists</li>
|
||||
<li>• <10s Scan Speed — dopamine hit = conversion</li>
|
||||
<li>• MCP Server (Pro tier) — AI agents can trigger scans directly</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot emerald"></div>
|
||||
<h3>IntelSight — New Features</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Competitive SEO Health Scan — top 5 competitors vs your site</li>
|
||||
<li>• Competitor AI Visibility Matrix — who gets cited in AI engines?</li>
|
||||
<li>• Combined CI + SEO Report — funding + team + SEO in one view</li>
|
||||
<li>• llms.txt Competitive Analysis — who's preparing for AI crawlers?</li>
|
||||
<li>• Cross-product benchmarks — industry SEO averages from ITPP data</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot amber"></div>
|
||||
<h3>Implementation Sequence</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Phase 1 (Wk 1-2): Crawler Engine + Checker Library + API Gateway</li>
|
||||
<li>• Phase 2 (Wk 3-4): SEO Tool launch — audit, snippets, AI prompts</li>
|
||||
<li>• Phase 3 (Wk 5-8): IntelSight SEO features — competitive scan, AI matrix</li>
|
||||
<li>• Phase 4 (Wk 9-12): Cross-product benchmarks, white-label, MCP server</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="footer">
|
||||
IT Pro Partner — Product Division · git.itpropartner.com/ippadmin/seo-tool · intelsight.io
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,250 @@
|
||||
# SEO Tool + IntelSight — Shared Architecture
|
||||
|
||||
**Status:** OPEN
|
||||
**Date:** 2026-08-10
|
||||
**Products:** SEO Audit Tool (`git.itpropartner.com/ippadmin/seo-tool`), IntelSight (`intelsight.io`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Principle
|
||||
|
||||
Two products. One infrastructure layer. Zero product merge.
|
||||
|
||||
| | SEO Audit Tool | IntelSight |
|
||||
|---|---|---|
|
||||
| **Buyer** | ITPP hosting client (small business owner) | Marketing VP, Founder, Strategy lead |
|
||||
| **Question** | "Why isn't my site getting traffic?" | "What are my competitors doing?" |
|
||||
| **Pricing** | Free / $49/mo Pro | $199–$1,499/mo |
|
||||
| **Current state** | Proposal (repo created Aug 10) | Proposal (July 25, 2026) |
|
||||
|
||||
They share the crawl engine, the audit checker library, the report renderer, and the Super Search backend. They sell to different people, at different price points, through different channels.
|
||||
|
||||
---
|
||||
|
||||
## 2. Shared Infrastructure Layer
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ SHARED INFRASTRUCTURE │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────┐ │
|
||||
│ │ Crawler │ │ Audit │ │ Report │ │ Super │ │
|
||||
│ │ Engine │ │ Checkers │ │ Renderer │ │ Search │ │
|
||||
│ │ (Python/ │ │ (50+ rules│ │ (PDF/HTML│ │ (7 │ │
|
||||
│ │ requests)│ │ library) │ │ /email) │ │providers│ │
|
||||
│ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └───┬────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌────┴──────────────┴─────────────┴────────────┴───┐ │
|
||||
│ │ API Gateway (FastAPI) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ IntelSight DB │ │ SEO Audit DB │ │
|
||||
│ │ (competitor │ │ (site audits, │ │
|
||||
│ │ profiles, │ │ scores, trends,│ │
|
||||
│ │ alerts, CI) │ │ client sites) │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Crawler Engine
|
||||
|
||||
- HTTP crawler that fetches and parses any URL
|
||||
- Extracts: HTML structure, meta tags, headings, links, images, structured data, performance metrics
|
||||
- Used by both products — SEO tool crawls the client's own site; IntelSight crawls competitor sites
|
||||
- Configurable depth, rate limiting, politeness
|
||||
|
||||
### Audit Checker Library
|
||||
|
||||
- 50+ reusable check functions: `check_meta_description()`, `check_og_tags()`, `check_canonical()`, `check_structured_data()`, `check_heading_hierarchy()`, `check_image_alts()`, `check_internal_links()`, `check_llms_txt()`
|
||||
- Each check returns: `{ status: pass|fail|warn, score: 0-100, fix: "<html snippet>", impact: high|medium|low, effort: low|medium|high }`
|
||||
- Used by both products — SEO tool runs all checks on the client's site; IntelSight runs summary checks on competitor sites
|
||||
|
||||
### Report Renderer
|
||||
|
||||
- Takes audit results and produces: HTML dashboard, PDF report, email summary
|
||||
- SEO tool renders: per-page issues, fix-it snippets, trend charts
|
||||
- IntelSight renders: competitive SEO comparison, AI visibility matrix, CI + SEO combined report
|
||||
|
||||
### Super Search Backend
|
||||
|
||||
- Already production-hardened: 7 providers, circuit breakers, caching, port 8899
|
||||
- Powers IntelSight's competitive intelligence queries
|
||||
- Powers SEO tool's AI engine visibility checks (GEO/AEO)
|
||||
|
||||
---
|
||||
|
||||
## 3. Product-Specific Architecture
|
||||
|
||||
### SEO Audit Tool
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ SEO AUDIT TOOL │
|
||||
│ │
|
||||
│ Launch Audit Monthly Crawl │
|
||||
│ (on deploy) (cron, 30 days) │
|
||||
│ │ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌────────┴────────┐ │
|
||||
│ │ Per-Site Audit │ │
|
||||
│ │ Dashboard │ │
|
||||
│ │ seo.itpropartner│ │
|
||||
│ │ .com/{site} │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┴───────────┐ │
|
||||
│ │ Fix-It Snippets │ │
|
||||
│ │ (copy-paste HTML │ │
|
||||
│ │ per issue, per page)│ │
|
||||
│ └───────────────────────┘ │
|
||||
│ │
|
||||
│ AI Fix Prompts AI Visibility │
|
||||
│ (Claude/Cursor/ (GEO/AEO check │
|
||||
│ Codex ready-made via Super Search)│
|
||||
│ fix instructions) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Feature Enhancements (from SeoLoupe analysis):**
|
||||
|
||||
| Enhancement | Effort | Value | Description |
|
||||
|---|---|---|---|
|
||||
| **AI Fix Prompt Generation** | Low | High | Each finding generates a ready-made prompt for Claude/Cursor/Codex — "Fix the meta description on /pricing. Current: '...' Suggested: '...'" |
|
||||
| **llms.txt Checking** | Low | Medium | Check if llms.txt exists and is well-formed. Flag missing or malformed. Zero-cost add to crawl phase |
|
||||
| **GEO/AEO Visibility Scan** | Medium | High | Query Super Search providers: "Is {domain} cited in AI search results?" Surface how the site appears in ChatGPT, Gemini, Claude, Perplexity |
|
||||
| **Impact x Effort Matrix** | Low | Medium | Formalize priority ranking as a 2x2 matrix (not just P0/P1/P2 list). Makes reports more actionable |
|
||||
| **<10s Scan Speed Target** | Medium | High | Automate end-to-end pipeline for instant feedback. Dopamine hit = conversion |
|
||||
| **MCP Server (Pro tier)** | Medium | High | Expose audit as MCP tools so external AI agents can trigger scans. First-mover in the agent ecosystem |
|
||||
|
||||
### IntelSight
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ INTELSIGHT │
|
||||
│ │
|
||||
│ Competitor Market Analysis │
|
||||
│ Profiles (funding, moves, │
|
||||
│ (Crunchbase, team changes, │
|
||||
│ Hunter.io, product launches) │
|
||||
│ OSINT) │
|
||||
│ │ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌────────┴────────┐ │
|
||||
│ │ CI Dashboard │ │
|
||||
│ │ intelsight.io │ │
|
||||
│ │ /dashboard │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┴───────────┐ │
|
||||
│ │ Competitive SEO Scan │ ← NEW │
|
||||
│ │ (competitor site │ │
|
||||
│ │ health vs yours) │ │
|
||||
│ └───────────────────────┘ │
|
||||
│ │
|
||||
│ Competitor AI Alert Engine │
|
||||
│ Visibility (funding, team, │
|
||||
│ (GEO matrix) product changes) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Feature Enhancements:**
|
||||
|
||||
| Enhancement | Effort | Value | Description |
|
||||
|---|---|---|---|
|
||||
| **Competitive SEO Health Scan** | Medium | High | Crawl top 5 competitors and surface their SEO health side-by-side with yours. "Their meta tags are complete. Yours are missing on 12 pages." Uses the shared audit checker library |
|
||||
| **Competitor AI Visibility Matrix** | Medium | High | How visible are your competitors in ChatGPT, Gemini, Claude, Perplexity, Grok? Which competitors get cited? What queries? The GEO/AEO scan but applied to competitors |
|
||||
| **Combined CI + SEO Report** | Medium | Very High | One report per competitor: funding moves, team changes, product launches, AND their SEO health. "They raised $5M, hired a CMO, and their SEO score dropped 15 points — they're distracted, your window is now" |
|
||||
| **llms.txt Competitive Analysis** | Low | Low | Flag competitors who have/don't have llms.txt. Trend indicator — who's thinking about AI crawlers? |
|
||||
|
||||
---
|
||||
|
||||
## 4. Cross-Product Data Flow
|
||||
|
||||
```
|
||||
SEO Audit Tool IntelSight
|
||||
────────────── ──────────
|
||||
Client site crawl ──────────────→ Competitive SEO baseline
|
||||
(anonymous, aggregated)
|
||||
|
||||
Shared Crawler
|
||||
←────────────── Competitor site crawl
|
||||
|
||||
Client SEO scores ──────────────→ Industry benchmarks
|
||||
(aggregated, anon) "Average SMB scores 62/100.
|
||||
Your clients average 78/100."
|
||||
|
||||
←─────────────── IntelSight alerts
|
||||
"Competitor X launched new site.
|
||||
Run audit comparison?"
|
||||
```
|
||||
|
||||
Key rule: **client data stays in the SEO tool. Competitor data stays in IntelSight.** The shared layer only moves aggregated, anonymous benchmarks between them. No client PII crosses the boundary.
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Sequence
|
||||
|
||||
### Phase 1: Foundation (Weeks 1-2)
|
||||
- Build the shared Crawler Engine (Python/requests, configurable depth, politeness)
|
||||
- Build the Audit Checker Library (50+ check functions)
|
||||
- Stand up shared API Gateway (FastAPI)
|
||||
- Separate databases per product
|
||||
|
||||
### Phase 2: SEO Tool Launch (Weeks 3-4)
|
||||
- Launch Audit: auto-runs on CloudPanel deploy webhook
|
||||
- Fix-It Snippets: HTML output per finding
|
||||
- Monthly crawl cron
|
||||
- AI Fix Prompt Generation (low effort, high impact)
|
||||
- llms.txt checking
|
||||
- Impact x Effort matrix in reports
|
||||
- Dashboard at `seo.itpropartner.com`
|
||||
|
||||
### Phase 3: IntelSight Enhancements (Weeks 5-8)
|
||||
- Competitive SEO Health Scan (uses shared audit checker)
|
||||
- Competitor AI Visibility Matrix (uses Super Search GEO queries)
|
||||
- Combined CI + SEO report template
|
||||
- MCP Server for external AI agent access
|
||||
|
||||
### Phase 4: Cross-Product (Weeks 9-12)
|
||||
- Anonymous industry benchmarks from SEO tool data
|
||||
- IntelSight alerts triggering SEO comparisons
|
||||
- White-label dashboard for agency clients
|
||||
- <10s scan speed optimization
|
||||
|
||||
---
|
||||
|
||||
## 6. What Stays Separate
|
||||
|
||||
| Layer | Separate? | Why |
|
||||
|---|---|---|
|
||||
| **Databases** | Separate | Client site data vs competitor intelligence — different access patterns, retention policies, privacy considerations |
|
||||
| **Auth / Tenants** | Separate | SEO tool: ITPP hosting auth (Stack Auth, existing). IntelSight: standalone SaaS auth with Stripe billing |
|
||||
| **Pricing / Billing** | Separate | SEO: bundled with hosting or $49/mo add-on. IntelSight: $199-1,499/mo standalone |
|
||||
| **Branding** | Separate | SEO: ITPP-branded, "your hosting includes this." IntelSight: standalone brand, intelsight.io |
|
||||
| **Go-to-Market** | Separate | SEO: hosting upsell, retention play. IntelSight: SaaS marketing, Product Hunt, direct sales |
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Decisions
|
||||
|
||||
| Decision | Status | Notes |
|
||||
|---|---|---|
|
||||
| Merge products into one platform? | **SETTLED: No** | Different buyers, different pricing, different GTM |
|
||||
| Share crawl engine? | **SETTLED: Yes** | Same underlying tech, no reason to build twice |
|
||||
| Share audit checker library? | **SETTLED: Yes** | Check functions are pure logic — zero product coupling |
|
||||
| Cross-product data sharing? | **OPEN** | Aggregated/anonymous only. Privacy boundary TBD |
|
||||
| SEO Tool MCP Server? | **OPEN** | Pro tier feature. Proximity to agent ecosystem is a differentiator |
|
||||
| IntelSight SEO report depth? | **OPEN** | Full audit (like SEO tool) or summary (top 20 checks)? Depends on crawl politeness and competitor detection risk |
|
||||
|
||||
---
|
||||
|
||||
## 8. Next Actions
|
||||
|
||||
- [ ] Build shared Crawler Engine (`seo-crawler` package)
|
||||
- [ ] Define Audit Checker Library spec (50+ checks, output format)
|
||||
- [ ] Add AI Fix Prompt Generation to seo-audit skill (immediate low-effort win)
|
||||
- [ ] Add llms.txt check to seo-audit skill crawl phase
|
||||
- [ ] Prototype GEO/AEO visibility query via Super Search
|
||||
@@ -0,0 +1,119 @@
|
||||
# SEO + AI Visibility Checker — Product Plan
|
||||
|
||||
**Status:** OPEN · Draft v1
|
||||
**Date:** 2026-08-23
|
||||
**Trigger:** Competitor review of tranx.io "Alice" (SEO + AI visibility check)
|
||||
|
||||
---
|
||||
|
||||
## 1. Why this exists
|
||||
|
||||
Two things changed that make a pure Google-only SEO scanner obsolete:
|
||||
|
||||
1. **AI crawlers are a second index.** ChatGPT, Perplexity, Claude, and Gemini answer questions by rendering and retrieving pages — and they each read `/llms.txt`, `robots.txt` (AI user-agents), and structured data differently from Googlebot. A site can rank #1 on Google and be invisible to every AI assistant.
|
||||
2. **AI-visibility is not measured by anyone cheap.** Screaming Frog / Ahrefs / Semrush measure Google. `llms.txt` and AI-crawler access are a blind spot. The only tools touching it (Alice, Profound, Peec) are either bare or lock it behind enterprise signup.
|
||||
|
||||
We already own the hard parts: Super Search (22 tools, web_extract, AI-answer probing) and a production-tested `seo-audit` skill with a browser_cdp methodology. This tool is productizing that skill + adding the AI-visibility layer nobody else has.
|
||||
|
||||
This follows the existing "obstacles as products" pattern: we needed to SEO-optimize our own 15 sites, so we build the checker first.
|
||||
|
||||
---
|
||||
|
||||
## 2. Product definition
|
||||
|
||||
**One-line:** A 30-second scan that answers "can Google *and* ChatGPT/Perplexity/Claude find, render, and cite this site?" — with a 0-100 score, severity-ranked issues, and copy-paste fixes.
|
||||
|
||||
**Core promise:** "Know how visible you are to search *and* AI — in 30 seconds, no signup."
|
||||
|
||||
### The five check families (parity with Alice, plus our edge)
|
||||
|
||||
| Family | Checks |
|
||||
|---|---|
|
||||
| **On-page SEO** | title, meta description, H1, canonical, Open Graph, structured data (JSON-LD), lang, viewport |
|
||||
| **Crawlability** | robots.txt (status + directive parse), sitemap.xml (status + URL count), HTTPS, redirects (www↔non-www, http→https) |
|
||||
| **Indexability** | brand SERP probe, indexed-page estimate (site: query), sitemap-vs-index gap |
|
||||
| **AI visibility** ← *our moat* | `/llms.txt` presence + parse, per-AI-crawler robots directives (GPTBot, ClaudeBot, PerplexityBot, Google-Extended), server-rendered vs JS-only content, schema markup richness, and a **live "how does ChatGPT/Perplexity see this brand" probe** via Super Search |
|
||||
| **Measurement** | GTM, GA4, Google Ads, Meta Pixel, Klaviyo detection + ID health |
|
||||
|
||||
### Output
|
||||
|
||||
- 0-100 score + one-line narrative
|
||||
- Issues ranked healthy / warning / error with per-issue fix snippets
|
||||
- Top-3 action list ("moves score most for least work")
|
||||
- Optional: connect Search Console / GA4 / Ads for query-level depth (Phase 2, paid)
|
||||
|
||||
---
|
||||
|
||||
## 3. Differentiation vs Alice (tranx.io)
|
||||
|
||||
| Axis | Alice | Ours |
|
||||
|---|---|---|
|
||||
| AI visibility | `/llms.txt` check only | `/llms.txt` + per-AI-crawler robots + **live AI-answer probe** (does ChatGPT actually cite this brand?) |
|
||||
| Depth | On-page + crawl + index + tags | Same + AI-answer retrieval testing + sitemap/index gap analysis |
|
||||
| Stack | Closed SaaS, credit-metered-ish upsell | Super Search backend, self-hosted, MCP-native |
|
||||
| Pricing | Freemium, enterprise upsell | Freemium scan + flat monthly (no per-lookup) |
|
||||
| Output | Score + issues + top-3 | Same + copy-paste fix snippets + export (JSON/PDF) |
|
||||
|
||||
**Moat:** the "does an AI actually cite you" probe. That's the question every founder will have in 2026 and nobody answers it in a free tool. It requires an LLM + search backend, which we already run.
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
```
|
||||
[Web] seo-check.<tld> (single-file SPA, static)
|
||||
│ POST /scan {url}
|
||||
▼
|
||||
[FastAPI backend] :8088 (deploy like other python-web-service-deployment)
|
||||
├─ fetch + parse target (requests + BeautifulSoup + lxml)
|
||||
├─ robots/sitemap/redirect checks (httpx)
|
||||
├─ structured-data validation (JSON-LD parse)
|
||||
├─ llms.txt fetch + parse
|
||||
├─ per-AI-crawler robots analysis
|
||||
├─ JS-render probe (optional headless via existing Crawl4AI / browserless)
|
||||
└─ AI-answer probe → Super Search MCP (web_search + web_extract, brand+category query)
|
||||
▼
|
||||
[Super Search MCP] (already live, 22 tools)
|
||||
└─ "what does {brand} do" → check if brand appears in top AI/search results
|
||||
```
|
||||
|
||||
**Reuse:** Super Search (search + extract), Crawl4AI :8910 (JS render when needed), existing `seo-audit` skill logic as the scoring spec. No new infra — this is a thin FastAPI service on Core behind Caddy.
|
||||
|
||||
---
|
||||
|
||||
## 5. Build path (phased)
|
||||
|
||||
| Phase | Scope | Exit criterion |
|
||||
|---|---|---|
|
||||
| **P1 — MVP (free scan)** | On-page + crawl + index + AI-visibility (llms.txt + crawler robots + AI-answer probe) + measurement. Score + top-3 + snippets. | Live scan of our own 15 sites, all green |
|
||||
| **P2 — Auth + depth** | Central auth login, saved scans, GSC/GA4/Ads connect, query-level data | Paying first user |
|
||||
| **P3 — API + MCP** | REST API + MCP server (`seo-check` tool) so Hermes and other agents can run scans programmatically | MCP server live, documented |
|
||||
|
||||
**P1 is the same work as "SEO-optimize our 15 sites"** — build the checker, run it on ourselves, fix what it finds. The tool and the site-fix are one effort.
|
||||
|
||||
---
|
||||
|
||||
## 6. Pricing (value-based, premium — never undercut)
|
||||
|
||||
| Tier | Price | Includes |
|
||||
|---|---|---|
|
||||
| Free | $0 | 1-off scans, rate-limited per IP, no signup |
|
||||
| Pro | $29/mo | Unlimited scans, saved reports, scheduled re-scans, AI-answer monitoring alerts |
|
||||
| Agency | $99/mo | 50 domains, white-label PDF reports, API access |
|
||||
| API | usage | Per-scan API + MCP server for agent platforms |
|
||||
|
||||
Anchored against Alice (free) and Semrush/Ahrefs ($129+/mo). We undercut enterprise but never race to the bottom on the free tier — the free tier is a lead-gen funnel, not the product.
|
||||
|
||||
---
|
||||
|
||||
## 7. Open decisions (need Germaine)
|
||||
|
||||
- [ ] **Brand/domain:** own product name + domain, or a subdomain under an existing property? (Suggests: standalone `.io`/`.com` like the other micro-SaaS products — IntelSight, VerdictTank.)
|
||||
- [ ] **Scope of "AI-answer probe":** how deep — full retrieval comparison or a yes/no "brand cited" flag for v1?
|
||||
- [ ] **Ship target:** P1 as a free public tool first (lead-gen), or internal-only until our 15 sites are clean?
|
||||
|
||||
---
|
||||
|
||||
## 8. Immediate next step
|
||||
|
||||
The P1 build and the "optimize our 15 sites" ask are the same workstream. Sweep results are in `sites-seo-sweep-2026-08-23.md`. Fix the 9 failing sites first (proves the scoring model), then ship the checker as a product.
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify no double hyphens outside style tags."""
|
||||
import re
|
||||
|
||||
with open('/root/projects/itpp-infrastructure/projects/hotnow-savannah-v2.html') as f:
|
||||
html = f.read()
|
||||
|
||||
# Split by style tags
|
||||
parts = re.split(r'(<style>.*?</style>)', html, flags=re.DOTALL)
|
||||
count = 0
|
||||
for part in parts:
|
||||
if not part.startswith('<style>'):
|
||||
found = list(re.finditer(r'--', part))
|
||||
for m in found:
|
||||
ctx = part[max(0,m.start()-20):m.end()+20]
|
||||
count += 1
|
||||
print(f' offset {m.start()}: ...{ctx!r}...')
|
||||
|
||||
if count == 0:
|
||||
print('ZERO double hyphens outside <style> tags. Clean!')
|
||||
else:
|
||||
print(f'Found {count} double hyphens outside style tags')
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify no double hyphens in visible text content."""
|
||||
import re
|
||||
|
||||
with open('/root/projects/itpp-infrastructure/projects/hotnow-savannah-v2.html') as f:
|
||||
html = f.read()
|
||||
|
||||
# Remove all HTML tags, comments, and style blocks
|
||||
clean = html
|
||||
# Remove style blocks
|
||||
clean = re.sub(r'<style>.*?</style>', '', clean, flags=re.DOTALL)
|
||||
# Remove HTML comments
|
||||
clean = re.sub(r'<!--.*?-->', '', clean, flags=re.DOTALL)
|
||||
# Remove HTML tags
|
||||
clean = re.sub(r'<[^>]+>', '', clean)
|
||||
# Decode HTML entities that contain --
|
||||
clean = clean.replace('—', '')
|
||||
|
||||
# Find --
|
||||
matches = list(re.finditer(r'--', clean))
|
||||
if matches:
|
||||
print(f'Found {len(matches)} double hyphens in visible text:')
|
||||
for m in matches[:20]:
|
||||
ctx = clean[max(0,m.start()-30):m.end()+30]
|
||||
print(f' offset {m.start()}: "...{ctx.strip()}..."')
|
||||
else:
|
||||
print('ZERO double hyphens in visible text content. Clean!')
|
||||
Reference in New Issue
Block a user