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,290 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ import { loadConfig, saveConfig, getDefaultConfig, ensureConfigDir } from '../utils/config.js';
6
+ import { detectOpenClaw, installSkills, listAvailableSkills } from '../../integrations/openclaw-bridge.js';
7
+ import { registerAgent } from '../../integrations/hyrve-bridge.js';
8
+
9
+ const orange = chalk.hex('#FF6B35');
10
+ const green = chalk.hex('#16C784');
11
+ const dim = chalk.dim;
12
+
13
+ const SERVICE_MAP = {
14
+ seo_audit: { label: 'SEO Audit', skills: ['cashclaw-seo-auditor'] },
15
+ content_writing: { label: 'Content Writing', skills: ['cashclaw-content-writer'] },
16
+ lead_generation: { label: 'Lead Generation', skills: ['cashclaw-lead-generator'] },
17
+ whatsapp_management: { label: 'WhatsApp Management', skills: ['cashclaw-whatsapp-manager'] },
18
+ social_media: { label: 'Social Media', skills: ['cashclaw-social-media'] },
19
+ };
20
+
21
+ export async function runInit() {
22
+ console.log(orange.bold('\n CashClaw Setup Wizard\n'));
23
+ console.log(dim(' Let\'s configure your AI agent as a freelance business.\n'));
24
+
25
+ const config = getDefaultConfig();
26
+
27
+ // ─── Step 1: Agent Details ───────────────────────────────────────────
28
+ console.log(orange(' Step 1/5: ') + chalk.bold('Agent Details\n'));
29
+
30
+ const step1 = await inquirer.prompt([
31
+ {
32
+ type: 'input',
33
+ name: 'agent_name',
34
+ message: 'Agent name:',
35
+ default: 'MyCashClaw',
36
+ validate: (v) => (v.trim().length > 0 ? true : 'Agent name is required'),
37
+ },
38
+ {
39
+ type: 'input',
40
+ name: 'owner',
41
+ message: 'Your name (owner):',
42
+ default: '',
43
+ },
44
+ {
45
+ type: 'input',
46
+ name: 'email',
47
+ message: 'Contact email:',
48
+ default: '',
49
+ validate: (v) => {
50
+ if (!v) return true; // optional
51
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? true : 'Enter a valid email';
52
+ },
53
+ },
54
+ {
55
+ type: 'list',
56
+ name: 'currency',
57
+ message: 'Preferred currency:',
58
+ choices: [
59
+ { name: 'USD ($)', value: 'USD' },
60
+ { name: 'EUR (€)', value: 'EUR' },
61
+ { name: 'GBP (£)', value: 'GBP' },
62
+ { name: 'TRY (₺)', value: 'TRY' },
63
+ ],
64
+ default: 'USD',
65
+ },
66
+ ]);
67
+
68
+ config.agent.name = step1.agent_name.trim();
69
+ config.agent.owner = step1.owner.trim();
70
+ config.agent.email = step1.email.trim();
71
+ config.agent.currency = step1.currency;
72
+ config.agent.created_at = new Date().toISOString();
73
+
74
+ // ─── Step 2: Stripe ──────────────────────────────────────────────────
75
+ console.log(orange('\n Step 2/5: ') + chalk.bold('Payment Setup (Stripe)\n'));
76
+ console.log(dim(' Stripe enables you to accept payments. You can skip this and add it later.\n'));
77
+
78
+ const step2 = await inquirer.prompt([
79
+ {
80
+ type: 'confirm',
81
+ name: 'setup_stripe',
82
+ message: 'Configure Stripe now?',
83
+ default: false,
84
+ },
85
+ ]);
86
+
87
+ if (step2.setup_stripe) {
88
+ const stripeAnswers = await inquirer.prompt([
89
+ {
90
+ type: 'password',
91
+ name: 'secret_key',
92
+ message: 'Stripe Secret Key (sk_test_... or sk_live_...):',
93
+ mask: '*',
94
+ validate: (v) => {
95
+ if (!v) return 'Stripe key is required when setting up';
96
+ if (!v.startsWith('sk_test_') && !v.startsWith('sk_live_')) {
97
+ return 'Key must start with sk_test_ or sk_live_';
98
+ }
99
+ return true;
100
+ },
101
+ },
102
+ ]);
103
+
104
+ config.stripe.secret_key = stripeAnswers.secret_key;
105
+ config.stripe.connected = true;
106
+ config.stripe.mode = stripeAnswers.secret_key.startsWith('sk_live_') ? 'live' : 'test';
107
+ console.log(green('\n Stripe configured in ' + config.stripe.mode + ' mode.\n'));
108
+ } else {
109
+ console.log(dim('\n Skipped. Run "cashclaw config set stripe.secret_key <key>" later.\n'));
110
+ }
111
+
112
+ // ─── Step 3: Services ────────────────────────────────────────────────
113
+ console.log(orange('\n Step 3/5: ') + chalk.bold('Select Services\n'));
114
+ console.log(dim(' Choose which services your agent will offer:\n'));
115
+
116
+ const step3 = await inquirer.prompt([
117
+ {
118
+ type: 'checkbox',
119
+ name: 'services',
120
+ message: 'Enable services:',
121
+ choices: [
122
+ { name: 'SEO Audit - Automated site audits ($9-$59)', value: 'seo_audit', checked: true },
123
+ { name: 'Content Writing - Blog posts & newsletters ($5-$12)', value: 'content_writing', checked: true },
124
+ { name: 'Lead Generation - Targeted lead lists ($9-$25)', value: 'lead_generation' },
125
+ { name: 'WhatsApp Mgmt - Setup & automation ($19-$49)', value: 'whatsapp_management' },
126
+ { name: 'Social Media - Content & scheduling ($9-$49)', value: 'social_media' },
127
+ ],
128
+ validate: (v) => (v.length > 0 ? true : 'Select at least one service'),
129
+ },
130
+ ]);
131
+
132
+ for (const svcKey of step3.services) {
133
+ config.services[svcKey].enabled = true;
134
+ }
135
+
136
+ // ─── Step 4: Pricing ─────────────────────────────────────────────────
137
+ console.log(orange('\n Step 4/5: ') + chalk.bold('Pricing\n'));
138
+ console.log(dim(' Default prices are shown. Press Enter to keep defaults.\n'));
139
+
140
+ for (const svcKey of step3.services) {
141
+ const svc = config.services[svcKey];
142
+ const label = SERVICE_MAP[svcKey]?.label || svcKey;
143
+ console.log(orange(`\n ${label} Pricing:`));
144
+
145
+ const pricingQuestions = Object.entries(svc.pricing).map(([tier, defaultPrice]) => ({
146
+ type: 'input',
147
+ name: tier,
148
+ message: ` ${formatTierName(tier)}:`,
149
+ default: String(defaultPrice),
150
+ validate: (v) => {
151
+ const num = parseFloat(v);
152
+ if (isNaN(num) || num < 0) return 'Enter a valid price (0 or more)';
153
+ return true;
154
+ },
155
+ filter: (v) => parseFloat(v) || 0,
156
+ }));
157
+
158
+ const pricingAnswers = await inquirer.prompt(pricingQuestions);
159
+
160
+ for (const [tier, price] of Object.entries(pricingAnswers)) {
161
+ config.services[svcKey].pricing[tier] = price;
162
+ }
163
+ }
164
+
165
+ // ─── Step 5: OpenClaw Skills ─────────────────────────────────────────
166
+ console.log(orange('\n Step 5/5: ') + chalk.bold('OpenClaw Integration\n'));
167
+
168
+ const spinner = ora('Detecting OpenClaw workspace...').start();
169
+ const detection = await detectOpenClaw();
170
+
171
+ if (detection.found) {
172
+ spinner.succeed(`OpenClaw found at ${dim(detection.path)}`);
173
+ config.openclaw.workspace = detection.path;
174
+ config.openclaw.skills_dir = detection.skills_dir;
175
+ config.openclaw.auto_detected = true;
176
+
177
+ const skillNames = step3.services
178
+ .flatMap((svcKey) => SERVICE_MAP[svcKey]?.skills || []);
179
+
180
+ // Always include core skills
181
+ const allSkills = ['cashclaw-core', 'cashclaw-invoicer', ...skillNames];
182
+ const uniqueSkills = [...new Set(allSkills)];
183
+
184
+ const installConfirm = await inquirer.prompt([
185
+ {
186
+ type: 'confirm',
187
+ name: 'install',
188
+ message: `Install ${uniqueSkills.length} skills to OpenClaw?`,
189
+ default: true,
190
+ },
191
+ ]);
192
+
193
+ if (installConfirm.install) {
194
+ const installSpinner = ora('Installing skills...').start();
195
+ const result = await installSkills(uniqueSkills);
196
+ if (result.installed.length > 0) {
197
+ installSpinner.succeed(`Installed ${result.installed.length} skill(s)`);
198
+ for (const name of result.installed) {
199
+ console.log(green(` + ${name}`));
200
+ }
201
+ }
202
+ if (result.failed.length > 0) {
203
+ for (const f of result.failed) {
204
+ console.log(chalk.yellow(` ! ${f.name}: ${f.error}`));
205
+ }
206
+ }
207
+ }
208
+ } else {
209
+ spinner.warn('OpenClaw workspace not detected');
210
+ console.log(dim(' Skills will be installed when OpenClaw is set up.\n'));
211
+ console.log(dim(' Expected locations:'));
212
+ for (const p of detection.paths_checked) {
213
+ console.log(dim(` - ${p}`));
214
+ }
215
+ }
216
+
217
+ // ─── Save Config ─────────────────────────────────────────────────────
218
+ const saveSpinner = ora('Saving configuration...').start();
219
+ const configPath = await saveConfig(config);
220
+ saveSpinner.succeed(`Config saved to ${dim(configPath)}`);
221
+
222
+ // ─── Try HYRVE Registration ──────────────────────────────────────────
223
+ const hyrveSpinner = ora('Registering with HYRVEai marketplace...').start();
224
+ const hyrveResult = await registerAgent(config);
225
+ if (hyrveResult.success) {
226
+ config.hyrve.registered = true;
227
+ config.hyrve.agent_id = hyrveResult.agent_id;
228
+ await saveConfig(config);
229
+ hyrveSpinner.succeed('Registered with HYRVEai');
230
+ } else {
231
+ hyrveSpinner.info(hyrveResult.message);
232
+ }
233
+
234
+ // ─── Summary ─────────────────────────────────────────────────────────
235
+ console.log(orange.bold('\n Setup Complete!\n'));
236
+
237
+ const table = new Table({
238
+ chars: {
239
+ top: '-', 'top-mid': '+', 'top-left': '+', 'top-right': '+',
240
+ bottom: '-', 'bottom-mid': '+', 'bottom-left': '+', 'bottom-right': '+',
241
+ left: '|', 'left-mid': '+', mid: '-', 'mid-mid': '+',
242
+ right: '|', 'right-mid': '+', middle: '|',
243
+ },
244
+ colWidths: [22, 40],
245
+ });
246
+
247
+ table.push(
248
+ [orange('Agent Name'), config.agent.name],
249
+ [orange('Owner'), config.agent.owner || dim('not set')],
250
+ [orange('Email'), config.agent.email || dim('not set')],
251
+ [orange('Currency'), config.agent.currency],
252
+ [orange('Stripe'), config.stripe.connected ? green(`Connected (${config.stripe.mode})`) : chalk.yellow('Not configured')],
253
+ [orange('Services'), step3.services.map((s) => SERVICE_MAP[s]?.label || s).join(', ')],
254
+ [orange('OpenClaw'), detection.found ? green('Detected') : chalk.yellow('Not found')],
255
+ [orange('HYRVEai'), hyrveResult.success ? green('Registered') : dim('Pending')],
256
+ [orange('Dashboard'), dim(`http://localhost:${config.server.port}`)],
257
+ );
258
+
259
+ console.log(table.toString());
260
+
261
+ console.log(`
262
+ ${orange('Next steps:')}
263
+ ${dim('1.')} Run ${chalk.bold('cashclaw status')} to see your agent status
264
+ ${dim('2.')} Run ${chalk.bold('cashclaw dashboard')} to open the web dashboard
265
+ ${dim('3.')} Run ${chalk.bold('cashclaw missions')} to manage client work
266
+ `);
267
+ }
268
+
269
+ /**
270
+ * Format tier names for display: "post_500" -> "$500 Post", "basic" -> "Basic"
271
+ */
272
+ function formatTierName(tier) {
273
+ const map = {
274
+ basic: 'Basic',
275
+ standard: 'Standard',
276
+ pro: 'Pro',
277
+ post_500: '500-word Post',
278
+ post_1500: '1500-word Post',
279
+ newsletter: 'Newsletter',
280
+ starter_25: 'Starter (25 leads)',
281
+ standard_50: 'Standard (50 leads)',
282
+ pro_100: 'Pro (100 leads)',
283
+ setup: 'Setup',
284
+ monthly: 'Monthly',
285
+ weekly_1: '1 post/week',
286
+ weekly_3: '3 posts/week',
287
+ monthly_full: 'Full monthly',
288
+ };
289
+ return map[tier] || tier.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
290
+ }
@@ -0,0 +1,174 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { loadConfig } from '../utils/config.js';
4
+ import { getMissionStats } from '../../engine/mission-runner.js';
5
+ import { getTotal, getMonthly, getWeekly, getToday } from '../../engine/earnings-tracker.js';
6
+ import { listInstalledSkills } from '../../integrations/openclaw-bridge.js';
7
+ import { showMiniBanner } from '../utils/banner.js';
8
+
9
+ const orange = chalk.hex('#FF6B35');
10
+ const green = chalk.hex('#16C784');
11
+ const dim = chalk.dim;
12
+
13
+ export async function runStatus() {
14
+ showMiniBanner();
15
+
16
+ const config = await loadConfig();
17
+
18
+ // Check if initialized
19
+ if (!config.agent.name || config.agent.name === 'MyCashClaw') {
20
+ const isDefault = !config.agent.owner && !config.agent.email;
21
+ if (isDefault) {
22
+ console.log(chalk.yellow(' Agent not initialized. Run "cashclaw init" first.\n'));
23
+ return;
24
+ }
25
+ }
26
+
27
+ // ─── Agent Info ──────────────────────────────────────────────────────
28
+ const infoTable = new Table({
29
+ chars: { top: '', 'top-mid': '', 'top-left': '', 'top-right': '',
30
+ bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
31
+ left: ' ', 'left-mid': '', mid: '', 'mid-mid': '',
32
+ right: '', 'right-mid': '', middle: ' ' },
33
+ colWidths: [20, 42],
34
+ style: { 'padding-left': 0, 'padding-right': 0 },
35
+ });
36
+
37
+ infoTable.push(
38
+ [orange.bold('Agent'), chalk.bold(config.agent.name)],
39
+ [orange('Owner'), config.agent.owner || dim('not set')],
40
+ [orange('Email'), config.agent.email || dim('not set')],
41
+ [orange('Currency'), config.agent.currency],
42
+ [orange('Created'), config.agent.created_at ? new Date(config.agent.created_at).toLocaleDateString() : dim('unknown')],
43
+ );
44
+
45
+ console.log(infoTable.toString());
46
+ console.log();
47
+
48
+ // ─── Services ────────────────────────────────────────────────────────
49
+ console.log(orange.bold(' Services'));
50
+ console.log();
51
+
52
+ const svcTable = new Table({
53
+ head: [dim('Service'), dim('Status'), dim('Pricing')],
54
+ chars: { top: '', 'top-mid': '', 'top-left': ' ', 'top-right': '',
55
+ bottom: '', 'bottom-mid': '', 'bottom-left': ' ', 'bottom-right': '',
56
+ left: ' ', 'left-mid': ' ', mid: '-', 'mid-mid': '+',
57
+ right: '', 'right-mid': '', middle: ' | ' },
58
+ colWidths: [22, 12, 28],
59
+ style: { head: [], 'padding-left': 0, 'padding-right': 0 },
60
+ });
61
+
62
+ const serviceLabels = {
63
+ seo_audit: 'SEO Audit',
64
+ content_writing: 'Content Writing',
65
+ lead_generation: 'Lead Generation',
66
+ whatsapp_management: 'WhatsApp Mgmt',
67
+ social_media: 'Social Media',
68
+ };
69
+
70
+ const currencySymbol = { USD: '$', EUR: '€', GBP: '£', TRY: '₺' }[config.agent.currency] || '$';
71
+
72
+ for (const [key, svc] of Object.entries(config.services)) {
73
+ const label = serviceLabels[key] || key;
74
+ const status = svc.enabled ? green('active') : dim('off');
75
+ const prices = svc.enabled
76
+ ? Object.values(svc.pricing).map((p) => `${currencySymbol}${p}`).join(', ')
77
+ : dim('-');
78
+ svcTable.push([label, status, prices]);
79
+ }
80
+
81
+ console.log(svcTable.toString());
82
+ console.log();
83
+
84
+ // ─── Stripe ──────────────────────────────────────────────────────────
85
+ const stripeStatus = config.stripe.connected
86
+ ? green(`Connected (${config.stripe.mode})`)
87
+ : chalk.yellow('Not configured');
88
+ console.log(` ${orange('Stripe')} ${stripeStatus}`);
89
+ console.log();
90
+
91
+ // ─── Earnings ────────────────────────────────────────────────────────
92
+ console.log(orange.bold(' Earnings'));
93
+ console.log();
94
+
95
+ try {
96
+ const [total, monthly, weekly, today] = await Promise.all([
97
+ getTotal(),
98
+ getMonthly(),
99
+ getWeekly(),
100
+ getToday(),
101
+ ]);
102
+
103
+ const earnTable = new Table({
104
+ chars: { top: '', 'top-mid': '', 'top-left': ' ', 'top-right': '',
105
+ bottom: '', 'bottom-mid': '', 'bottom-left': ' ', 'bottom-right': '',
106
+ left: ' ', 'left-mid': ' ', mid: '', 'mid-mid': '',
107
+ right: '', 'right-mid': '', middle: ' ' },
108
+ colWidths: [20, 18],
109
+ style: { 'padding-left': 0, 'padding-right': 0 },
110
+ });
111
+
112
+ earnTable.push(
113
+ [dim('Total'), green.bold(`${currencySymbol}${total.toFixed(2)}`)],
114
+ [dim('This Month'), `${currencySymbol}${monthly.total.toFixed(2)} (${monthly.count} jobs)`],
115
+ [dim('This Week'), `${currencySymbol}${weekly.total.toFixed(2)} (${weekly.count} jobs)`],
116
+ [dim('Today'), `${currencySymbol}${today.total.toFixed(2)} (${today.count} jobs)`],
117
+ );
118
+
119
+ console.log(earnTable.toString());
120
+ } catch {
121
+ console.log(dim(' No earnings data yet.'));
122
+ }
123
+ console.log();
124
+
125
+ // ─── Missions ────────────────────────────────────────────────────────
126
+ console.log(orange.bold(' Missions'));
127
+ console.log();
128
+
129
+ try {
130
+ const stats = await getMissionStats();
131
+
132
+ const missionTable = new Table({
133
+ chars: { top: '', 'top-mid': '', 'top-left': ' ', 'top-right': '',
134
+ bottom: '', 'bottom-mid': '', 'bottom-left': ' ', 'bottom-right': '',
135
+ left: ' ', 'left-mid': ' ', mid: '', 'mid-mid': '',
136
+ right: '', 'right-mid': '', middle: ' ' },
137
+ colWidths: [20, 12],
138
+ style: { 'padding-left': 0, 'padding-right': 0 },
139
+ });
140
+
141
+ missionTable.push(
142
+ [dim('Total'), String(stats.total)],
143
+ [dim('In Progress'), chalk.yellow(String(stats.in_progress))],
144
+ [dim('Completed'), green(String(stats.completed))],
145
+ [dim('Total Value'), `${currencySymbol}${stats.total_value.toFixed(2)}`],
146
+ );
147
+
148
+ console.log(missionTable.toString());
149
+ } catch {
150
+ console.log(dim(' No missions yet.'));
151
+ }
152
+ console.log();
153
+
154
+ // ─── Installed Skills ────────────────────────────────────────────────
155
+ console.log(orange.bold(' Skills'));
156
+ console.log();
157
+
158
+ try {
159
+ const installed = await listInstalledSkills(config.openclaw?.skills_dir);
160
+ if (installed.length > 0) {
161
+ for (const skill of installed) {
162
+ console.log(` ${green('+')} ${skill}`);
163
+ }
164
+ } else {
165
+ console.log(dim(' No CashClaw skills installed.'));
166
+ }
167
+ } catch {
168
+ console.log(dim(' Could not check installed skills.'));
169
+ }
170
+
171
+ console.log();
172
+ console.log(dim(` Dashboard: http://localhost:${config.server.port}`));
173
+ console.log();
174
+ }