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,329 @@
1
+ /**
2
+ * CashClaw Dashboard - Frontend Application
3
+ * Fetches data from the Express API and renders it dynamically.
4
+ * Auto-refreshes every 30 seconds.
5
+ */
6
+
7
+ const REFRESH_INTERVAL = 30000;
8
+ const CURRENCY_SYMBOLS = { USD: '$', EUR: '\u20ac', GBP: '\u00a3', TRY: '\u20ba' };
9
+
10
+ let currentCurrency = '$';
11
+
12
+ // ─── Initialization ────────────────────────────────────────────────────
13
+
14
+ document.addEventListener('DOMContentLoaded', () => {
15
+ loadAll();
16
+ setInterval(loadAll, REFRESH_INTERVAL);
17
+ });
18
+
19
+ async function loadAll() {
20
+ try {
21
+ await Promise.all([
22
+ loadStatus(),
23
+ loadEarnings(),
24
+ loadMissions(),
25
+ loadSkills(),
26
+ ]);
27
+ document.getElementById('lastUpdated').textContent =
28
+ 'Updated: ' + new Date().toLocaleTimeString();
29
+ document.getElementById('statusDot').classList.remove('offline');
30
+ } catch (err) {
31
+ console.error('Failed to load data:', err);
32
+ document.getElementById('statusDot').classList.add('offline');
33
+ }
34
+ }
35
+
36
+ // ─── Status ────────────────────────────────────────────────────────────
37
+
38
+ async function loadStatus() {
39
+ const res = await fetch('/api/status');
40
+ const data = await res.json();
41
+
42
+ currentCurrency = CURRENCY_SYMBOLS[data.agent?.currency] || '$';
43
+
44
+ // Agent name in header
45
+ document.getElementById('agentName').textContent = data.agent?.name || 'CashClaw';
46
+
47
+ // Agent info panel
48
+ document.getElementById('infoOwner').textContent = data.agent?.owner || '-';
49
+ document.getElementById('infoEmail').textContent = data.agent?.email || '-';
50
+ document.getElementById('infoCurrency').textContent = data.agent?.currency || 'USD';
51
+
52
+ const stripeEl = document.getElementById('infoStripe');
53
+ if (data.stripe?.connected) {
54
+ stripeEl.textContent = 'Connected (' + data.stripe.mode + ')';
55
+ stripeEl.className = 'info-value connected';
56
+ } else {
57
+ stripeEl.textContent = 'Not configured';
58
+ stripeEl.className = 'info-value disconnected';
59
+ }
60
+
61
+ const hyrveEl = document.getElementById('infoHyrve');
62
+ if (data.hyrve?.registered) {
63
+ hyrveEl.textContent = 'Registered';
64
+ hyrveEl.className = 'info-value connected';
65
+ } else {
66
+ hyrveEl.textContent = 'Not registered';
67
+ hyrveEl.className = 'info-value disconnected';
68
+ }
69
+
70
+ const openClawEl = document.getElementById('infoOpenClaw');
71
+ if (data.openclaw?.auto_detected) {
72
+ openClawEl.textContent = 'Detected';
73
+ openClawEl.className = 'info-value connected';
74
+ } else {
75
+ openClawEl.textContent = 'Not found';
76
+ openClawEl.className = 'info-value disconnected';
77
+ }
78
+
79
+ // Services
80
+ renderServices(data.services || []);
81
+ }
82
+
83
+ // ─── Earnings ──────────────────────────────────────────────────────────
84
+
85
+ async function loadEarnings() {
86
+ const res = await fetch('/api/earnings');
87
+ const data = await res.json();
88
+
89
+ const s = data.summary || {};
90
+
91
+ document.getElementById('totalEarned').textContent =
92
+ currentCurrency + (s.total || 0).toFixed(2);
93
+ document.getElementById('monthlyEarned').textContent =
94
+ currentCurrency + (s.monthly || 0).toFixed(2);
95
+ document.getElementById('monthlyCount').textContent =
96
+ (s.monthly_count || 0) + ' jobs';
97
+ document.getElementById('weeklyEarned').textContent =
98
+ currentCurrency + (s.weekly || 0).toFixed(2);
99
+ document.getElementById('weeklyCount').textContent =
100
+ (s.weekly_count || 0) + ' jobs';
101
+ document.getElementById('todayEarned').textContent =
102
+ currentCurrency + (s.today || 0).toFixed(2);
103
+ document.getElementById('todayCount').textContent =
104
+ (s.today_count || 0) + ' jobs';
105
+
106
+ // Chart
107
+ renderChart(data.daily_totals || []);
108
+
109
+ // Recent earnings
110
+ renderRecentEarnings(data.history || []);
111
+ }
112
+
113
+ // ─── Missions ──────────────────────────────────────────────────────────
114
+
115
+ async function loadMissions() {
116
+ const res = await fetch('/api/missions');
117
+ const data = await res.json();
118
+
119
+ document.getElementById('missionCount').textContent = data.total || 0;
120
+ renderMissions(data.missions || []);
121
+ }
122
+
123
+ // ─── Skills ────────────────────────────────────────────────────────────
124
+
125
+ async function loadSkills() {
126
+ const res = await fetch('/api/skills');
127
+ const data = await res.json();
128
+
129
+ document.getElementById('skillsCount').textContent = data.total_installed || 0;
130
+ renderSkills(data.skills || []);
131
+ }
132
+
133
+ // ─── Renderers ─────────────────────────────────────────────────────────
134
+
135
+ function renderServices(services) {
136
+ const container = document.getElementById('servicesList');
137
+
138
+ if (services.length === 0) {
139
+ container.innerHTML = '<div class="empty-state">No services configured. Run "cashclaw init" to set up.</div>';
140
+ return;
141
+ }
142
+
143
+ const serviceLabels = {
144
+ seo_audit: 'SEO Audit',
145
+ content_writing: 'Content Writing',
146
+ lead_generation: 'Lead Generation',
147
+ whatsapp_management: 'WhatsApp Management',
148
+ social_media: 'Social Media',
149
+ };
150
+
151
+ container.innerHTML = services.map(svc => {
152
+ const label = serviceLabels[svc.type] || svc.type;
153
+ const prices = Object.values(svc.pricing || {})
154
+ .map(p => currentCurrency + p)
155
+ .join(', ');
156
+
157
+ return `
158
+ <div class="service-item">
159
+ <div>
160
+ <div class="service-name">${escapeHtml(label)}</div>
161
+ <div class="service-prices">${escapeHtml(prices)}</div>
162
+ </div>
163
+ </div>
164
+ `;
165
+ }).join('');
166
+ }
167
+
168
+ function renderMissions(missions) {
169
+ const container = document.getElementById('missionsList');
170
+
171
+ if (missions.length === 0) {
172
+ container.innerHTML = '<div class="empty-state">No missions yet. Create one with:<br><code>cashclaw missions create seo-audit-basic</code></div>';
173
+ return;
174
+ }
175
+
176
+ container.innerHTML = missions.slice(0, 15).map(m => {
177
+ const statusClass = 'status-' + (m.status || 'created');
178
+ return `
179
+ <div class="mission-item">
180
+ <div class="mission-info">
181
+ <div class="mission-name">${escapeHtml(m.name)}</div>
182
+ <div class="mission-meta">${escapeHtml(m.client?.name || '-')} &middot; ${new Date(m.created_at).toLocaleDateString()}</div>
183
+ </div>
184
+ <span class="mission-price">${currentCurrency}${m.price_usd}</span>
185
+ <span class="mission-status ${statusClass}">${escapeHtml(m.status)}</span>
186
+ </div>
187
+ `;
188
+ }).join('');
189
+ }
190
+
191
+ function renderSkills(skills) {
192
+ const container = document.getElementById('skillsList');
193
+
194
+ if (skills.length === 0) {
195
+ container.innerHTML = '<div class="empty-state">No skills found</div>';
196
+ return;
197
+ }
198
+
199
+ container.innerHTML = skills.map(s => {
200
+ const badgeClass = s.installed ? 'skill-installed' : 'skill-available';
201
+ const badgeText = s.installed ? 'installed' : 'available';
202
+ return `
203
+ <div class="skill-item">
204
+ <span class="skill-name">${escapeHtml(s.name)}</span>
205
+ <span class="skill-badge ${badgeClass}">${badgeText}</span>
206
+ </div>
207
+ `;
208
+ }).join('');
209
+ }
210
+
211
+ function renderRecentEarnings(history) {
212
+ const container = document.getElementById('recentList');
213
+
214
+ if (history.length === 0) {
215
+ container.innerHTML = '<div class="empty-state">No earnings recorded yet</div>';
216
+ return;
217
+ }
218
+
219
+ container.innerHTML = history.slice(0, 10).map(e => {
220
+ return `
221
+ <div class="recent-item">
222
+ <div class="recent-info">
223
+ <div class="recent-service">${escapeHtml(e.service_type || 'general')}</div>
224
+ <div class="recent-date">${escapeHtml(e.client_name || '-')} &middot; ${new Date(e.recorded_at).toLocaleDateString()}</div>
225
+ </div>
226
+ <span class="recent-amount">${currentCurrency}${(e.amount || 0).toFixed(2)}</span>
227
+ </div>
228
+ `;
229
+ }).join('');
230
+ }
231
+
232
+ // ─── Canvas Chart ──────────────────────────────────────────────────────
233
+
234
+ function renderChart(dailyTotals) {
235
+ const canvas = document.getElementById('earningsChart');
236
+ const ctx = canvas.getContext('2d');
237
+ const container = canvas.parentElement;
238
+
239
+ // Set canvas size to container size for crisp rendering
240
+ const dpr = window.devicePixelRatio || 1;
241
+ const rect = container.getBoundingClientRect();
242
+ canvas.width = rect.width * dpr;
243
+ canvas.height = rect.height * dpr;
244
+ ctx.scale(dpr, dpr);
245
+
246
+ const w = rect.width;
247
+ const h = rect.height;
248
+ const padding = { top: 20, right: 20, bottom: 40, left: 50 };
249
+ const chartW = w - padding.left - padding.right;
250
+ const chartH = h - padding.top - padding.bottom;
251
+
252
+ // Clear
253
+ ctx.clearRect(0, 0, w, h);
254
+
255
+ if (!dailyTotals || dailyTotals.length === 0) {
256
+ ctx.fillStyle = '#5A6678';
257
+ ctx.font = '13px sans-serif';
258
+ ctx.textAlign = 'center';
259
+ ctx.fillText('No data yet', w / 2, h / 2);
260
+ return;
261
+ }
262
+
263
+ const values = dailyTotals.map(d => d.total);
264
+ const maxVal = Math.max(...values, 1);
265
+ const barCount = values.length;
266
+ const barGap = 2;
267
+ const barWidth = Math.max(1, (chartW - barGap * (barCount - 1)) / barCount);
268
+
269
+ // Y-axis grid lines
270
+ ctx.strokeStyle = '#2A3A5C';
271
+ ctx.lineWidth = 0.5;
272
+ const gridLines = 4;
273
+ for (let i = 0; i <= gridLines; i++) {
274
+ const y = padding.top + (chartH / gridLines) * i;
275
+ ctx.beginPath();
276
+ ctx.moveTo(padding.left, y);
277
+ ctx.lineTo(w - padding.right, y);
278
+ ctx.stroke();
279
+
280
+ // Y-axis labels
281
+ const val = maxVal - (maxVal / gridLines) * i;
282
+ ctx.fillStyle = '#5A6678';
283
+ ctx.font = '10px sans-serif';
284
+ ctx.textAlign = 'right';
285
+ ctx.fillText(currentCurrency + val.toFixed(0), padding.left - 6, y + 3);
286
+ }
287
+
288
+ // Bars
289
+ for (let i = 0; i < barCount; i++) {
290
+ const x = padding.left + i * (barWidth + barGap);
291
+ const barH = (values[i] / maxVal) * chartH;
292
+ const y = padding.top + chartH - barH;
293
+
294
+ // Bar gradient
295
+ const gradient = ctx.createLinearGradient(x, y, x, padding.top + chartH);
296
+ gradient.addColorStop(0, values[i] > 0 ? '#16C784' : '#2A3A5C');
297
+ gradient.addColorStop(1, values[i] > 0 ? '#0D7A4F' : '#1E2A45');
298
+
299
+ ctx.fillStyle = gradient;
300
+ ctx.beginPath();
301
+ ctx.roundRect(x, y, barWidth, barH, [2, 2, 0, 0]);
302
+ ctx.fill();
303
+
304
+ // X-axis labels (show every 5th day)
305
+ if (i % 5 === 0 || i === barCount - 1) {
306
+ const label = dailyTotals[i].date.slice(5); // MM-DD
307
+ ctx.fillStyle = '#5A6678';
308
+ ctx.font = '9px sans-serif';
309
+ ctx.textAlign = 'center';
310
+ ctx.fillText(label, x + barWidth / 2, h - padding.bottom + 14);
311
+ }
312
+ }
313
+ }
314
+
315
+ // ─── Helpers ───────────────────────────────────────────────────────────
316
+
317
+ function escapeHtml(str) {
318
+ if (!str) return '';
319
+ const div = document.createElement('div');
320
+ div.textContent = str;
321
+ return div.innerHTML;
322
+ }
323
+
324
+ // Handle window resize for chart
325
+ let resizeTimeout;
326
+ window.addEventListener('resize', () => {
327
+ clearTimeout(resizeTimeout);
328
+ resizeTimeout = setTimeout(loadAll, 250);
329
+ });
@@ -0,0 +1,139 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CashClaw Dashboard</title>
7
+ <link rel="stylesheet" href="/style.css">
8
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>&#x1f99e;</text></svg>">
9
+ </head>
10
+ <body>
11
+ <div id="app">
12
+ <!-- Header -->
13
+ <header class="header">
14
+ <div class="header-left">
15
+ <span class="logo">CashClaw</span>
16
+ <span class="version">v1.0.0</span>
17
+ </div>
18
+ <div class="header-right">
19
+ <span class="agent-name" id="agentName">Loading...</span>
20
+ <span class="status-dot" id="statusDot"></span>
21
+ </div>
22
+ </header>
23
+
24
+ <!-- Earnings Cards -->
25
+ <section class="cards">
26
+ <div class="card card-total">
27
+ <div class="card-label">Total Earned</div>
28
+ <div class="card-value" id="totalEarned">$0.00</div>
29
+ </div>
30
+ <div class="card card-monthly">
31
+ <div class="card-label">This Month</div>
32
+ <div class="card-value" id="monthlyEarned">$0.00</div>
33
+ <div class="card-sub" id="monthlyCount">0 jobs</div>
34
+ </div>
35
+ <div class="card card-weekly">
36
+ <div class="card-label">This Week</div>
37
+ <div class="card-value" id="weeklyEarned">$0.00</div>
38
+ <div class="card-sub" id="weeklyCount">0 jobs</div>
39
+ </div>
40
+ <div class="card card-today">
41
+ <div class="card-label">Today</div>
42
+ <div class="card-value" id="todayEarned">$0.00</div>
43
+ <div class="card-sub" id="todayCount">0 jobs</div>
44
+ </div>
45
+ </section>
46
+
47
+ <!-- Main Content -->
48
+ <div class="main-grid">
49
+ <!-- Earnings Chart -->
50
+ <section class="panel chart-panel">
51
+ <h2 class="panel-title">Earnings (Last 30 Days)</h2>
52
+ <div class="chart-container">
53
+ <canvas id="earningsChart"></canvas>
54
+ </div>
55
+ </section>
56
+
57
+ <!-- Active Missions -->
58
+ <section class="panel missions-panel">
59
+ <h2 class="panel-title">
60
+ Missions
61
+ <span class="badge" id="missionCount">0</span>
62
+ </h2>
63
+ <div class="missions-list" id="missionsList">
64
+ <div class="empty-state">No missions yet</div>
65
+ </div>
66
+ </section>
67
+
68
+ <!-- Services -->
69
+ <section class="panel services-panel">
70
+ <h2 class="panel-title">Active Services</h2>
71
+ <div class="services-list" id="servicesList">
72
+ <div class="empty-state">No services configured</div>
73
+ </div>
74
+ </section>
75
+
76
+ <!-- Skills -->
77
+ <section class="panel skills-panel">
78
+ <h2 class="panel-title">
79
+ Skills
80
+ <span class="badge" id="skillsCount">0</span>
81
+ </h2>
82
+ <div class="skills-list" id="skillsList">
83
+ <div class="empty-state">No skills installed</div>
84
+ </div>
85
+ </section>
86
+
87
+ <!-- Recent Earnings -->
88
+ <section class="panel recent-panel">
89
+ <h2 class="panel-title">Recent Earnings</h2>
90
+ <div class="recent-list" id="recentList">
91
+ <div class="empty-state">No earnings recorded</div>
92
+ </div>
93
+ </section>
94
+
95
+ <!-- Agent Info -->
96
+ <section class="panel info-panel">
97
+ <h2 class="panel-title">Agent Info</h2>
98
+ <div class="info-grid" id="agentInfo">
99
+ <div class="info-row">
100
+ <span class="info-label">Owner</span>
101
+ <span class="info-value" id="infoOwner">-</span>
102
+ </div>
103
+ <div class="info-row">
104
+ <span class="info-label">Email</span>
105
+ <span class="info-value" id="infoEmail">-</span>
106
+ </div>
107
+ <div class="info-row">
108
+ <span class="info-label">Currency</span>
109
+ <span class="info-value" id="infoCurrency">-</span>
110
+ </div>
111
+ <div class="info-row">
112
+ <span class="info-label">Stripe</span>
113
+ <span class="info-value" id="infoStripe">-</span>
114
+ </div>
115
+ <div class="info-row">
116
+ <span class="info-label">HYRVEai</span>
117
+ <span class="info-value" id="infoHyrve">-</span>
118
+ </div>
119
+ <div class="info-row">
120
+ <span class="info-label">OpenClaw</span>
121
+ <span class="info-value" id="infoOpenClaw">-</span>
122
+ </div>
123
+ </div>
124
+ </section>
125
+ </div>
126
+
127
+ <!-- Footer -->
128
+ <footer class="footer">
129
+ <span>CashClaw v1.0.0</span>
130
+ <span class="footer-sep">|</span>
131
+ <a href="https://cashclawai.com" target="_blank">cashclawai.com</a>
132
+ <span class="footer-sep">|</span>
133
+ <span id="lastUpdated">-</span>
134
+ </footer>
135
+ </div>
136
+
137
+ <script src="/app.js"></script>
138
+ </body>
139
+ </html>