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,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 || '-')} · ${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 || '-')} · ${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'>🦞</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>
|