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,464 @@
1
+ /* ────────────────────────────────────────────────────────────────
2
+ CashClaw Dashboard - Dark Theme
3
+ Colors: #1A1A2E bg, #FF6B35 orange, #16C784 green
4
+ ──────────────────────────────────────────────────────────────── */
5
+
6
+ :root {
7
+ --bg-primary: #1A1A2E;
8
+ --bg-secondary: #16213E;
9
+ --bg-card: #1E2A45;
10
+ --bg-hover: #253555;
11
+ --orange: #FF6B35;
12
+ --green: #16C784;
13
+ --red: #E74C3C;
14
+ --yellow: #F39C12;
15
+ --blue: #3498DB;
16
+ --text-primary: #E8E8E8;
17
+ --text-secondary: #8892A4;
18
+ --text-muted: #5A6678;
19
+ --border: #2A3A5C;
20
+ --shadow: rgba(0, 0, 0, 0.3);
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
31
+ background-color: var(--bg-primary);
32
+ color: var(--text-primary);
33
+ min-height: 100vh;
34
+ line-height: 1.5;
35
+ }
36
+
37
+ #app {
38
+ max-width: 1280px;
39
+ margin: 0 auto;
40
+ padding: 0 20px;
41
+ }
42
+
43
+ /* ─── Header ─────────────────────────────────────────────────────── */
44
+
45
+ .header {
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ padding: 20px 0;
50
+ border-bottom: 1px solid var(--border);
51
+ margin-bottom: 24px;
52
+ }
53
+
54
+ .header-left {
55
+ display: flex;
56
+ align-items: baseline;
57
+ gap: 10px;
58
+ }
59
+
60
+ .logo {
61
+ font-size: 24px;
62
+ font-weight: 800;
63
+ color: var(--orange);
64
+ letter-spacing: -0.5px;
65
+ }
66
+
67
+ .version {
68
+ font-size: 12px;
69
+ color: var(--text-muted);
70
+ }
71
+
72
+ .header-right {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 10px;
76
+ }
77
+
78
+ .agent-name {
79
+ font-size: 14px;
80
+ color: var(--text-secondary);
81
+ font-weight: 500;
82
+ }
83
+
84
+ .status-dot {
85
+ width: 8px;
86
+ height: 8px;
87
+ border-radius: 50%;
88
+ background-color: var(--green);
89
+ display: inline-block;
90
+ }
91
+
92
+ .status-dot.offline {
93
+ background-color: var(--red);
94
+ }
95
+
96
+ /* ─── Cards ──────────────────────────────────────────────────────── */
97
+
98
+ .cards {
99
+ display: grid;
100
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
101
+ gap: 16px;
102
+ margin-bottom: 24px;
103
+ }
104
+
105
+ .card {
106
+ background: var(--bg-card);
107
+ border-radius: 12px;
108
+ padding: 20px 24px;
109
+ border: 1px solid var(--border);
110
+ transition: border-color 0.2s;
111
+ }
112
+
113
+ .card:hover {
114
+ border-color: var(--orange);
115
+ }
116
+
117
+ .card-label {
118
+ font-size: 12px;
119
+ color: var(--text-muted);
120
+ text-transform: uppercase;
121
+ letter-spacing: 0.5px;
122
+ margin-bottom: 6px;
123
+ }
124
+
125
+ .card-value {
126
+ font-size: 28px;
127
+ font-weight: 700;
128
+ color: var(--text-primary);
129
+ }
130
+
131
+ .card-total .card-value {
132
+ color: var(--green);
133
+ }
134
+
135
+ .card-sub {
136
+ font-size: 12px;
137
+ color: var(--text-secondary);
138
+ margin-top: 4px;
139
+ }
140
+
141
+ /* ─── Main Grid ──────────────────────────────────────────────────── */
142
+
143
+ .main-grid {
144
+ display: grid;
145
+ grid-template-columns: 1fr 1fr;
146
+ gap: 20px;
147
+ margin-bottom: 24px;
148
+ }
149
+
150
+ .chart-panel {
151
+ grid-column: 1 / -1;
152
+ }
153
+
154
+ /* ─── Panels ─────────────────────────────────────────────────────── */
155
+
156
+ .panel {
157
+ background: var(--bg-card);
158
+ border-radius: 12px;
159
+ padding: 20px 24px;
160
+ border: 1px solid var(--border);
161
+ }
162
+
163
+ .panel-title {
164
+ font-size: 14px;
165
+ font-weight: 600;
166
+ color: var(--text-secondary);
167
+ text-transform: uppercase;
168
+ letter-spacing: 0.5px;
169
+ margin-bottom: 16px;
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 8px;
173
+ }
174
+
175
+ .badge {
176
+ background: var(--orange);
177
+ color: white;
178
+ font-size: 11px;
179
+ font-weight: 700;
180
+ padding: 2px 8px;
181
+ border-radius: 10px;
182
+ }
183
+
184
+ /* ─── Chart ──────────────────────────────────────────────────────── */
185
+
186
+ .chart-container {
187
+ width: 100%;
188
+ height: 200px;
189
+ position: relative;
190
+ }
191
+
192
+ .chart-container canvas {
193
+ width: 100% !important;
194
+ height: 100% !important;
195
+ }
196
+
197
+ /* ─── Missions List ──────────────────────────────────────────────── */
198
+
199
+ .missions-list {
200
+ max-height: 320px;
201
+ overflow-y: auto;
202
+ }
203
+
204
+ .mission-item {
205
+ display: flex;
206
+ justify-content: space-between;
207
+ align-items: center;
208
+ padding: 10px 0;
209
+ border-bottom: 1px solid var(--border);
210
+ }
211
+
212
+ .mission-item:last-child {
213
+ border-bottom: none;
214
+ }
215
+
216
+ .mission-info {
217
+ flex: 1;
218
+ }
219
+
220
+ .mission-name {
221
+ font-size: 14px;
222
+ font-weight: 500;
223
+ color: var(--text-primary);
224
+ }
225
+
226
+ .mission-meta {
227
+ font-size: 12px;
228
+ color: var(--text-muted);
229
+ margin-top: 2px;
230
+ }
231
+
232
+ .mission-price {
233
+ font-size: 16px;
234
+ font-weight: 700;
235
+ color: var(--green);
236
+ margin-left: 16px;
237
+ }
238
+
239
+ .mission-status {
240
+ font-size: 11px;
241
+ font-weight: 600;
242
+ padding: 3px 10px;
243
+ border-radius: 12px;
244
+ margin-left: 12px;
245
+ text-transform: uppercase;
246
+ }
247
+
248
+ .status-created { background: var(--blue); color: white; }
249
+ .status-in_progress { background: var(--yellow); color: #1a1a2e; }
250
+ .status-completed { background: var(--green); color: #1a1a2e; }
251
+ .status-cancelled { background: var(--red); color: white; }
252
+ .status-paid { background: var(--green); color: #1a1a2e; }
253
+
254
+ /* ─── Services List ──────────────────────────────────────────────── */
255
+
256
+ .service-item {
257
+ display: flex;
258
+ justify-content: space-between;
259
+ align-items: center;
260
+ padding: 10px 0;
261
+ border-bottom: 1px solid var(--border);
262
+ }
263
+
264
+ .service-item:last-child {
265
+ border-bottom: none;
266
+ }
267
+
268
+ .service-name {
269
+ font-size: 14px;
270
+ font-weight: 500;
271
+ }
272
+
273
+ .service-prices {
274
+ font-size: 12px;
275
+ color: var(--text-secondary);
276
+ }
277
+
278
+ /* ─── Skills List ────────────────────────────────────────────────── */
279
+
280
+ .skill-item {
281
+ display: flex;
282
+ justify-content: space-between;
283
+ align-items: center;
284
+ padding: 8px 0;
285
+ border-bottom: 1px solid var(--border);
286
+ }
287
+
288
+ .skill-item:last-child {
289
+ border-bottom: none;
290
+ }
291
+
292
+ .skill-name {
293
+ font-size: 13px;
294
+ font-weight: 500;
295
+ font-family: monospace;
296
+ }
297
+
298
+ .skill-badge {
299
+ font-size: 11px;
300
+ padding: 2px 8px;
301
+ border-radius: 10px;
302
+ font-weight: 600;
303
+ }
304
+
305
+ .skill-installed {
306
+ background: var(--green);
307
+ color: #1a1a2e;
308
+ }
309
+
310
+ .skill-available {
311
+ background: var(--border);
312
+ color: var(--text-secondary);
313
+ }
314
+
315
+ /* ─── Recent Earnings ────────────────────────────────────────────── */
316
+
317
+ .recent-list {
318
+ max-height: 320px;
319
+ overflow-y: auto;
320
+ }
321
+
322
+ .recent-item {
323
+ display: flex;
324
+ justify-content: space-between;
325
+ align-items: center;
326
+ padding: 8px 0;
327
+ border-bottom: 1px solid var(--border);
328
+ }
329
+
330
+ .recent-item:last-child {
331
+ border-bottom: none;
332
+ }
333
+
334
+ .recent-info {
335
+ flex: 1;
336
+ }
337
+
338
+ .recent-service {
339
+ font-size: 13px;
340
+ color: var(--text-primary);
341
+ }
342
+
343
+ .recent-date {
344
+ font-size: 11px;
345
+ color: var(--text-muted);
346
+ }
347
+
348
+ .recent-amount {
349
+ font-size: 14px;
350
+ font-weight: 700;
351
+ color: var(--green);
352
+ }
353
+
354
+ /* ─── Agent Info ─────────────────────────────────────────────────── */
355
+
356
+ .info-row {
357
+ display: flex;
358
+ justify-content: space-between;
359
+ padding: 8px 0;
360
+ border-bottom: 1px solid var(--border);
361
+ }
362
+
363
+ .info-row:last-child {
364
+ border-bottom: none;
365
+ }
366
+
367
+ .info-label {
368
+ font-size: 13px;
369
+ color: var(--text-muted);
370
+ }
371
+
372
+ .info-value {
373
+ font-size: 13px;
374
+ color: var(--text-primary);
375
+ font-weight: 500;
376
+ }
377
+
378
+ .info-value.connected {
379
+ color: var(--green);
380
+ }
381
+
382
+ .info-value.disconnected {
383
+ color: var(--yellow);
384
+ }
385
+
386
+ /* ─── Empty State ────────────────────────────────────────────────── */
387
+
388
+ .empty-state {
389
+ text-align: center;
390
+ padding: 24px;
391
+ color: var(--text-muted);
392
+ font-size: 13px;
393
+ }
394
+
395
+ /* ─── Footer ─────────────────────────────────────────────────────── */
396
+
397
+ .footer {
398
+ text-align: center;
399
+ padding: 20px 0;
400
+ border-top: 1px solid var(--border);
401
+ font-size: 12px;
402
+ color: var(--text-muted);
403
+ }
404
+
405
+ .footer a {
406
+ color: var(--orange);
407
+ text-decoration: none;
408
+ }
409
+
410
+ .footer a:hover {
411
+ text-decoration: underline;
412
+ }
413
+
414
+ .footer-sep {
415
+ margin: 0 8px;
416
+ opacity: 0.4;
417
+ }
418
+
419
+ /* ─── Scrollbar ──────────────────────────────────────────────────── */
420
+
421
+ ::-webkit-scrollbar {
422
+ width: 6px;
423
+ }
424
+
425
+ ::-webkit-scrollbar-track {
426
+ background: var(--bg-secondary);
427
+ }
428
+
429
+ ::-webkit-scrollbar-thumb {
430
+ background: var(--border);
431
+ border-radius: 3px;
432
+ }
433
+
434
+ ::-webkit-scrollbar-thumb:hover {
435
+ background: var(--text-muted);
436
+ }
437
+
438
+ /* ─── Responsive ─────────────────────────────────────────────────── */
439
+
440
+ @media (max-width: 768px) {
441
+ .main-grid {
442
+ grid-template-columns: 1fr;
443
+ }
444
+
445
+ .cards {
446
+ grid-template-columns: repeat(2, 1fr);
447
+ }
448
+
449
+ .card-value {
450
+ font-size: 22px;
451
+ }
452
+ }
453
+
454
+ @media (max-width: 480px) {
455
+ .cards {
456
+ grid-template-columns: 1fr;
457
+ }
458
+
459
+ .header {
460
+ flex-direction: column;
461
+ align-items: flex-start;
462
+ gap: 8px;
463
+ }
464
+ }
@@ -0,0 +1,224 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { loadConfig, saveConfig } from '../cli/utils/config.js';
5
+ import { listMissions, getMissionStats } from '../engine/mission-runner.js';
6
+ import { getTotal, getMonthly, getWeekly, getToday, getHistory, getByService, getDailyTotals } from '../engine/earnings-tracker.js';
7
+ import { listInstalledSkills, listAvailableSkills } from '../integrations/openclaw-bridge.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ /**
13
+ * Create and configure the Express dashboard server.
14
+ * @returns {express.Application}
15
+ */
16
+ export function createDashboardServer() {
17
+ const app = express();
18
+
19
+ // Middleware
20
+ app.use(express.json());
21
+ app.use(express.urlencoded({ extended: false }));
22
+
23
+ // CORS for local development
24
+ app.use((req, res, next) => {
25
+ res.header('Access-Control-Allow-Origin', '*');
26
+ res.header('Access-Control-Allow-Headers', 'Content-Type');
27
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
28
+ if (req.method === 'OPTIONS') {
29
+ return res.sendStatus(200);
30
+ }
31
+ next();
32
+ });
33
+
34
+ // Serve static files from public/
35
+ const publicDir = path.join(__dirname, 'public');
36
+ app.use(express.static(publicDir));
37
+
38
+ // ─── API Routes ────────────────────────────────────────────────────
39
+
40
+ /**
41
+ * GET /api/status
42
+ * Returns agent status, config summary, and overall stats.
43
+ */
44
+ app.get('/api/status', async (req, res) => {
45
+ try {
46
+ const config = await loadConfig();
47
+ const missionStats = await getMissionStats();
48
+ const total = await getTotal();
49
+ const monthly = await getMonthly();
50
+ const today = await getToday();
51
+
52
+ const enabledServices = Object.entries(config.services || {})
53
+ .filter(([_, svc]) => svc.enabled)
54
+ .map(([key, svc]) => ({
55
+ type: key,
56
+ pricing: svc.pricing,
57
+ description: svc.description,
58
+ }));
59
+
60
+ res.json({
61
+ agent: config.agent,
62
+ stripe: {
63
+ connected: config.stripe?.connected || false,
64
+ mode: config.stripe?.mode || 'test',
65
+ },
66
+ services: enabledServices,
67
+ services_count: enabledServices.length,
68
+ missions: missionStats,
69
+ earnings: {
70
+ total,
71
+ monthly: monthly.total,
72
+ monthly_count: monthly.count,
73
+ today: today.total,
74
+ today_count: today.count,
75
+ },
76
+ server: config.server,
77
+ hyrve: {
78
+ registered: config.hyrve?.registered || false,
79
+ agent_id: config.hyrve?.agent_id || null,
80
+ },
81
+ openclaw: {
82
+ workspace: config.openclaw?.workspace || null,
83
+ auto_detected: config.openclaw?.auto_detected || false,
84
+ },
85
+ });
86
+ } catch (err) {
87
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
88
+ }
89
+ });
90
+
91
+ /**
92
+ * GET /api/missions
93
+ * Returns list of all missions with optional status filter.
94
+ */
95
+ app.get('/api/missions', async (req, res) => {
96
+ try {
97
+ const statusFilter = req.query.status || null;
98
+ const missions = await listMissions(statusFilter);
99
+ const stats = await getMissionStats();
100
+
101
+ res.json({
102
+ missions,
103
+ stats,
104
+ total: missions.length,
105
+ });
106
+ } catch (err) {
107
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
108
+ }
109
+ });
110
+
111
+ /**
112
+ * GET /api/earnings
113
+ * Returns earnings summary, history, breakdown by service, and daily totals.
114
+ */
115
+ app.get('/api/earnings', async (req, res) => {
116
+ try {
117
+ const limit = parseInt(req.query.limit) || 50;
118
+ const days = parseInt(req.query.days) || 30;
119
+
120
+ const [total, monthly, weekly, today, history, byService, dailyTotals] = await Promise.all([
121
+ getTotal(),
122
+ getMonthly(),
123
+ getWeekly(),
124
+ getToday(),
125
+ getHistory(limit),
126
+ getByService(),
127
+ getDailyTotals(days),
128
+ ]);
129
+
130
+ res.json({
131
+ summary: {
132
+ total,
133
+ monthly: monthly.total,
134
+ monthly_count: monthly.count,
135
+ weekly: weekly.total,
136
+ weekly_count: weekly.count,
137
+ today: today.total,
138
+ today_count: today.count,
139
+ },
140
+ by_service: byService,
141
+ daily_totals: dailyTotals,
142
+ history,
143
+ });
144
+ } catch (err) {
145
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
146
+ }
147
+ });
148
+
149
+ /**
150
+ * GET /api/skills
151
+ * Returns available and installed CashClaw skills.
152
+ */
153
+ app.get('/api/skills', async (req, res) => {
154
+ try {
155
+ const config = await loadConfig();
156
+ const available = await listAvailableSkills();
157
+ const installed = await listInstalledSkills(config.openclaw?.skills_dir);
158
+
159
+ const skills = available.map((s) => ({
160
+ name: s.name,
161
+ installed: installed.includes(s.name),
162
+ has_skill_md: s.has_skill_md,
163
+ }));
164
+
165
+ res.json({
166
+ skills,
167
+ total_available: available.length,
168
+ total_installed: installed.length,
169
+ openclaw_detected: !!config.openclaw?.workspace,
170
+ });
171
+ } catch (err) {
172
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
173
+ }
174
+ });
175
+
176
+ /**
177
+ * POST /api/config
178
+ * Update configuration values.
179
+ * Body: { key: "dot.notation.key", value: "new value" }
180
+ */
181
+ app.post('/api/config', async (req, res) => {
182
+ try {
183
+ const { key, value } = req.body;
184
+
185
+ if (!key) {
186
+ return res.status(400).json({
187
+ error: { code: 'VALIDATION_ERROR', message: 'Key is required' },
188
+ });
189
+ }
190
+
191
+ const config = await loadConfig();
192
+ const keys = key.split('.');
193
+ let obj = config;
194
+ for (let i = 0; i < keys.length - 1; i++) {
195
+ if (!obj[keys[i]] || typeof obj[keys[i]] !== 'object') {
196
+ obj[keys[i]] = {};
197
+ }
198
+ obj = obj[keys[i]];
199
+ }
200
+ obj[keys[keys.length - 1]] = value;
201
+
202
+ await saveConfig(config);
203
+
204
+ res.json({ success: true, key, value });
205
+ } catch (err) {
206
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
207
+ }
208
+ });
209
+
210
+ /**
211
+ * GET /api/health
212
+ * Simple health check endpoint.
213
+ */
214
+ app.get('/api/health', (req, res) => {
215
+ res.json({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString() });
216
+ });
217
+
218
+ // Fallback: serve index.html for SPA routing
219
+ app.get('*', (req, res) => {
220
+ res.sendFile(path.join(publicDir, 'index.html'));
221
+ });
222
+
223
+ return app;
224
+ }