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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/bin/cashclaw.js +2 -0
  4. package/missions/blog-post-1500.json +21 -0
  5. package/missions/blog-post-500.json +19 -0
  6. package/missions/lead-list-50.json +20 -0
  7. package/missions/seo-audit-basic.json +19 -0
  8. package/missions/seo-audit-pro.json +23 -0
  9. package/missions/social-media-weekly.json +19 -0
  10. package/missions/whatsapp-setup.json +22 -0
  11. package/package.json +45 -0
  12. package/skills/cashclaw-content-writer/SKILL.md +245 -0
  13. package/skills/cashclaw-core/SKILL.md +251 -0
  14. package/skills/cashclaw-invoicer/SKILL.md +395 -0
  15. package/skills/cashclaw-invoicer/scripts/stripe-ops.js +441 -0
  16. package/skills/cashclaw-lead-generator/SKILL.md +246 -0
  17. package/skills/cashclaw-lead-generator/scripts/scraper.js +356 -0
  18. package/skills/cashclaw-seo-auditor/SKILL.md +240 -0
  19. package/skills/cashclaw-seo-auditor/scripts/audit.js +401 -0
  20. package/skills/cashclaw-social-media/SKILL.md +374 -0
  21. package/skills/cashclaw-whatsapp-manager/SKILL.md +357 -0
  22. package/src/cli/commands/dashboard.js +72 -0
  23. package/src/cli/commands/init.js +290 -0
  24. package/src/cli/commands/status.js +174 -0
  25. package/src/cli/index.js +496 -0
  26. package/src/cli/utils/banner.js +44 -0
  27. package/src/cli/utils/config.js +170 -0
  28. package/src/dashboard/public/app.js +329 -0
  29. package/src/dashboard/public/index.html +139 -0
  30. package/src/dashboard/public/style.css +464 -0
  31. package/src/dashboard/server.js +224 -0
  32. package/src/engine/earnings-tracker.js +184 -0
  33. package/src/engine/mission-runner.js +224 -0
  34. package/src/engine/scheduler.js +139 -0
  35. package/src/integrations/hyrve-bridge.js +213 -0
  36. package/src/integrations/openclaw-bridge.js +207 -0
  37. package/src/integrations/stripe-connect.js +204 -0
  38. package/templates/config.default.json +83 -0
  39. package/templates/invoice.html +260 -0
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CashClaw SEO Auditor - Technical Audit Script
5
+ *
6
+ * Usage:
7
+ * node audit.js --url "https://example.com" [--output audit.json]
8
+ *
9
+ * Fetches a URL, parses the HTML, and generates structured audit data
10
+ * covering meta tags, headings, images, links, and performance signals.
11
+ */
12
+
13
+ const { argv, exit } = require("process");
14
+ const { writeFileSync } = require("fs");
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Argument parsing
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function parseArgs() {
21
+ const args = {};
22
+ for (let i = 2; i < argv.length; i++) {
23
+ if (argv[i] === "--url" && argv[i + 1]) {
24
+ args.url = argv[++i];
25
+ } else if (argv[i] === "--output" && argv[i + 1]) {
26
+ args.output = argv[++i];
27
+ }
28
+ }
29
+ if (!args.url) {
30
+ console.error("Usage: node audit.js --url <URL> [--output <file.json>]");
31
+ exit(1);
32
+ }
33
+ return args;
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // HTML helpers (lightweight, no external deps)
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function extractTag(html, tag) {
41
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
42
+ const matches = [];
43
+ let m;
44
+ while ((m = re.exec(html)) !== null) matches.push(m[1].trim());
45
+ return matches;
46
+ }
47
+
48
+ function extractMeta(html, nameOrProperty) {
49
+ // <meta name="..." content="..."> or <meta property="..." content="...">
50
+ const re = new RegExp(
51
+ `<meta[^>]*(?:name|property)=["']${nameOrProperty}["'][^>]*content=["']([^"']*)["']`,
52
+ "i"
53
+ );
54
+ const alt = new RegExp(
55
+ `<meta[^>]*content=["']([^"']*)["'][^>]*(?:name|property)=["']${nameOrProperty}["']`,
56
+ "i"
57
+ );
58
+ const m = html.match(re) || html.match(alt);
59
+ return m ? m[1] : null;
60
+ }
61
+
62
+ function extractTagAttr(html, tag, attr) {
63
+ const re = new RegExp(`<${tag}[^>]*${attr}=["']([^"']*)["']`, "gi");
64
+ const results = [];
65
+ let m;
66
+ while ((m = re.exec(html)) !== null) results.push(m[1]);
67
+ return results;
68
+ }
69
+
70
+ function extractLinks(html) {
71
+ const re = /<a[^>]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi;
72
+ const links = [];
73
+ let m;
74
+ while ((m = re.exec(html)) !== null) {
75
+ links.push({ href: m[1], text: m[2].replace(/<[^>]*>/g, "").trim() });
76
+ }
77
+ return links;
78
+ }
79
+
80
+ function extractImages(html) {
81
+ const re = /<img[^>]*>/gi;
82
+ const images = [];
83
+ let m;
84
+ while ((m = re.exec(html)) !== null) {
85
+ const tag = m[0];
86
+ const src = (tag.match(/src=["']([^"']*)["']/i) || [])[1] || "";
87
+ const alt = (tag.match(/alt=["']([^"']*)["']/i) || [])[1] || "";
88
+ const loading = (tag.match(/loading=["']([^"']*)["']/i) || [])[1] || "";
89
+ images.push({ src, alt, loading });
90
+ }
91
+ return images;
92
+ }
93
+
94
+ function wordCount(html) {
95
+ const text = html
96
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
97
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
98
+ .replace(/<[^>]*>/g, " ")
99
+ .replace(/\s+/g, " ")
100
+ .trim();
101
+ return text ? text.split(" ").length : 0;
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Audit checks
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function check(name, pass, value, note) {
109
+ return {
110
+ name,
111
+ status: pass === null ? "N/A" : pass ? "PASS" : "FAIL",
112
+ value,
113
+ note: note || "",
114
+ };
115
+ }
116
+
117
+ function auditTechnical(url, html, headers, timing) {
118
+ const results = [];
119
+
120
+ // HTTPS
121
+ results.push(
122
+ check("HTTPS enabled", url.startsWith("https"), url)
123
+ );
124
+
125
+ // Response time
126
+ results.push(
127
+ check(
128
+ "Server response time",
129
+ timing < 500,
130
+ `${timing}ms`,
131
+ timing < 500 ? "Good" : "Slow - target < 500ms"
132
+ )
133
+ );
134
+
135
+ // Viewport meta
136
+ const hasViewport = /meta[^>]*name=["']viewport["']/i.test(html);
137
+ results.push(check("Mobile viewport meta", hasViewport, hasViewport));
138
+
139
+ // Canonical
140
+ const canonical = (html.match(/<link[^>]*rel=["']canonical["'][^>]*href=["']([^"']*)["']/i) || [])[1];
141
+ results.push(check("Canonical tag", !!canonical, canonical || "Missing"));
142
+
143
+ // Language
144
+ const lang = (html.match(/<html[^>]*lang=["']([^"']*)["']/i) || [])[1];
145
+ results.push(check("HTML lang attribute", !!lang, lang || "Missing"));
146
+
147
+ // Charset
148
+ const hasCharset = /meta[^>]*charset/i.test(html);
149
+ results.push(check("Charset declaration", hasCharset, hasCharset));
150
+
151
+ // Compression
152
+ const encoding = headers["content-encoding"] || "";
153
+ const compressed = /gzip|br|deflate/i.test(encoding);
154
+ results.push(check("Compression (gzip/br)", compressed, encoding || "None"));
155
+
156
+ return results;
157
+ }
158
+
159
+ function auditOnPage(html) {
160
+ const results = [];
161
+
162
+ // Title
163
+ const titles = extractTag(html, "title");
164
+ const title = titles[0] || "";
165
+ results.push(
166
+ check(
167
+ "Title tag exists",
168
+ titles.length === 1 && title.length > 0,
169
+ title || "Missing",
170
+ title ? `${title.length} chars` : "No title tag found"
171
+ )
172
+ );
173
+ results.push(
174
+ check(
175
+ "Title length (50-60 chars)",
176
+ title.length >= 50 && title.length <= 60,
177
+ `${title.length} chars`,
178
+ title.length < 50 ? "Too short" : title.length > 60 ? "Too long" : "Good"
179
+ )
180
+ );
181
+
182
+ // Meta description
183
+ const desc = extractMeta(html, "description") || "";
184
+ results.push(
185
+ check("Meta description exists", desc.length > 0, desc || "Missing")
186
+ );
187
+ results.push(
188
+ check(
189
+ "Meta description length (150-160)",
190
+ desc.length >= 150 && desc.length <= 160,
191
+ `${desc.length} chars`
192
+ )
193
+ );
194
+
195
+ // Headings
196
+ const h1s = extractTag(html, "h1");
197
+ results.push(
198
+ check("H1 tag exists and unique", h1s.length === 1, `${h1s.length} H1 tags found`)
199
+ );
200
+
201
+ const h2s = extractTag(html, "h2");
202
+ results.push(
203
+ check("H2 tags present", h2s.length > 0, `${h2s.length} H2 tags`)
204
+ );
205
+
206
+ // Images
207
+ const images = extractImages(html);
208
+ const imagesWithAlt = images.filter((img) => img.alt.length > 0);
209
+ const allHaveAlt = images.length === 0 || imagesWithAlt.length === images.length;
210
+ results.push(
211
+ check(
212
+ "Images have alt text",
213
+ allHaveAlt,
214
+ `${imagesWithAlt.length}/${images.length} have alt`,
215
+ allHaveAlt ? "Good" : "Add alt text to all images"
216
+ )
217
+ );
218
+
219
+ const lazyLoaded = images.filter((img) => img.loading === "lazy");
220
+ results.push(
221
+ check(
222
+ "Images use lazy loading",
223
+ images.length === 0 || lazyLoaded.length > 0,
224
+ `${lazyLoaded.length}/${images.length} lazy`
225
+ )
226
+ );
227
+
228
+ // Open Graph
229
+ const ogTitle = extractMeta(html, "og:title");
230
+ const ogDesc = extractMeta(html, "og:description");
231
+ const ogImage = extractMeta(html, "og:image");
232
+ results.push(
233
+ check("Open Graph tags", !!(ogTitle && ogDesc && ogImage), {
234
+ title: ogTitle || "Missing",
235
+ description: ogDesc || "Missing",
236
+ image: ogImage || "Missing",
237
+ })
238
+ );
239
+
240
+ // Twitter Card
241
+ const twCard = extractMeta(html, "twitter:card");
242
+ results.push(check("Twitter Card meta", !!twCard, twCard || "Missing"));
243
+
244
+ // Word count
245
+ const wc = wordCount(html);
246
+ results.push(
247
+ check("Content length (300+ words)", wc >= 300, `${wc} words`)
248
+ );
249
+
250
+ // Favicon
251
+ const hasFavicon = /<link[^>]*rel=["'](?:shortcut )?icon["']/i.test(html);
252
+ results.push(check("Favicon configured", hasFavicon, hasFavicon));
253
+
254
+ return results;
255
+ }
256
+
257
+ function auditLinks(html, baseUrl) {
258
+ const links = extractLinks(html);
259
+ const internal = links.filter((l) => {
260
+ try {
261
+ const u = new URL(l.href, baseUrl);
262
+ return u.hostname === new URL(baseUrl).hostname;
263
+ } catch {
264
+ return false;
265
+ }
266
+ });
267
+ const external = links.filter((l) => !internal.includes(l));
268
+ const emptyAnchors = links.filter((l) => l.text.length === 0);
269
+
270
+ return {
271
+ total: links.length,
272
+ internal: internal.length,
273
+ external: external.length,
274
+ emptyAnchors: emptyAnchors.length,
275
+ links: links.slice(0, 50), // cap at 50 for output size
276
+ };
277
+ }
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Score calculation
281
+ // ---------------------------------------------------------------------------
282
+
283
+ function calcScore(checks) {
284
+ let total = 0;
285
+ let passed = 0;
286
+ for (const c of checks) {
287
+ if (c.status === "N/A") continue;
288
+ total++;
289
+ if (c.status === "PASS") passed++;
290
+ }
291
+ return total === 0 ? 100 : Math.round((passed / total) * 100);
292
+ }
293
+
294
+ function grade(score) {
295
+ if (score >= 90) return "A";
296
+ if (score >= 80) return "B";
297
+ if (score >= 70) return "C";
298
+ if (score >= 60) return "D";
299
+ return "F";
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Main
304
+ // ---------------------------------------------------------------------------
305
+
306
+ async function main() {
307
+ const args = parseArgs();
308
+ const url = args.url;
309
+
310
+ console.log(`\n CashClaw SEO Auditor`);
311
+ console.log(` Auditing: ${url}\n`);
312
+
313
+ const startTime = Date.now();
314
+ let html, headers, status;
315
+
316
+ try {
317
+ const response = await fetch(url, {
318
+ headers: {
319
+ "User-Agent":
320
+ "CashClawBot/1.0 (+https://cashclaw.ai) Mozilla/5.0 compatible",
321
+ Accept: "text/html,application/xhtml+xml",
322
+ },
323
+ redirect: "follow",
324
+ });
325
+ status = response.status;
326
+ headers = Object.fromEntries(response.headers.entries());
327
+ html = await response.text();
328
+ } catch (err) {
329
+ console.error(` Failed to fetch ${url}: ${err.message}`);
330
+ exit(1);
331
+ }
332
+
333
+ const timing = Date.now() - startTime;
334
+
335
+ console.log(` Status: ${status}`);
336
+ console.log(` Response time: ${timing}ms`);
337
+ console.log(` Page size: ${(html.length / 1024).toFixed(1)}KB\n`);
338
+
339
+ // Run audits
340
+ const technical = auditTechnical(url, html, headers, timing);
341
+ const onPage = auditOnPage(html);
342
+ const linkData = auditLinks(html, url);
343
+
344
+ const allChecks = [...technical, ...onPage];
345
+ const overallScore = calcScore(allChecks);
346
+ const technicalScore = calcScore(technical);
347
+ const onPageScore = calcScore(onPage);
348
+
349
+ const audit = {
350
+ url,
351
+ audited_at: new Date().toISOString(),
352
+ response: {
353
+ status,
354
+ timing_ms: timing,
355
+ page_size_bytes: html.length,
356
+ content_type: headers["content-type"] || "",
357
+ },
358
+ scores: {
359
+ overall: overallScore,
360
+ overall_grade: grade(overallScore),
361
+ technical: technicalScore,
362
+ technical_grade: grade(technicalScore),
363
+ on_page: onPageScore,
364
+ on_page_grade: grade(onPageScore),
365
+ },
366
+ technical,
367
+ on_page: onPage,
368
+ links: linkData,
369
+ images: extractImages(html).length,
370
+ word_count: wordCount(html),
371
+ headings: {
372
+ h1: extractTag(html, "h1"),
373
+ h2: extractTag(html, "h2"),
374
+ h3: extractTag(html, "h3"),
375
+ },
376
+ };
377
+
378
+ // Output
379
+ const json = JSON.stringify(audit, null, 2);
380
+
381
+ if (args.output) {
382
+ writeFileSync(args.output, json, "utf-8");
383
+ console.log(` Report saved to: ${args.output}`);
384
+ } else {
385
+ console.log(json);
386
+ }
387
+
388
+ // Summary
389
+ console.log(`\n ---- Score Summary ----`);
390
+ console.log(` Overall: ${overallScore}/100 (${grade(overallScore)})`);
391
+ console.log(` Technical: ${technicalScore}/100 (${grade(technicalScore)})`);
392
+ console.log(` On-Page: ${onPageScore}/100 (${grade(onPageScore)})`);
393
+ console.log(
394
+ ` Issues: ${allChecks.filter((c) => c.status === "FAIL").length} failures, ${allChecks.filter((c) => c.status === "PASS").length} passed\n`
395
+ );
396
+ }
397
+
398
+ main().catch((err) => {
399
+ console.error(`Fatal error: ${err.message}`);
400
+ exit(1);
401
+ });