cashclaw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/bin/cashclaw.js +2 -0
- package/missions/blog-post-1500.json +21 -0
- package/missions/blog-post-500.json +19 -0
- package/missions/lead-list-50.json +20 -0
- package/missions/seo-audit-basic.json +19 -0
- package/missions/seo-audit-pro.json +23 -0
- package/missions/social-media-weekly.json +19 -0
- package/missions/whatsapp-setup.json +22 -0
- package/package.json +45 -0
- package/skills/cashclaw-content-writer/SKILL.md +245 -0
- package/skills/cashclaw-core/SKILL.md +251 -0
- package/skills/cashclaw-invoicer/SKILL.md +395 -0
- package/skills/cashclaw-invoicer/scripts/stripe-ops.js +441 -0
- package/skills/cashclaw-lead-generator/SKILL.md +246 -0
- package/skills/cashclaw-lead-generator/scripts/scraper.js +356 -0
- package/skills/cashclaw-seo-auditor/SKILL.md +240 -0
- package/skills/cashclaw-seo-auditor/scripts/audit.js +401 -0
- package/skills/cashclaw-social-media/SKILL.md +374 -0
- package/skills/cashclaw-whatsapp-manager/SKILL.md +357 -0
- package/src/cli/commands/dashboard.js +72 -0
- package/src/cli/commands/init.js +290 -0
- package/src/cli/commands/status.js +174 -0
- package/src/cli/index.js +496 -0
- package/src/cli/utils/banner.js +44 -0
- package/src/cli/utils/config.js +170 -0
- package/src/dashboard/public/app.js +329 -0
- package/src/dashboard/public/index.html +139 -0
- package/src/dashboard/public/style.css +464 -0
- package/src/dashboard/server.js +224 -0
- package/src/engine/earnings-tracker.js +184 -0
- package/src/engine/mission-runner.js +224 -0
- package/src/engine/scheduler.js +139 -0
- package/src/integrations/hyrve-bridge.js +213 -0
- package/src/integrations/openclaw-bridge.js +207 -0
- package/src/integrations/stripe-connect.js +204 -0
- package/templates/config.default.json +83 -0
- package/templates/invoice.html +260 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CashClaw Lead Generator - Lead Scraper Script
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scraper.js --query "SaaS companies Austin" --count 25 [--output leads.json]
|
|
8
|
+
*
|
|
9
|
+
* Takes a search query and desired lead count. Searches publicly available
|
|
10
|
+
* business directories and company websites to build a qualified lead list.
|
|
11
|
+
* Outputs a JSON array of leads with company, contact, and scoring data.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { argv, exit } = require("process");
|
|
15
|
+
const { writeFileSync } = require("fs");
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Argument parsing
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function parseArgs() {
|
|
22
|
+
const args = { count: 25 };
|
|
23
|
+
for (let i = 2; i < argv.length; i++) {
|
|
24
|
+
if (argv[i] === "--query" && argv[i + 1]) args.query = argv[++i];
|
|
25
|
+
else if (argv[i] === "--count" && argv[i + 1]) args.count = parseInt(argv[++i], 10);
|
|
26
|
+
else if (argv[i] === "--output" && argv[i + 1]) args.output = argv[++i];
|
|
27
|
+
else if (argv[i] === "--industry" && argv[i + 1]) args.industry = argv[++i];
|
|
28
|
+
else if (argv[i] === "--size" && argv[i + 1]) args.size = argv[++i];
|
|
29
|
+
}
|
|
30
|
+
if (!args.query) {
|
|
31
|
+
console.error('Usage: node scraper.js --query "search terms" --count 25 [--output leads.json]');
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
return args;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Email pattern generator
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function generateEmailGuesses(firstName, lastName, domain) {
|
|
42
|
+
const f = firstName.toLowerCase().replace(/[^a-z]/g, "");
|
|
43
|
+
const l = lastName.toLowerCase().replace(/[^a-z]/g, "");
|
|
44
|
+
if (!f || !l || !domain) return [];
|
|
45
|
+
return [
|
|
46
|
+
`${f}@${domain}`,
|
|
47
|
+
`${f}.${l}@${domain}`,
|
|
48
|
+
`${f}${l}@${domain}`,
|
|
49
|
+
`${f[0]}${l}@${domain}`,
|
|
50
|
+
`${f}.${l[0]}@${domain}`,
|
|
51
|
+
`${l}@${domain}`,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateEmailFormat(email) {
|
|
56
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Domain extraction
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
function extractDomain(url) {
|
|
64
|
+
try {
|
|
65
|
+
const u = new URL(url.startsWith("http") ? url : `https://${url}`);
|
|
66
|
+
return u.hostname.replace(/^www\./, "");
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// MX record check via DNS over HTTPS
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
async function checkMXRecord(domain) {
|
|
77
|
+
try {
|
|
78
|
+
const resp = await fetch(
|
|
79
|
+
`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=MX`,
|
|
80
|
+
{ signal: AbortSignal.timeout(5000) }
|
|
81
|
+
);
|
|
82
|
+
const data = await resp.json();
|
|
83
|
+
return !!(data.Answer && data.Answer.length > 0);
|
|
84
|
+
} catch {
|
|
85
|
+
return null; // could not verify
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Page fetcher (simple HTML scraper)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
async function fetchPage(url) {
|
|
94
|
+
try {
|
|
95
|
+
const resp = await fetch(url, {
|
|
96
|
+
headers: {
|
|
97
|
+
"User-Agent":
|
|
98
|
+
"CashClawBot/1.0 (+https://cashclaw.ai) Mozilla/5.0 compatible",
|
|
99
|
+
Accept: "text/html,application/xhtml+xml",
|
|
100
|
+
},
|
|
101
|
+
redirect: "follow",
|
|
102
|
+
signal: AbortSignal.timeout(10000),
|
|
103
|
+
});
|
|
104
|
+
if (!resp.ok) return null;
|
|
105
|
+
return await resp.text();
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Extract contact info from HTML
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
function extractEmails(html) {
|
|
116
|
+
const re = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
117
|
+
const found = html.match(re) || [];
|
|
118
|
+
// Filter out common false positives
|
|
119
|
+
return [...new Set(found)].filter(
|
|
120
|
+
(e) =>
|
|
121
|
+
!e.endsWith(".png") &&
|
|
122
|
+
!e.endsWith(".jpg") &&
|
|
123
|
+
!e.endsWith(".svg") &&
|
|
124
|
+
!e.includes("example.com") &&
|
|
125
|
+
!e.includes("sentry") &&
|
|
126
|
+
!e.includes("webpack")
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractPhones(html) {
|
|
131
|
+
const re = /(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}/g;
|
|
132
|
+
const found = html.match(re) || [];
|
|
133
|
+
return [...new Set(found)].slice(0, 3);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function extractLinkedIn(html) {
|
|
137
|
+
const re = /https?:\/\/(?:www\.)?linkedin\.com\/(?:in|company)\/[a-zA-Z0-9_-]+/g;
|
|
138
|
+
const found = html.match(re) || [];
|
|
139
|
+
return [...new Set(found)];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Lead scoring
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
function scoreLead(lead, filters) {
|
|
147
|
+
let score = 0;
|
|
148
|
+
|
|
149
|
+
// Has verified email
|
|
150
|
+
if (lead.contact.email && validateEmailFormat(lead.contact.email)) score += 1;
|
|
151
|
+
|
|
152
|
+
// Has phone
|
|
153
|
+
if (lead.contact.phone) score += 1;
|
|
154
|
+
|
|
155
|
+
// Has LinkedIn
|
|
156
|
+
if (lead.contact.linkedin) score += 1;
|
|
157
|
+
|
|
158
|
+
// Has decision maker title
|
|
159
|
+
const dmTitles = ["ceo", "cto", "cfo", "cmo", "founder", "owner", "director", "head", "vp", "president"];
|
|
160
|
+
if (
|
|
161
|
+
lead.contact.title &&
|
|
162
|
+
dmTitles.some((t) => lead.contact.title.toLowerCase().includes(t))
|
|
163
|
+
) {
|
|
164
|
+
score += 2;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Has website
|
|
168
|
+
if (lead.website) score += 1;
|
|
169
|
+
|
|
170
|
+
// Industry match
|
|
171
|
+
if (filters.industry && lead.industry && lead.industry.toLowerCase().includes(filters.industry.toLowerCase())) {
|
|
172
|
+
score += 2;
|
|
173
|
+
} else {
|
|
174
|
+
score += 1; // partial credit if we cannot verify
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Has company name (basic validation)
|
|
178
|
+
if (lead.company && lead.company.length > 1) score += 1;
|
|
179
|
+
|
|
180
|
+
// Active signals
|
|
181
|
+
if (lead.signals && Object.values(lead.signals).some(Boolean)) score += 1;
|
|
182
|
+
|
|
183
|
+
return Math.min(score, 10);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Search and scrape pipeline
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
async function searchAndScrape(query, count, filters) {
|
|
191
|
+
const leads = [];
|
|
192
|
+
|
|
193
|
+
console.log(` Searching for: "${query}"`);
|
|
194
|
+
console.log(` Target count: ${count}`);
|
|
195
|
+
console.log(` Filters: ${JSON.stringify(filters)}\n`);
|
|
196
|
+
|
|
197
|
+
// Strategy: search for company websites via a directory or search results page
|
|
198
|
+
// In production this would use APIs (Clearbit, Hunter, Apollo). For the demo
|
|
199
|
+
// script, we scrape a few known directory patterns.
|
|
200
|
+
|
|
201
|
+
const searchUrls = [
|
|
202
|
+
`https://www.google.com/search?q=${encodeURIComponent(query + " company contact")}`,
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
console.log(" Phase 1: Fetching search results...");
|
|
206
|
+
|
|
207
|
+
for (const searchUrl of searchUrls) {
|
|
208
|
+
const html = await fetchPage(searchUrl);
|
|
209
|
+
if (!html) {
|
|
210
|
+
console.log(` Warning: Could not fetch ${searchUrl}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Extract URLs from search results
|
|
215
|
+
const urlRe = /https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\/[^\s"'<>)]*)?/g;
|
|
216
|
+
const urls = [...new Set((html.match(urlRe) || []))].filter(
|
|
217
|
+
(u) =>
|
|
218
|
+
!u.includes("google.com") &&
|
|
219
|
+
!u.includes("googleapis.com") &&
|
|
220
|
+
!u.includes("gstatic.com") &&
|
|
221
|
+
!u.includes("youtube.com") &&
|
|
222
|
+
!u.includes("wikipedia.org") &&
|
|
223
|
+
!u.includes("w3.org")
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
console.log(` Found ${urls.length} candidate URLs`);
|
|
227
|
+
|
|
228
|
+
// Phase 2: Visit each URL and extract contact info
|
|
229
|
+
console.log(" Phase 2: Extracting contact data...\n");
|
|
230
|
+
|
|
231
|
+
const visited = new Set();
|
|
232
|
+
for (const url of urls.slice(0, count * 2)) {
|
|
233
|
+
const domain = extractDomain(url);
|
|
234
|
+
if (!domain || visited.has(domain)) continue;
|
|
235
|
+
visited.add(domain);
|
|
236
|
+
|
|
237
|
+
if (leads.length >= count) break;
|
|
238
|
+
|
|
239
|
+
console.log(` Scanning: ${domain}`);
|
|
240
|
+
const pageHtml = await fetchPage(`https://${domain}`);
|
|
241
|
+
if (!pageHtml) continue;
|
|
242
|
+
|
|
243
|
+
const emails = extractEmails(pageHtml);
|
|
244
|
+
const phones = extractPhones(pageHtml);
|
|
245
|
+
const linkedins = extractLinkedIn(pageHtml);
|
|
246
|
+
|
|
247
|
+
// Extract company name from title
|
|
248
|
+
const titleMatch = pageHtml.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
249
|
+
const companyName = titleMatch
|
|
250
|
+
? titleMatch[1]
|
|
251
|
+
.split(/[|\-\u2013\u2014]/)[0]
|
|
252
|
+
.trim()
|
|
253
|
+
.substring(0, 80)
|
|
254
|
+
: domain;
|
|
255
|
+
|
|
256
|
+
if (emails.length === 0 && phones.length === 0) continue;
|
|
257
|
+
|
|
258
|
+
const lead = {
|
|
259
|
+
company: companyName,
|
|
260
|
+
website: `https://${domain}`,
|
|
261
|
+
industry: filters.industry || "Unknown",
|
|
262
|
+
size: filters.size || "Unknown",
|
|
263
|
+
location: "Unknown",
|
|
264
|
+
contact: {
|
|
265
|
+
name: "Contact",
|
|
266
|
+
title: "Decision Maker",
|
|
267
|
+
email: emails[0] || null,
|
|
268
|
+
phone: phones[0] || null,
|
|
269
|
+
linkedin: linkedins[0] || null,
|
|
270
|
+
},
|
|
271
|
+
signals: {
|
|
272
|
+
recently_funded: false,
|
|
273
|
+
hiring: /career|jobs|hiring|join our team/i.test(pageHtml),
|
|
274
|
+
tech_stack_match: false,
|
|
275
|
+
content_engagement: /<article|blog/i.test(pageHtml),
|
|
276
|
+
},
|
|
277
|
+
score: 0,
|
|
278
|
+
notes: "",
|
|
279
|
+
email_confidence: emails.length > 0 ? "found_on_site" : "unverified",
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
lead.score = scoreLead(lead, filters);
|
|
283
|
+
|
|
284
|
+
if (lead.signals.hiring) lead.notes += "Company is actively hiring. ";
|
|
285
|
+
if (lead.signals.content_engagement) lead.notes += "Active blog/content. ";
|
|
286
|
+
|
|
287
|
+
leads.push(lead);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Sort by score descending
|
|
292
|
+
leads.sort((a, b) => b.score - a.score);
|
|
293
|
+
|
|
294
|
+
return leads.slice(0, count);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Main
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
async function main() {
|
|
302
|
+
const args = parseArgs();
|
|
303
|
+
|
|
304
|
+
console.log(`\n CashClaw Lead Generator`);
|
|
305
|
+
console.log(` =======================\n`);
|
|
306
|
+
|
|
307
|
+
const filters = {
|
|
308
|
+
industry: args.industry || null,
|
|
309
|
+
size: args.size || null,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const leads = await searchAndScrape(args.query, args.count, filters);
|
|
313
|
+
|
|
314
|
+
// Calculate stats
|
|
315
|
+
const avgScore = leads.length > 0
|
|
316
|
+
? (leads.reduce((s, l) => s + l.score, 0) / leads.length).toFixed(1)
|
|
317
|
+
: 0;
|
|
318
|
+
const hot = leads.filter((l) => l.score >= 8).length;
|
|
319
|
+
const warm = leads.filter((l) => l.score >= 6 && l.score < 8).length;
|
|
320
|
+
const cool = leads.filter((l) => l.score >= 4 && l.score < 6).length;
|
|
321
|
+
|
|
322
|
+
const output = {
|
|
323
|
+
metadata: {
|
|
324
|
+
generated_at: new Date().toISOString(),
|
|
325
|
+
query: args.query,
|
|
326
|
+
filters,
|
|
327
|
+
total_leads: leads.length,
|
|
328
|
+
avg_score: parseFloat(avgScore),
|
|
329
|
+
score_distribution: { hot, warm, cool },
|
|
330
|
+
},
|
|
331
|
+
leads,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const json = JSON.stringify(output, null, 2);
|
|
335
|
+
|
|
336
|
+
if (args.output) {
|
|
337
|
+
writeFileSync(args.output, json, "utf-8");
|
|
338
|
+
console.log(`\n Leads saved to: ${args.output}`);
|
|
339
|
+
} else {
|
|
340
|
+
console.log("\n" + json);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`\n ---- Summary ----`);
|
|
344
|
+
console.log(` Total leads: ${leads.length}`);
|
|
345
|
+
console.log(` Avg score: ${avgScore}`);
|
|
346
|
+
console.log(` Hot (8-10): ${hot}`);
|
|
347
|
+
console.log(` Warm (6-7): ${warm}`);
|
|
348
|
+
console.log(` Cool (4-5): ${cool}`);
|
|
349
|
+
console.log(` With email: ${leads.filter((l) => l.contact.email).length}`);
|
|
350
|
+
console.log(` With phone: ${leads.filter((l) => l.contact.phone).length}\n`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
main().catch((err) => {
|
|
354
|
+
console.error(`Fatal error: ${err.message}`);
|
|
355
|
+
exit(1);
|
|
356
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cashclaw-seo-auditor
|
|
3
|
+
description: Performs comprehensive SEO audits on websites covering technical SEO, on-page optimization, off-page signals, and performance metrics. Generates actionable reports with prioritized recommendations.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"openclaw":
|
|
7
|
+
{
|
|
8
|
+
"emoji": "\U0001F50D",
|
|
9
|
+
"requires": { "bins": ["node", "curl"] },
|
|
10
|
+
"install":
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
"id": "npm",
|
|
14
|
+
"kind": "node",
|
|
15
|
+
"package": "cashclaw",
|
|
16
|
+
"bins": ["cashclaw"],
|
|
17
|
+
"label": "Install CashClaw via npm"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# CashClaw SEO Auditor
|
|
25
|
+
|
|
26
|
+
You perform professional SEO audits that clients pay for. Your output must be
|
|
27
|
+
thorough, actionable, and formatted as a deliverable report. Never produce
|
|
28
|
+
generic advice -- every recommendation must reference specific findings from
|
|
29
|
+
the target URL.
|
|
30
|
+
|
|
31
|
+
## Pricing Tiers
|
|
32
|
+
|
|
33
|
+
| Tier | Scope | Price | Delivery |
|
|
34
|
+
|------|-------|-------|----------|
|
|
35
|
+
| Basic | Single page, technical checks only | $9 | 1 hour |
|
|
36
|
+
| Standard | Up to 5 pages, full audit | $29 | 24 hours |
|
|
37
|
+
| Pro | Full site (up to 50 pages), competitive analysis | $59 | 48 hours |
|
|
38
|
+
|
|
39
|
+
## Audit Process
|
|
40
|
+
|
|
41
|
+
### Step 1: Fetch and Parse
|
|
42
|
+
|
|
43
|
+
Run the audit script to collect raw data:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node scripts/audit.js --url "https://example.com" --output audit-data.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the script is unavailable, perform manual checks using curl:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Fetch the page
|
|
53
|
+
curl -sL -o page.html -w "%{http_code} %{time_total}s %{size_download}bytes" "https://example.com"
|
|
54
|
+
|
|
55
|
+
# Check robots.txt
|
|
56
|
+
curl -sL "https://example.com/robots.txt"
|
|
57
|
+
|
|
58
|
+
# Check sitemap
|
|
59
|
+
curl -sL "https://example.com/sitemap.xml"
|
|
60
|
+
|
|
61
|
+
# Check SSL
|
|
62
|
+
curl -sI "https://example.com" | head -20
|
|
63
|
+
|
|
64
|
+
# Check redirect chain
|
|
65
|
+
curl -sIL "https://example.com" 2>&1 | grep -i "location\|HTTP/"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Step 2: Technical SEO Checklist
|
|
69
|
+
|
|
70
|
+
Evaluate every item. Score each as PASS, WARN, or FAIL.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
[ ] HTTPS enabled and forced (HTTP redirects to HTTPS)
|
|
74
|
+
[ ] SSL certificate valid and not expiring within 30 days
|
|
75
|
+
[ ] robots.txt exists and is properly configured
|
|
76
|
+
[ ] XML sitemap exists and is valid
|
|
77
|
+
[ ] No broken internal links (4xx status codes)
|
|
78
|
+
[ ] No redirect chains longer than 2 hops
|
|
79
|
+
[ ] Clean URL structure (no query params for content pages)
|
|
80
|
+
[ ] Canonical tags present and correct
|
|
81
|
+
[ ] Hreflang tags (if multi-language)
|
|
82
|
+
[ ] Structured data / JSON-LD present and valid
|
|
83
|
+
[ ] Mobile-friendly viewport meta tag
|
|
84
|
+
[ ] No mixed content (HTTP resources on HTTPS page)
|
|
85
|
+
[ ] Server response time under 500ms
|
|
86
|
+
[ ] Gzip or Brotli compression enabled
|
|
87
|
+
[ ] HTTP/2 or HTTP/3 support
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Step 3: On-Page SEO Checklist
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
[ ] Title tag exists, unique, 50-60 characters
|
|
94
|
+
[ ] Meta description exists, unique, 150-160 characters
|
|
95
|
+
[ ] H1 tag exists and is unique on the page
|
|
96
|
+
[ ] Heading hierarchy is logical (H1 > H2 > H3, no skips)
|
|
97
|
+
[ ] Images have descriptive alt text
|
|
98
|
+
[ ] Images are optimized (WebP/AVIF, lazy loading)
|
|
99
|
+
[ ] Internal links use descriptive anchor text
|
|
100
|
+
[ ] Content length adequate for topic (minimum 300 words for landing pages)
|
|
101
|
+
[ ] Keyword appears in: title, H1, first paragraph, URL
|
|
102
|
+
[ ] No duplicate content issues
|
|
103
|
+
[ ] No thin pages (< 100 words)
|
|
104
|
+
[ ] Open Graph tags present (og:title, og:description, og:image)
|
|
105
|
+
[ ] Twitter Card tags present
|
|
106
|
+
[ ] Favicon configured
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Step 4: Off-Page SEO Signals
|
|
110
|
+
|
|
111
|
+
For Standard and Pro tiers only:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
[ ] Check backlink profile (note: limited without API access, use available data)
|
|
115
|
+
[ ] Social media presence linked from site
|
|
116
|
+
[ ] Google Business Profile (if local business)
|
|
117
|
+
[ ] NAP consistency (Name, Address, Phone) across web
|
|
118
|
+
[ ] Domain authority estimation based on observable signals
|
|
119
|
+
[ ] Competitor comparison (Pro tier: audit top 3 competitors)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Step 5: Performance Metrics
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
[ ] Page load time (target: < 3 seconds)
|
|
126
|
+
[ ] First Contentful Paint (target: < 1.8s)
|
|
127
|
+
[ ] Largest Contentful Paint (target: < 2.5s)
|
|
128
|
+
[ ] Total page size (target: < 3MB)
|
|
129
|
+
[ ] Number of HTTP requests (target: < 50)
|
|
130
|
+
[ ] JavaScript bundle size (target: < 500KB compressed)
|
|
131
|
+
[ ] CSS optimization (unused CSS, render-blocking)
|
|
132
|
+
[ ] Font loading strategy (font-display: swap)
|
|
133
|
+
[ ] Third-party script impact
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step 6: Score Calculation
|
|
137
|
+
|
|
138
|
+
Calculate an overall score from 0-100:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Technical SEO: 35 points (each item = 35 / item_count)
|
|
142
|
+
On-Page SEO: 30 points (each item = 30 / item_count)
|
|
143
|
+
Off-Page SEO: 15 points (each item = 15 / item_count)
|
|
144
|
+
Performance: 20 points (each item = 20 / item_count)
|
|
145
|
+
|
|
146
|
+
PASS = full points
|
|
147
|
+
WARN = half points
|
|
148
|
+
FAIL = 0 points
|
|
149
|
+
|
|
150
|
+
Grade:
|
|
151
|
+
90-100: A (Excellent)
|
|
152
|
+
80-89: B (Good)
|
|
153
|
+
70-79: C (Needs Improvement)
|
|
154
|
+
60-69: D (Poor)
|
|
155
|
+
< 60: F (Critical)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Report Format
|
|
159
|
+
|
|
160
|
+
Generate the final report in Markdown. This is what the client receives.
|
|
161
|
+
|
|
162
|
+
```markdown
|
|
163
|
+
# SEO Audit Report
|
|
164
|
+
**URL:** {url}
|
|
165
|
+
**Date:** {date}
|
|
166
|
+
**Tier:** {Basic|Standard|Pro}
|
|
167
|
+
**Overall Score:** {score}/100 ({grade})
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Executive Summary
|
|
172
|
+
{2-3 sentences summarizing the site's SEO health and top priorities}
|
|
173
|
+
|
|
174
|
+
## Score Breakdown
|
|
175
|
+
| Category | Score | Grade |
|
|
176
|
+
|----------|-------|-------|
|
|
177
|
+
| Technical SEO | {x}/35 | {grade} |
|
|
178
|
+
| On-Page SEO | {x}/30 | {grade} |
|
|
179
|
+
| Off-Page SEO | {x}/15 | {grade} |
|
|
180
|
+
| Performance | {x}/20 | {grade} |
|
|
181
|
+
| **Overall** | **{x}/100** | **{grade}** |
|
|
182
|
+
|
|
183
|
+
## Critical Issues (Fix Immediately)
|
|
184
|
+
{Numbered list of FAIL items with specific fix instructions}
|
|
185
|
+
|
|
186
|
+
## Warnings (Fix Soon)
|
|
187
|
+
{Numbered list of WARN items with recommendations}
|
|
188
|
+
|
|
189
|
+
## What You Are Doing Well
|
|
190
|
+
{List of PASS items worth highlighting}
|
|
191
|
+
|
|
192
|
+
## Priority Action Plan
|
|
193
|
+
1. {Highest impact fix with estimated effort}
|
|
194
|
+
2. {Second highest impact fix}
|
|
195
|
+
3. {Third highest impact fix}
|
|
196
|
+
4. ...
|
|
197
|
+
|
|
198
|
+
## Detailed Findings
|
|
199
|
+
|
|
200
|
+
### Technical SEO
|
|
201
|
+
{Table of all items with PASS/WARN/FAIL and notes}
|
|
202
|
+
|
|
203
|
+
### On-Page SEO
|
|
204
|
+
{Table of all items with PASS/WARN/FAIL and notes}
|
|
205
|
+
|
|
206
|
+
### Off-Page SEO (Standard/Pro only)
|
|
207
|
+
{Table of all items with PASS/WARN/FAIL and notes}
|
|
208
|
+
|
|
209
|
+
### Performance
|
|
210
|
+
{Table of all items with PASS/WARN/FAIL and notes}
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
*Generated by CashClaw SEO Auditor | cashclaw.ai*
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Deliverable Packaging
|
|
217
|
+
|
|
218
|
+
1. Save the Markdown report as `seo-audit-{domain}-{date}.md`.
|
|
219
|
+
2. Save the raw audit data as `seo-audit-{domain}-{date}.json`.
|
|
220
|
+
3. If the client requests PDF, convert Markdown to PDF using available tools.
|
|
221
|
+
4. Place all deliverables in the mission's `deliverables/` directory.
|
|
222
|
+
5. Return to `cashclaw-core` with status `delivered` and file paths.
|
|
223
|
+
|
|
224
|
+
## Quality Standards
|
|
225
|
+
|
|
226
|
+
- Every FAIL finding must include a specific, actionable fix (not "improve your SEO").
|
|
227
|
+
- Include code snippets where applicable (e.g., exact meta tag to add).
|
|
228
|
+
- Compare against industry benchmarks, not arbitrary standards.
|
|
229
|
+
- If data is unavailable for a check, mark as N/A and explain why.
|
|
230
|
+
- Pro tier must include at least 3 competitor comparisons.
|
|
231
|
+
|
|
232
|
+
## Example curl to Test
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Run a basic audit
|
|
236
|
+
cashclaw audit --url "https://example.com" --tier basic
|
|
237
|
+
|
|
238
|
+
# Run a standard audit
|
|
239
|
+
cashclaw audit --url "https://example.com" --tier standard --output report.md
|
|
240
|
+
```
|