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,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CashClaw Invoicer - Stripe Operations Script
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node stripe-ops.js <command> [options]
|
|
8
|
+
*
|
|
9
|
+
* Commands:
|
|
10
|
+
* create-link Create a Stripe payment link
|
|
11
|
+
* create-invoice Create and send a Stripe invoice
|
|
12
|
+
* check-status Check payment status of an invoice
|
|
13
|
+
* send-reminder Send a payment reminder
|
|
14
|
+
* list-unpaid List all unpaid invoices
|
|
15
|
+
* refund Process a refund
|
|
16
|
+
*
|
|
17
|
+
* Requires: STRIPE_SECRET_KEY environment variable or ~/.cashclaw/config.json
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { argv, exit, env } = require("process");
|
|
21
|
+
const { readFileSync, writeFileSync, existsSync, appendFileSync } = require("fs");
|
|
22
|
+
const { join } = require("path");
|
|
23
|
+
const { homedir } = require("os");
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Configuration
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
function getStripeKey() {
|
|
30
|
+
// 1. Environment variable
|
|
31
|
+
if (env.STRIPE_SECRET_KEY) return env.STRIPE_SECRET_KEY;
|
|
32
|
+
|
|
33
|
+
// 2. Config file
|
|
34
|
+
const configPath = join(homedir(), ".cashclaw", "config.json");
|
|
35
|
+
if (existsSync(configPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
38
|
+
if (config.stripe_secret_key) return config.stripe_secret_key;
|
|
39
|
+
} catch {
|
|
40
|
+
// ignore parse errors
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.error("Error: STRIPE_SECRET_KEY not found.");
|
|
45
|
+
console.error("Set it via environment variable or in ~/.cashclaw/config.json");
|
|
46
|
+
exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getStripe() {
|
|
50
|
+
try {
|
|
51
|
+
const Stripe = require("stripe");
|
|
52
|
+
return new Stripe(getStripeKey(), { apiVersion: "2024-12-18.acacia" });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err.code === "MODULE_NOT_FOUND") {
|
|
55
|
+
console.error('Error: stripe package not installed. Run: npm install stripe');
|
|
56
|
+
exit(1);
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Ledger logging
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
function logEvent(event) {
|
|
67
|
+
const ledgerPath = join(homedir(), ".cashclaw", "ledger.jsonl");
|
|
68
|
+
const entry = { ts: new Date().toISOString(), ...event };
|
|
69
|
+
try {
|
|
70
|
+
appendFileSync(ledgerPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
71
|
+
} catch {
|
|
72
|
+
// If directory doesn't exist, skip logging
|
|
73
|
+
console.log(` [log] ${JSON.stringify(entry)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Argument parsing
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
function parseArgs() {
|
|
82
|
+
const args = { command: argv[2] };
|
|
83
|
+
for (let i = 3; i < argv.length; i++) {
|
|
84
|
+
const key = argv[i].replace(/^--/, "").replace(/-/g, "_");
|
|
85
|
+
const val = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : true;
|
|
86
|
+
args[key] = val;
|
|
87
|
+
}
|
|
88
|
+
return args;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Commands
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
async function createPaymentLink(stripe, args) {
|
|
96
|
+
const amount = parseInt(args.amount, 10);
|
|
97
|
+
const currency = args.currency || "usd";
|
|
98
|
+
const description = args.description || "CashClaw Service";
|
|
99
|
+
const missionId = args.mission || "unknown";
|
|
100
|
+
|
|
101
|
+
if (!amount || isNaN(amount)) {
|
|
102
|
+
console.error("Error: --amount required (in smallest currency unit, e.g., 2900 for $29)");
|
|
103
|
+
exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\n Creating payment link...`);
|
|
107
|
+
console.log(` Amount: ${(amount / 100).toFixed(2)} ${currency.toUpperCase()}`);
|
|
108
|
+
console.log(` Description: ${description}\n`);
|
|
109
|
+
|
|
110
|
+
const link = await stripe.paymentLinks.create({
|
|
111
|
+
line_items: [
|
|
112
|
+
{
|
|
113
|
+
price_data: {
|
|
114
|
+
currency,
|
|
115
|
+
product_data: {
|
|
116
|
+
name: description,
|
|
117
|
+
metadata: { mission_id: missionId },
|
|
118
|
+
},
|
|
119
|
+
unit_amount: amount,
|
|
120
|
+
},
|
|
121
|
+
quantity: 1,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
metadata: { mission_id: missionId },
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
logEvent({
|
|
128
|
+
event: "payment_link_created",
|
|
129
|
+
mission_id: missionId,
|
|
130
|
+
link_id: link.id,
|
|
131
|
+
url: link.url,
|
|
132
|
+
amount,
|
|
133
|
+
currency,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log(` Payment link created!`);
|
|
137
|
+
console.log(` URL: ${link.url}`);
|
|
138
|
+
console.log(` Link ID: ${link.id}\n`);
|
|
139
|
+
|
|
140
|
+
return link;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function createInvoice(stripe, args) {
|
|
144
|
+
const email = args.email;
|
|
145
|
+
const amount = parseInt(args.amount, 10);
|
|
146
|
+
const currency = args.currency || "usd";
|
|
147
|
+
const description = args.description || "CashClaw Service";
|
|
148
|
+
const dueDays = parseInt(args.due_days || "7", 10);
|
|
149
|
+
const missionId = args.mission || "unknown";
|
|
150
|
+
|
|
151
|
+
if (!email) {
|
|
152
|
+
console.error("Error: --email required");
|
|
153
|
+
exit(1);
|
|
154
|
+
}
|
|
155
|
+
if (!amount || isNaN(amount)) {
|
|
156
|
+
console.error("Error: --amount required (in smallest currency unit)");
|
|
157
|
+
exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`\n Creating invoice...`);
|
|
161
|
+
console.log(` Client: ${email}`);
|
|
162
|
+
console.log(` Amount: ${(amount / 100).toFixed(2)} ${currency.toUpperCase()}`);
|
|
163
|
+
console.log(` Due: ${dueDays} days\n`);
|
|
164
|
+
|
|
165
|
+
// Find or create customer
|
|
166
|
+
const customers = await stripe.customers.list({ email, limit: 1 });
|
|
167
|
+
let customer;
|
|
168
|
+
if (customers.data.length > 0) {
|
|
169
|
+
customer = customers.data[0];
|
|
170
|
+
console.log(` Found existing customer: ${customer.id}`);
|
|
171
|
+
} else {
|
|
172
|
+
customer = await stripe.customers.create({
|
|
173
|
+
email,
|
|
174
|
+
metadata: { source: "cashclaw" },
|
|
175
|
+
});
|
|
176
|
+
console.log(` Created new customer: ${customer.id}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create invoice item
|
|
180
|
+
await stripe.invoiceItems.create({
|
|
181
|
+
customer: customer.id,
|
|
182
|
+
amount,
|
|
183
|
+
currency,
|
|
184
|
+
description,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Create invoice
|
|
188
|
+
const invoice = await stripe.invoices.create({
|
|
189
|
+
customer: customer.id,
|
|
190
|
+
collection_method: "send_invoice",
|
|
191
|
+
days_until_due: dueDays,
|
|
192
|
+
auto_advance: true,
|
|
193
|
+
metadata: { mission_id: missionId },
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Finalize
|
|
197
|
+
const finalized = await stripe.invoices.finalizeInvoice(invoice.id);
|
|
198
|
+
|
|
199
|
+
// Send
|
|
200
|
+
await stripe.invoices.sendInvoice(finalized.id);
|
|
201
|
+
|
|
202
|
+
logEvent({
|
|
203
|
+
event: "invoice_created",
|
|
204
|
+
mission_id: missionId,
|
|
205
|
+
invoice_id: finalized.id,
|
|
206
|
+
amount,
|
|
207
|
+
currency,
|
|
208
|
+
customer_id: customer.id,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
logEvent({
|
|
212
|
+
event: "invoice_sent",
|
|
213
|
+
mission_id: missionId,
|
|
214
|
+
invoice_id: finalized.id,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log(` Invoice created and sent!`);
|
|
218
|
+
console.log(` Invoice ID: ${finalized.id}`);
|
|
219
|
+
console.log(` Invoice Number: ${finalized.number}`);
|
|
220
|
+
console.log(` Hosted URL: ${finalized.hosted_invoice_url}`);
|
|
221
|
+
console.log(` PDF: ${finalized.invoice_pdf}\n`);
|
|
222
|
+
|
|
223
|
+
return finalized;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function checkPaymentStatus(stripe, args) {
|
|
227
|
+
const invoiceId = args.invoice;
|
|
228
|
+
if (!invoiceId) {
|
|
229
|
+
console.error("Error: --invoice required (e.g., in_1234567890)");
|
|
230
|
+
exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(`\n Checking status for: ${invoiceId}\n`);
|
|
234
|
+
|
|
235
|
+
const invoice = await stripe.invoices.retrieve(invoiceId);
|
|
236
|
+
|
|
237
|
+
const status = {
|
|
238
|
+
id: invoice.id,
|
|
239
|
+
number: invoice.number,
|
|
240
|
+
status: invoice.status,
|
|
241
|
+
amount_due: invoice.amount_due,
|
|
242
|
+
amount_paid: invoice.amount_paid,
|
|
243
|
+
amount_remaining: invoice.amount_remaining,
|
|
244
|
+
currency: invoice.currency,
|
|
245
|
+
due_date: invoice.due_date
|
|
246
|
+
? new Date(invoice.due_date * 1000).toISOString()
|
|
247
|
+
: null,
|
|
248
|
+
created: new Date(invoice.created * 1000).toISOString(),
|
|
249
|
+
paid: invoice.paid,
|
|
250
|
+
hosted_url: invoice.hosted_invoice_url,
|
|
251
|
+
customer_email: invoice.customer_email,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
console.log(` Status: ${status.status}`);
|
|
255
|
+
console.log(` Paid: ${status.paid}`);
|
|
256
|
+
console.log(
|
|
257
|
+
` Amount Due: ${(status.amount_due / 100).toFixed(2)} ${status.currency.toUpperCase()}`
|
|
258
|
+
);
|
|
259
|
+
console.log(
|
|
260
|
+
` Amount Paid: ${(status.amount_paid / 100).toFixed(2)} ${status.currency.toUpperCase()}`
|
|
261
|
+
);
|
|
262
|
+
console.log(
|
|
263
|
+
` Amount Remaining: ${(status.amount_remaining / 100).toFixed(2)} ${status.currency.toUpperCase()}`
|
|
264
|
+
);
|
|
265
|
+
if (status.due_date) console.log(` Due Date: ${status.due_date}`);
|
|
266
|
+
console.log(` Customer: ${status.customer_email}`);
|
|
267
|
+
console.log();
|
|
268
|
+
|
|
269
|
+
return status;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function sendReminder(stripe, args) {
|
|
273
|
+
const invoiceId = args.invoice;
|
|
274
|
+
const template = args.template || "gentle";
|
|
275
|
+
|
|
276
|
+
if (!invoiceId) {
|
|
277
|
+
console.error("Error: --invoice required");
|
|
278
|
+
exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(`\n Sending ${template} reminder for: ${invoiceId}\n`);
|
|
282
|
+
|
|
283
|
+
const invoice = await stripe.invoices.retrieve(invoiceId);
|
|
284
|
+
|
|
285
|
+
if (invoice.paid) {
|
|
286
|
+
console.log(" Invoice is already paid. No reminder needed.\n");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Stripe does not have a native "send reminder" endpoint, so we re-send the invoice
|
|
291
|
+
try {
|
|
292
|
+
await stripe.invoices.sendInvoice(invoiceId);
|
|
293
|
+
console.log(" Reminder sent successfully (invoice re-sent).\n");
|
|
294
|
+
} catch (err) {
|
|
295
|
+
// If invoice cannot be re-sent (already sent recently), log it
|
|
296
|
+
console.log(` Could not re-send invoice: ${err.message}`);
|
|
297
|
+
console.log(" Consider sending a manual reminder email.\n");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
logEvent({
|
|
301
|
+
event: "reminder_sent",
|
|
302
|
+
invoice_id: invoiceId,
|
|
303
|
+
template,
|
|
304
|
+
amount: invoice.amount_due,
|
|
305
|
+
currency: invoice.currency,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function listUnpaid(stripe) {
|
|
310
|
+
console.log(`\n Listing unpaid invoices...\n`);
|
|
311
|
+
|
|
312
|
+
const invoices = await stripe.invoices.list({
|
|
313
|
+
status: "open",
|
|
314
|
+
limit: 100,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (invoices.data.length === 0) {
|
|
318
|
+
console.log(" No unpaid invoices found.\n");
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(
|
|
323
|
+
` ${"ID".padEnd(30)} ${"Amount".padEnd(12)} ${"Due".padEnd(12)} ${"Customer".padEnd(30)}`
|
|
324
|
+
);
|
|
325
|
+
console.log(" " + "-".repeat(84));
|
|
326
|
+
|
|
327
|
+
for (const inv of invoices.data) {
|
|
328
|
+
const amount = `${(inv.amount_due / 100).toFixed(2)} ${inv.currency.toUpperCase()}`;
|
|
329
|
+
const due = inv.due_date
|
|
330
|
+
? new Date(inv.due_date * 1000).toISOString().split("T")[0]
|
|
331
|
+
: "N/A";
|
|
332
|
+
console.log(
|
|
333
|
+
` ${inv.id.padEnd(30)} ${amount.padEnd(12)} ${due.padEnd(12)} ${(inv.customer_email || "N/A").padEnd(30)}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
console.log(`\n Total unpaid: ${invoices.data.length}\n`);
|
|
337
|
+
|
|
338
|
+
return invoices.data;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function processRefund(stripe, args) {
|
|
342
|
+
const paymentIntent = args.payment_intent;
|
|
343
|
+
const amount = args.amount ? parseInt(args.amount, 10) : undefined;
|
|
344
|
+
|
|
345
|
+
if (!paymentIntent) {
|
|
346
|
+
console.error("Error: --payment-intent required (e.g., pi_xxxxx)");
|
|
347
|
+
exit(1);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
console.log(`\n Processing refund for: ${paymentIntent}`);
|
|
351
|
+
if (amount) console.log(` Partial refund: ${(amount / 100).toFixed(2)}`);
|
|
352
|
+
else console.log(` Full refund`);
|
|
353
|
+
console.log();
|
|
354
|
+
|
|
355
|
+
const refundData = { payment_intent: paymentIntent };
|
|
356
|
+
if (amount) refundData.amount = amount;
|
|
357
|
+
|
|
358
|
+
const refund = await stripe.refunds.create(refundData);
|
|
359
|
+
|
|
360
|
+
logEvent({
|
|
361
|
+
event: "refund_processed",
|
|
362
|
+
payment_intent: paymentIntent,
|
|
363
|
+
refund_id: refund.id,
|
|
364
|
+
amount: refund.amount,
|
|
365
|
+
currency: refund.currency,
|
|
366
|
+
status: refund.status,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log(` Refund processed!`);
|
|
370
|
+
console.log(` Refund ID: ${refund.id}`);
|
|
371
|
+
console.log(` Amount: ${(refund.amount / 100).toFixed(2)} ${refund.currency.toUpperCase()}`);
|
|
372
|
+
console.log(` Status: ${refund.status}\n`);
|
|
373
|
+
|
|
374
|
+
return refund;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Main
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
async function main() {
|
|
382
|
+
const args = parseArgs();
|
|
383
|
+
|
|
384
|
+
if (!args.command) {
|
|
385
|
+
console.log(`
|
|
386
|
+
CashClaw Stripe Operations
|
|
387
|
+
|
|
388
|
+
Commands:
|
|
389
|
+
create-link Create a payment link
|
|
390
|
+
create-invoice Create and send an invoice
|
|
391
|
+
check-status Check invoice payment status
|
|
392
|
+
send-reminder Send a payment reminder
|
|
393
|
+
list-unpaid List all unpaid invoices
|
|
394
|
+
refund Process a refund
|
|
395
|
+
|
|
396
|
+
Examples:
|
|
397
|
+
node stripe-ops.js create-link --amount 2900 --description "SEO Audit"
|
|
398
|
+
node stripe-ops.js create-invoice --email "client@co.com" --amount 2900
|
|
399
|
+
node stripe-ops.js check-status --invoice "in_xxxxx"
|
|
400
|
+
node stripe-ops.js send-reminder --invoice "in_xxxxx" --template gentle
|
|
401
|
+
node stripe-ops.js list-unpaid
|
|
402
|
+
node stripe-ops.js refund --payment-intent "pi_xxxxx"
|
|
403
|
+
`);
|
|
404
|
+
exit(0);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const stripe = getStripe();
|
|
408
|
+
|
|
409
|
+
switch (args.command) {
|
|
410
|
+
case "create-link":
|
|
411
|
+
await createPaymentLink(stripe, args);
|
|
412
|
+
break;
|
|
413
|
+
case "create-invoice":
|
|
414
|
+
await createInvoice(stripe, args);
|
|
415
|
+
break;
|
|
416
|
+
case "check-status":
|
|
417
|
+
await checkPaymentStatus(stripe, args);
|
|
418
|
+
break;
|
|
419
|
+
case "send-reminder":
|
|
420
|
+
await sendReminder(stripe, args);
|
|
421
|
+
break;
|
|
422
|
+
case "list-unpaid":
|
|
423
|
+
await listUnpaid(stripe);
|
|
424
|
+
break;
|
|
425
|
+
case "refund":
|
|
426
|
+
await processRefund(stripe, args);
|
|
427
|
+
break;
|
|
428
|
+
default:
|
|
429
|
+
console.error(`Unknown command: ${args.command}`);
|
|
430
|
+
console.error('Run without arguments to see available commands.');
|
|
431
|
+
exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
main().catch((err) => {
|
|
436
|
+
console.error(`\n Stripe Error: ${err.message}`);
|
|
437
|
+
if (err.type === "StripeAuthenticationError") {
|
|
438
|
+
console.error(" Check your STRIPE_SECRET_KEY.");
|
|
439
|
+
}
|
|
440
|
+
exit(1);
|
|
441
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cashclaw-lead-generator
|
|
3
|
+
description: Generates qualified B2B leads through systematic research, data collection, and scoring. Delivers structured lead lists with contact information and qualification scores.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"openclaw":
|
|
7
|
+
{
|
|
8
|
+
"emoji": "\U0001F3AF",
|
|
9
|
+
"requires": { "bins": ["node"] },
|
|
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 Lead Generator
|
|
25
|
+
|
|
26
|
+
You generate qualified B2B leads that clients can immediately use for outreach.
|
|
27
|
+
Every lead must include verifiable contact information and a qualification score.
|
|
28
|
+
Quality over quantity -- a list of 25 qualified leads beats 100 unverified names.
|
|
29
|
+
|
|
30
|
+
## Pricing Tiers
|
|
31
|
+
|
|
32
|
+
| Tier | Lead Count | Price | Delivery |
|
|
33
|
+
|------|-----------|-------|----------|
|
|
34
|
+
| Starter | 25 qualified leads | $9 | 6 hours |
|
|
35
|
+
| Growth | 50 qualified leads | $15 | 12 hours |
|
|
36
|
+
| Scale | 100 qualified leads | $25 | 24 hours |
|
|
37
|
+
|
|
38
|
+
Custom enterprise packages available for 500+ leads at negotiated rates.
|
|
39
|
+
|
|
40
|
+
## Lead Generation Process
|
|
41
|
+
|
|
42
|
+
### Step 1: Define Ideal Customer Profile (ICP)
|
|
43
|
+
|
|
44
|
+
Before generating a single lead, extract or ask for these ICP parameters:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
ICP Definition:
|
|
48
|
+
Industry: {e.g., "SaaS", "E-commerce", "Healthcare"}
|
|
49
|
+
Company Size: {employee range, e.g., "10-50", "50-200"}
|
|
50
|
+
Revenue Range: {e.g., "$1M-$10M ARR"}
|
|
51
|
+
Geography: {country, region, or city}
|
|
52
|
+
Job Titles: {decision maker titles, e.g., "CEO", "CTO", "Head of Marketing"}
|
|
53
|
+
Tech Stack: {optional - tools they use, e.g., "Shopify", "HubSpot"}
|
|
54
|
+
Pain Points: {what problems does the client solve for these leads}
|
|
55
|
+
Exclusions: {competitors, existing clients, specific companies to skip}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If the client provides vague input like "find me SaaS companies," ask clarifying
|
|
59
|
+
questions. The ICP quality directly determines lead quality.
|
|
60
|
+
|
|
61
|
+
### Step 2: Research Channels
|
|
62
|
+
|
|
63
|
+
Use these sources in priority order:
|
|
64
|
+
|
|
65
|
+
1. **Company websites** - About pages, team pages, contact pages.
|
|
66
|
+
2. **LinkedIn** - Company pages, employee directories (respect rate limits).
|
|
67
|
+
3. **Industry directories** - Clutch, G2, Capterra, Crunchbase, AngelList.
|
|
68
|
+
4. **Job boards** - Companies hiring for relevant roles signal growth.
|
|
69
|
+
5. **Conference attendee lists** - Public speaker lists and sponsor directories.
|
|
70
|
+
6. **Press releases** - Funding announcements signal budget availability.
|
|
71
|
+
7. **GitHub** - For developer-focused leads, check org pages.
|
|
72
|
+
8. **Social media** - Twitter/X bios, LinkedIn posts related to pain points.
|
|
73
|
+
|
|
74
|
+
### Step 3: Data Collection
|
|
75
|
+
|
|
76
|
+
For each lead, collect the following fields:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"company": "Company Name",
|
|
81
|
+
"website": "https://company.com",
|
|
82
|
+
"industry": "SaaS",
|
|
83
|
+
"size": "50-200",
|
|
84
|
+
"location": "San Francisco, CA",
|
|
85
|
+
"contact": {
|
|
86
|
+
"name": "John Doe",
|
|
87
|
+
"title": "CTO",
|
|
88
|
+
"email": "john@company.com",
|
|
89
|
+
"phone": "+1-555-0123",
|
|
90
|
+
"linkedin": "https://linkedin.com/in/johndoe"
|
|
91
|
+
},
|
|
92
|
+
"signals": {
|
|
93
|
+
"recently_funded": false,
|
|
94
|
+
"hiring": true,
|
|
95
|
+
"tech_stack_match": true,
|
|
96
|
+
"content_engagement": false
|
|
97
|
+
},
|
|
98
|
+
"score": 7,
|
|
99
|
+
"notes": "Recently posted about scaling challenges. Hiring 3 engineers."
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Required fields:** company, website, contact.name, contact.title, contact.email, score.
|
|
104
|
+
**Strongly recommended:** phone, linkedin, location, industry.
|
|
105
|
+
**Optional but valuable:** signals, notes.
|
|
106
|
+
|
|
107
|
+
### Step 4: Lead Qualification Scoring
|
|
108
|
+
|
|
109
|
+
Score every lead from 1-10 using this rubric:
|
|
110
|
+
|
|
111
|
+
| Criteria | Points | How to Verify |
|
|
112
|
+
|----------|--------|---------------|
|
|
113
|
+
| Matches ICP industry | +2 | Company website, LinkedIn |
|
|
114
|
+
| Matches ICP company size | +1 | LinkedIn employee count, website |
|
|
115
|
+
| Decision maker identified | +2 | Title matches target role |
|
|
116
|
+
| Verified email address | +1 | Email pattern validation |
|
|
117
|
+
| Phone number available | +1 | Website contact page |
|
|
118
|
+
| Buying signals present | +2 | Hiring, funding, pain point mentions |
|
|
119
|
+
| Active online presence | +1 | Recent posts, blog, social activity |
|
|
120
|
+
|
|
121
|
+
**Score interpretation:**
|
|
122
|
+
|
|
123
|
+
| Score | Label | Meaning |
|
|
124
|
+
|-------|-------|---------|
|
|
125
|
+
| 8-10 | Hot | High-priority, immediate outreach recommended |
|
|
126
|
+
| 6-7 | Warm | Good fit, likely to respond |
|
|
127
|
+
| 4-5 | Cool | Partial fit, nurture needed |
|
|
128
|
+
| 1-3 | Cold | Low priority, may not be a fit |
|
|
129
|
+
|
|
130
|
+
Only include leads with score >= 4 in the deliverable. Replace any leads
|
|
131
|
+
scoring below 4 with better-qualified alternatives.
|
|
132
|
+
|
|
133
|
+
### Step 5: Email Verification
|
|
134
|
+
|
|
135
|
+
For every email, apply these validation checks:
|
|
136
|
+
|
|
137
|
+
1. **Format check** - Valid email format (RFC 5322).
|
|
138
|
+
2. **Domain check** - Domain exists, has MX records.
|
|
139
|
+
3. **Pattern matching** - Use common patterns: first@, first.last@, firstl@.
|
|
140
|
+
4. **Catch-all detection** - Note if the domain accepts all addresses.
|
|
141
|
+
5. **Disposable email** - Flag and exclude disposable email domains.
|
|
142
|
+
|
|
143
|
+
Mark email confidence level:
|
|
144
|
+
- **Verified** - Confirmed via public source or pattern match on known domain.
|
|
145
|
+
- **Likely** - Pattern-based guess on valid domain.
|
|
146
|
+
- **Unverified** - Could not confirm, included as best guess.
|
|
147
|
+
|
|
148
|
+
### Step 6: Deliverable Assembly
|
|
149
|
+
|
|
150
|
+
Package leads in the following formats:
|
|
151
|
+
|
|
152
|
+
**Primary: JSON file**
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"metadata": {
|
|
157
|
+
"generated_at": "2026-02-23T12:00:00Z",
|
|
158
|
+
"icp": { "industry": "SaaS", "size": "10-50", "geo": "US" },
|
|
159
|
+
"total_leads": 25,
|
|
160
|
+
"avg_score": 7.2,
|
|
161
|
+
"score_distribution": { "hot": 8, "warm": 12, "cool": 5 }
|
|
162
|
+
},
|
|
163
|
+
"leads": [
|
|
164
|
+
{ "...lead object..." }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Secondary: CSV file**
|
|
170
|
+
|
|
171
|
+
```csv
|
|
172
|
+
company,website,contact_name,contact_title,email,phone,linkedin,location,industry,score,notes
|
|
173
|
+
"Acme Corp","https://acme.com","Jane Smith","CEO","jane@acme.com","+1-555-0123","linkedin.com/in/janesmith","Austin, TX","SaaS",8,"Series A funded Q1 2026"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Summary: Markdown report**
|
|
177
|
+
|
|
178
|
+
```markdown
|
|
179
|
+
# Lead Generation Report
|
|
180
|
+
|
|
181
|
+
**Client:** {name}
|
|
182
|
+
**ICP:** {industry} | {size} | {geo}
|
|
183
|
+
**Date:** {date}
|
|
184
|
+
**Leads Delivered:** {count}
|
|
185
|
+
|
|
186
|
+
## Score Distribution
|
|
187
|
+
- Hot (8-10): {count} leads
|
|
188
|
+
- Warm (6-7): {count} leads
|
|
189
|
+
- Cool (4-5): {count} leads
|
|
190
|
+
|
|
191
|
+
## Top 5 Leads
|
|
192
|
+
1. **{company}** - {contact} ({title}) - Score: {score}
|
|
193
|
+
{one-line why this is a top lead}
|
|
194
|
+
2. ...
|
|
195
|
+
|
|
196
|
+
## Recommended Outreach Sequence
|
|
197
|
+
1. Day 1: Personalized cold email referencing {signal}
|
|
198
|
+
2. Day 3: LinkedIn connection request with note
|
|
199
|
+
3. Day 7: Follow-up email with value-add content
|
|
200
|
+
4. Day 14: Final touchpoint, offer call
|
|
201
|
+
|
|
202
|
+
## Methodology
|
|
203
|
+
{Brief description of sources used and verification approach}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Ethical Guidelines
|
|
207
|
+
|
|
208
|
+
These are non-negotiable rules:
|
|
209
|
+
|
|
210
|
+
1. **No scraping personal data** from platforms that prohibit it in their ToS.
|
|
211
|
+
2. **Business emails only** - Never collect personal email addresses.
|
|
212
|
+
3. **No deception** - Never impersonate someone to get contact info.
|
|
213
|
+
4. **Respect opt-outs** - If a company has a "do not contact" notice, skip them.
|
|
214
|
+
5. **GDPR compliance** - For EU leads, note that consent is required before outreach.
|
|
215
|
+
6. **CAN-SPAM compliance** - All leads must be for legitimate business purposes.
|
|
216
|
+
7. **Data minimization** - Only collect what is needed for outreach.
|
|
217
|
+
8. **Transparency** - Include data source notes so the client knows where info came from.
|
|
218
|
+
|
|
219
|
+
## Quality Standards
|
|
220
|
+
|
|
221
|
+
- Minimum 80% email verification rate (verified + likely).
|
|
222
|
+
- No duplicate companies in the same list.
|
|
223
|
+
- No leads from the client's exclusion list.
|
|
224
|
+
- Every lead must have at least: company name, website, contact name, title, email.
|
|
225
|
+
- Average score of delivered list must be >= 6.0.
|
|
226
|
+
- Hot leads must represent at least 20% of the list.
|
|
227
|
+
|
|
228
|
+
## Script Usage
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Generate leads using the scraper script
|
|
232
|
+
node scripts/scraper.js --query "SaaS companies Austin Texas" --count 25 --output leads.json
|
|
233
|
+
|
|
234
|
+
# With ICP filters
|
|
235
|
+
node scripts/scraper.js --query "e-commerce startups" --count 50 --industry ecommerce --size "10-50"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Example Commands
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Generate a starter lead list
|
|
242
|
+
cashclaw leads --icp "saas,10-50,US" --count 25 --output leads.json
|
|
243
|
+
|
|
244
|
+
# Generate with specific titles
|
|
245
|
+
cashclaw leads --icp "ecommerce,50-200,EU" --titles "CEO,CMO" --count 50
|
|
246
|
+
```
|