cashclaw 1.4.0 → 1.4.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to CashClaw will be documented in this file.
4
4
 
5
+ ## [1.4.5] - 2026-03-20
6
+
7
+ ### Added
8
+ - HYRVE Marketplace panel in CashClaw dashboard (jobs, orders, wallet, profile)
9
+ - Job acceptance from dashboard UI (Accept button)
10
+ - Work delivery from dashboard UI (Deliver button with URL + notes)
11
+ - Wallet panel with available/pending/total earned balances
12
+ - CLI: `cashclaw hyrve accept <job-id>` command
13
+ - CLI: `cashclaw hyrve deliver <order-id> --url <url>` command
14
+ - CLI: `cashclaw hyrve profile` command
15
+ - CLI: `cashclaw hyrve orders` command
16
+ - `getWallet()` bridge function for wallet data
17
+ - Status badges for orders (escrow, delivered, completed, disputed)
18
+
19
+ ### Fixed
20
+ - Job prices showing $0 (was reading wrong field, now uses `budget_usd`)
21
+ - Agent registration using self-register endpoint (no auth required)
22
+ - Order amounts parsing (string to float conversion)
23
+
5
24
  ## [1.4.0] - 2026-03-19
6
25
 
7
26
  ### Added
package/README.md CHANGED
@@ -23,6 +23,17 @@
23
23
 
24
24
  ---
25
25
 
26
+ <p align="center">
27
+ <img src="https://img.shields.io/badge/stars-115-yellow?style=flat-square&logo=github" alt="115 stars" />
28
+ <img src="https://img.shields.io/badge/forks-38-blue?style=flat-square&logo=github" alt="38 forks" />
29
+ <img src="https://img.shields.io/badge/npm%20downloads-1.5k+-red?style=flat-square&logo=npm" alt="1,500+ downloads" />
30
+ <img src="https://img.shields.io/badge/skills-12-purple?style=flat-square" alt="12 skills" />
31
+ <img src="https://img.shields.io/badge/HYRVE%20users-3,000+-ff6b35?style=flat-square" alt="3,000+ users" />
32
+ <img src="https://img.shields.io/badge/agents-107-brightgreen?style=flat-square" alt="107 agents" />
33
+ </p>
34
+
35
+ ---
36
+
26
37
  > *"I deployed CashClaw on Friday. By Monday, my agent had completed 12 missions and earned $847."*
27
38
  >
28
39
  > -- Early beta tester
@@ -403,6 +414,19 @@ cashclaw/
403
414
  README.md
404
415
  ```
405
416
 
417
+ ## Platform Stats
418
+
419
+ | Metric | Value |
420
+ |--------|-------|
421
+ | GitHub Stars | 115 |
422
+ | GitHub Forks | 38 |
423
+ | npm Downloads | 1,500+ |
424
+ | Skills | 12 |
425
+ | HYRVE Registered Users | 3,000+ |
426
+ | Active Agents | 107 |
427
+ | Platform Revenue | $45.75 |
428
+ | Total Orders | 9 |
429
+
406
430
  ## Built By
407
431
 
408
432
  Built by [Ertugrul Akben](https://ertugrulakben.com) and the team at [EAGM Group](https://eagmgroup.com).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashclaw",
3
- "version": "1.4.0",
3
+ "version": "1.4.5",
4
4
  "description": "Turn your OpenClaw AI agent into a money-making machine — 12 skills, audit trails, security hardened",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,8 @@ import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
6
6
  import { loadConfig } from '../utils/config.js';
7
- import { listAvailableJobs, listOrders } from '../../integrations/hyrve-bridge.js';
7
+ import { showMiniBanner } from '../utils/banner.js';
8
+ import { listAvailableJobs, listOrders, acceptJob, deliverJob, getAgentProfile, getWallet } from '../../integrations/hyrve-bridge.js';
8
9
  import MppBridge from '../../integrations/mpp-bridge.js';
9
10
 
10
11
  export function createHyrveCommand() {
@@ -85,6 +86,83 @@ export function createHyrveCommand() {
85
86
  }
86
87
  });
87
88
 
89
+ hyrve
90
+ .command('accept <jobId>')
91
+ .description('Accept a job from the HYRVE marketplace')
92
+ .action(async (jobId) => {
93
+ showMiniBanner();
94
+ console.log(chalk.cyan(' Accepting job...'));
95
+ const result = await acceptJob(jobId);
96
+ if (result.success) {
97
+ console.log(chalk.green(` ✔ Job accepted! Order created.`));
98
+ if (result.order_id) console.log(chalk.gray(` Order ID: ${result.order_id}`));
99
+ } else {
100
+ console.log(chalk.red(` ✖ ${result.message}`));
101
+ }
102
+ });
103
+
104
+ hyrve
105
+ .command('deliver <orderId>')
106
+ .description('Deliver work for a HYRVE order')
107
+ .option('--url <url>', 'Deliverables URL')
108
+ .option('--summary <text>', 'Delivery summary/notes')
109
+ .action(async (orderId, opts) => {
110
+ showMiniBanner();
111
+ if (!opts.url) {
112
+ console.log(chalk.red(' ✖ --url is required (deliverables link)'));
113
+ return;
114
+ }
115
+ console.log(chalk.cyan(' Delivering work...'));
116
+ const result = await deliverJob(orderId, { deliverables: opts.url, notes: opts.summary || '' });
117
+ if (result.success) {
118
+ console.log(chalk.green(` ✔ Work delivered! Waiting for client approval.`));
119
+ } else {
120
+ console.log(chalk.red(` ✖ ${result.message}`));
121
+ }
122
+ });
123
+
124
+ hyrve
125
+ .command('profile')
126
+ .description('View your HYRVE marketplace profile')
127
+ .action(async () => {
128
+ showMiniBanner();
129
+ console.log(chalk.cyan(' Fetching profile...'));
130
+ const result = await getAgentProfile();
131
+ if (result.success || result.agent) {
132
+ const a = result.agent || result;
133
+ console.log(`\n ${chalk.bold(a.name || 'Unknown')}`);
134
+ console.log(` ${chalk.gray('ID:')} ${a.id || 'N/A'}`);
135
+ console.log(` ${chalk.gray('Slug:')} ${a.slug || 'N/A'}`);
136
+ console.log(` ${chalk.gray('Rating:')} ${a.avg_rating || '0'}/5`);
137
+ console.log(` ${chalk.gray('Jobs:')} ${a.total_jobs || 0} total, ${a.completed_jobs || 0} completed`);
138
+ console.log(` ${chalk.gray('Earned:')} $${parseFloat(a.total_earned || 0).toFixed(2)}`);
139
+ console.log(` ${chalk.gray('Online:')} ${a.is_online ? chalk.green('Yes') : chalk.red('No')}`);
140
+ console.log(` ${chalk.gray('URL:')} https://app.hyrveai.com/agents/${a.slug}`);
141
+ } else {
142
+ console.log(chalk.red(` ✖ ${result.message || 'Profile not available'}`));
143
+ }
144
+ });
145
+
146
+ hyrve
147
+ .command('orders')
148
+ .description('List your HYRVE marketplace orders')
149
+ .option('--status <status>', 'Filter by status (all/active/completed)', 'all')
150
+ .action(async (opts) => {
151
+ showMiniBanner();
152
+ console.log(chalk.cyan(' Fetching orders...'));
153
+ const result = await listOrders({ status: opts.status });
154
+ const orders = result.orders || result.data || [];
155
+ if (orders.length === 0) {
156
+ console.log(chalk.gray(' No orders found.'));
157
+ return;
158
+ }
159
+ console.log(`\n ${chalk.bold('HYRVE Orders')} (${orders.length})\n`);
160
+ for (const o of orders) {
161
+ const statusColor = { completed: 'green', escrow: 'yellow', delivered: 'cyan', disputed: 'red' }[o.status] || 'gray';
162
+ console.log(` ${chalk.gray(o.id?.slice(0, 8) || '?')} ${o.task_description?.slice(0, 40) || 'Order'} $${parseFloat(o.amount_usd || 0).toFixed(2)} ${chalk[statusColor](o.status)}`);
163
+ }
164
+ });
165
+
88
166
  hyrve
89
167
  .command('dashboard')
90
168
  .description('Open HYRVE AI dashboard in browser')
@@ -23,6 +23,8 @@ async function loadAll() {
23
23
  loadEarnings(),
24
24
  loadMissions(),
25
25
  loadSkills(),
26
+ loadHyrve(),
27
+ loadWallet(),
26
28
  ]);
27
29
  document.getElementById('lastUpdated').textContent =
28
30
  'Updated: ' + new Date().toLocaleTimeString();
@@ -130,6 +132,131 @@ async function loadSkills() {
130
132
  renderSkills(data.skills || []);
131
133
  }
132
134
 
135
+ // ─── HYRVE Marketplace ────────────────────────────────────────────────
136
+
137
+ let _hyrveJobs = [];
138
+
139
+ async function loadHyrve() {
140
+ const res = await fetch('/api/hyrve');
141
+ const data = await res.json();
142
+
143
+ const statusEl = document.getElementById('hyrveStatus');
144
+ const contentEl = document.getElementById('hyrveContent');
145
+ const actionsEl = document.getElementById('hyrveActions');
146
+
147
+ actionsEl.style.display = 'flex';
148
+
149
+ if (!data.registered) {
150
+ statusEl.textContent = 'Not Connected';
151
+ statusEl.className = 'badge badge-warning';
152
+ document.getElementById('hyrveRegisterBtn').style.display = 'inline-block';
153
+ document.getElementById('hyrveSyncBtn').style.display = 'none';
154
+ contentEl.innerHTML = '<div class="empty-state">Not registered on HYRVE marketplace.<br>Click "Register on HYRVE" to connect.</div>';
155
+ return;
156
+ }
157
+
158
+ statusEl.textContent = 'Connected';
159
+ statusEl.className = 'badge badge-success';
160
+ document.getElementById('hyrveRegisterBtn').style.display = 'none';
161
+ document.getElementById('hyrveSyncBtn').style.display = 'inline-block';
162
+
163
+ let html = '';
164
+
165
+ // Profile
166
+ if (data.profile) {
167
+ html += '<div class="hyrve-section"><h3>Agent Profile</h3>';
168
+ html += '<div class="info-row"><span class="info-label">Agent ID</span><span class="info-value">' + escapeHtml(data.agent_id) + '</span></div>';
169
+ html += '<div class="info-row"><span class="info-label">Rating</span><span class="info-value">' + (data.profile.avg_rating || 'N/A') + '</span></div>';
170
+ html += '<div class="info-row"><span class="info-label">Status</span><span class="info-value">' + (data.profile.status || 'active') + '</span></div>';
171
+ html += '</div>';
172
+ }
173
+
174
+ // Available Jobs
175
+ const jobs = data.jobs || [];
176
+ _hyrveJobs = jobs;
177
+ html += '<div class="hyrve-section"><h3>Available Jobs (' + jobs.length + ')</h3>';
178
+ if (jobs.length === 0) {
179
+ html += '<div class="empty-state">No jobs available right now</div>';
180
+ } else {
181
+ html += jobs.slice(0, 10).map(j => {
182
+ const price = parseFloat(j.budget_usd || j.budget || j.price || 0);
183
+ return '<div class="mission-item">' +
184
+ '<div class="mission-info">' +
185
+ '<div class="mission-name" style="cursor:pointer" onclick="showJobDetail(\'' + j.id + '\')">' + escapeHtml(j.title || j.description || 'Job') + '</div>' +
186
+ '<div class="mission-meta">' + escapeHtml(j.category || '') + ' &middot; ' + escapeHtml(j.client_name || '') + '</div>' +
187
+ '</div>' +
188
+ '<span class="mission-price">$' + price.toFixed(2) + '</span>' +
189
+ '<a class="btn btn-sm btn-outline" href="https://app.hyrveai.com/marketplace" target="_blank">View</a>' +
190
+ '</div>';
191
+ }).join('');
192
+ }
193
+ html += '</div>';
194
+
195
+ // Orders
196
+ const orders = data.orders || [];
197
+ html += '<div class="hyrve-section"><h3>Orders (' + orders.length + ')</h3>';
198
+ if (orders.length === 0) {
199
+ html += '<div class="empty-state">No orders yet</div>';
200
+ } else {
201
+ html += orders.slice(0, 10).map(o => {
202
+ const amount = parseFloat(o.amount_usd || o.amount || o.price || 0);
203
+ const statusColors = { escrow: 'badge-warning', delivered: 'badge-info', completed: 'badge-success', disputed: 'badge-danger', in_progress: 'badge-info' };
204
+ const badgeClass = statusColors[o.status] || '';
205
+ const canDeliver = ['escrow', 'in_progress'].includes(o.status);
206
+ return '<div class="mission-item">' +
207
+ '<div class="mission-info">' +
208
+ '<div class="mission-name">' + escapeHtml(o.task_description || 'Order #' + (o.id || '').slice(0,8)) + '</div>' +
209
+ '<div class="mission-meta"><span class="order-badge ' + badgeClass + '">' + escapeHtml(o.status || '') + '</span></div>' +
210
+ '</div>' +
211
+ '<span class="mission-price">$' + amount.toFixed(2) + '</span>' +
212
+ (canDeliver ? '<button class="btn btn-sm btn-secondary" onclick="openDeliverModal(\'' + o.id + '\')">Deliver</button>' : '') +
213
+ '</div>';
214
+ }).join('');
215
+ }
216
+ html += '</div>';
217
+
218
+ contentEl.innerHTML = html;
219
+ }
220
+
221
+ async function registerOnHyrve() {
222
+ const btn = document.getElementById('hyrveRegisterBtn');
223
+ btn.textContent = 'Registering...';
224
+ btn.disabled = true;
225
+
226
+ try {
227
+ const res = await fetch('/api/hyrve/register', { method: 'POST' });
228
+ const data = await res.json();
229
+
230
+ if (data.success) {
231
+ btn.textContent = 'Registered!';
232
+ await loadHyrve();
233
+ } else {
234
+ btn.textContent = 'Failed - Retry';
235
+ btn.disabled = false;
236
+ alert('Registration failed: ' + (data.message || 'Unknown error'));
237
+ }
238
+ } catch (err) {
239
+ btn.textContent = 'Error - Retry';
240
+ btn.disabled = false;
241
+ alert('Error: ' + err.message);
242
+ }
243
+ }
244
+
245
+ async function syncHyrve() {
246
+ const btn = document.getElementById('hyrveSyncBtn');
247
+ btn.textContent = 'Syncing...';
248
+
249
+ try {
250
+ await fetch('/api/hyrve/sync', { method: 'POST' });
251
+ btn.textContent = 'Synced!';
252
+ await loadHyrve();
253
+ setTimeout(() => { btn.textContent = 'Sync Status'; }, 2000);
254
+ } catch (err) {
255
+ btn.textContent = 'Sync Failed';
256
+ setTimeout(() => { btn.textContent = 'Sync Status'; }, 2000);
257
+ }
258
+ }
259
+
133
260
  // ─── Renderers ─────────────────────────────────────────────────────────
134
261
 
135
262
  function renderServices(services) {
@@ -332,3 +459,127 @@ window.addEventListener('resize', () => {
332
459
  clearTimeout(resizeTimeout);
333
460
  resizeTimeout = setTimeout(loadAll, 250);
334
461
  });
462
+
463
+ // ─── Wallet ─────────────────────────────────────────────────────────
464
+
465
+ async function loadWallet() {
466
+ try {
467
+ const res = await fetch('/api/hyrve/wallet');
468
+ const data = await res.json();
469
+ if (!data.success && !data.wallet) {
470
+ document.getElementById('walletAvailable').textContent = '$0.00';
471
+ document.getElementById('walletPending').textContent = '$0.00';
472
+ document.getElementById('walletTotal').textContent = '$0.00';
473
+ return;
474
+ }
475
+ const w = data.wallet || {};
476
+ document.getElementById('walletAvailable').textContent = '$' + parseFloat(w.available || 0).toFixed(2);
477
+ document.getElementById('walletPending').textContent = '$' + parseFloat(w.pending || 0).toFixed(2);
478
+ document.getElementById('walletTotal').textContent = '$' + parseFloat(w.total_earned || 0).toFixed(2);
479
+
480
+ const txEl = document.getElementById('walletTransactions');
481
+ const txs = data.transactions || [];
482
+ if (txs.length === 0) {
483
+ txEl.innerHTML = '<div class="empty-state">No transactions yet</div>';
484
+ } else {
485
+ txEl.innerHTML = txs.slice(0, 5).map(t => {
486
+ const isCredit = ['payment', 'earning'].includes(t.type);
487
+ return '<div class="recent-item">' +
488
+ '<div class="recent-info">' +
489
+ '<div class="recent-service">' + escapeHtml(t.description || t.type) + '</div>' +
490
+ '<div class="recent-date">' + escapeHtml(t.status || '') + ' &middot; ' + new Date(t.created_at).toLocaleDateString() + '</div>' +
491
+ '</div>' +
492
+ '<span class="recent-amount" style="color:' + (isCredit ? '#16C784' : '#F5A623') + '">' + (isCredit ? '+' : '-') + '$' + Math.abs(t.amount || 0).toFixed(2) + '</span>' +
493
+ '</div>';
494
+ }).join('');
495
+ }
496
+ } catch (err) {
497
+ console.error('Wallet load error:', err);
498
+ }
499
+ }
500
+
501
+ // ─── Job Accept ─────────────────────────────────────────────────────
502
+
503
+ async function acceptJobFromDashboard(jobId) {
504
+ if (!confirm('Accept this job?')) return;
505
+ try {
506
+ const res = await fetch('/api/hyrve/jobs/' + jobId + '/accept', { method: 'POST' });
507
+ const data = await res.json();
508
+ if (data.success) {
509
+ alert('Job accepted! Order created.');
510
+ await loadHyrve();
511
+ } else {
512
+ alert('Failed: ' + (data.message || 'Unknown error'));
513
+ }
514
+ } catch (err) {
515
+ alert('Error: ' + err.message);
516
+ }
517
+ }
518
+
519
+ function showJobDetail(jobId) {
520
+ const job = _hyrveJobs.find(j => j.id === jobId);
521
+ if (!job) return;
522
+ const price = parseFloat(job.budget_usd || job.budget || 0);
523
+ document.getElementById('jobModalTitle').textContent = job.title || 'Job Details';
524
+ document.getElementById('jobModalBody').innerHTML =
525
+ '<div class="info-row"><span class="info-label">Category</span><span class="info-value">' + escapeHtml(job.category || 'N/A') + '</span></div>' +
526
+ '<div class="info-row"><span class="info-label">Budget</span><span class="info-value">$' + price.toFixed(2) + '</span></div>' +
527
+ '<div class="info-row"><span class="info-label">Client</span><span class="info-value">' + escapeHtml(job.client_name || 'N/A') + '</span></div>' +
528
+ '<div class="info-row"><span class="info-label">Status</span><span class="info-value">' + escapeHtml(job.status || 'open') + '</span></div>' +
529
+ '<div style="margin-top:12px;"><strong>Description:</strong><p style="color:#8B9CC0;margin-top:4px;">' + escapeHtml(job.description || 'No description') + '</p></div>';
530
+ document.getElementById('jobModalFooter').innerHTML =
531
+ '<a class="btn btn-primary" href="https://app.hyrveai.com/marketplace" target="_blank">Apply on HYRVE ($' + price.toFixed(2) + ')</a>' +
532
+ '<button class="btn btn-outline" onclick="closeJobModal()">Close</button>';
533
+ document.getElementById('jobModal').style.display = 'flex';
534
+ }
535
+
536
+ function closeJobModal() {
537
+ document.getElementById('jobModal').style.display = 'none';
538
+ }
539
+
540
+ // ─── Deliver ────────────────────────────────────────────────────────
541
+
542
+ let _currentDeliverOrderId = null;
543
+
544
+ function openDeliverModal(orderId) {
545
+ _currentDeliverOrderId = orderId;
546
+ document.getElementById('deliverUrl').value = '';
547
+ document.getElementById('deliverNotes').value = '';
548
+ document.getElementById('deliverModal').style.display = 'flex';
549
+ }
550
+
551
+ function closeDeliverModal() {
552
+ document.getElementById('deliverModal').style.display = 'none';
553
+ _currentDeliverOrderId = null;
554
+ }
555
+
556
+ async function submitDelivery() {
557
+ const url = document.getElementById('deliverUrl').value.trim();
558
+ const notes = document.getElementById('deliverNotes').value.trim();
559
+ if (!url) { alert('Deliverables URL is required'); return; }
560
+
561
+ const btn = document.getElementById('deliverSubmitBtn');
562
+ btn.textContent = 'Submitting...';
563
+ btn.disabled = true;
564
+
565
+ try {
566
+ const res = await fetch('/api/hyrve/orders/' + _currentDeliverOrderId + '/deliver', {
567
+ method: 'POST',
568
+ headers: { 'Content-Type': 'application/json' },
569
+ body: JSON.stringify({ deliverables: url, notes }),
570
+ });
571
+ const data = await res.json();
572
+ if (data.success) {
573
+ alert('Work delivered! Waiting for client approval.');
574
+ closeDeliverModal();
575
+ await loadHyrve();
576
+ } else {
577
+ alert('Failed: ' + (data.message || 'Unknown error'));
578
+ }
579
+ } catch (err) {
580
+ alert('Error: ' + err.message);
581
+ } finally {
582
+ btn.textContent = 'Submit Delivery';
583
+ btn.disabled = false;
584
+ }
585
+ }
@@ -13,7 +13,7 @@
13
13
  <header class="header">
14
14
  <div class="header-left">
15
15
  <span class="logo">CashClaw</span>
16
- <span class="version" id="appVersion">v1.2.0</span>
16
+ <span class="version" id="appVersion">v1.4.5</span>
17
17
  </div>
18
18
  <div class="header-right">
19
19
  <span class="agent-name" id="agentName">Loading...</span>
@@ -84,6 +84,44 @@
84
84
  </div>
85
85
  </section>
86
86
 
87
+ <!-- HYRVE Marketplace -->
88
+ <section class="panel hyrve-panel">
89
+ <h2 class="panel-title">
90
+ HYRVE Marketplace
91
+ <span class="badge" id="hyrveStatus">-</span>
92
+ </h2>
93
+ <div id="hyrveContent">
94
+ <div class="empty-state">Loading HYRVE data...</div>
95
+ </div>
96
+ <div class="hyrve-actions" id="hyrveActions" style="display:none;">
97
+ <button class="btn btn-primary" id="hyrveRegisterBtn" onclick="registerOnHyrve()">Register on HYRVE</button>
98
+ <button class="btn btn-secondary" id="hyrveSyncBtn" onclick="syncHyrve()">Sync Status</button>
99
+ <a class="btn btn-outline" href="https://app.hyrveai.com" target="_blank">Open Dashboard</a>
100
+ </div>
101
+ </section>
102
+
103
+ <!-- HYRVE Wallet -->
104
+ <section class="panel wallet-panel">
105
+ <h2 class="panel-title">HYRVE Wallet</h2>
106
+ <div class="wallet-cards" id="walletCards">
107
+ <div class="wallet-card wallet-available">
108
+ <div class="card-label">Available</div>
109
+ <div class="card-value" id="walletAvailable">$0.00</div>
110
+ </div>
111
+ <div class="wallet-card wallet-pending">
112
+ <div class="card-label">Pending (Escrow)</div>
113
+ <div class="card-value" id="walletPending">$0.00</div>
114
+ </div>
115
+ <div class="wallet-card wallet-total">
116
+ <div class="card-label">Total Earned</div>
117
+ <div class="card-value" id="walletTotal">$0.00</div>
118
+ </div>
119
+ </div>
120
+ <div class="wallet-transactions" id="walletTransactions">
121
+ <div class="empty-state">No transactions yet</div>
122
+ </div>
123
+ </section>
124
+
87
125
  <!-- Recent Earnings -->
88
126
  <section class="panel recent-panel">
89
127
  <h2 class="panel-title">Recent Earnings</h2>
@@ -126,7 +164,7 @@
126
164
 
127
165
  <!-- Footer -->
128
166
  <footer class="footer">
129
- <span id="footerVersion">CashClaw v1.2.0</span>
167
+ <span id="footerVersion">CashClaw v1.4.5</span>
130
168
  <span class="footer-sep">|</span>
131
169
  <a href="https://cashclawai.com" target="_blank">cashclawai.com</a>
132
170
  <span class="footer-sep">|</span>
@@ -134,6 +172,37 @@
134
172
  </footer>
135
173
  </div>
136
174
 
175
+ <!-- Job Detail Modal -->
176
+ <div class="modal-overlay" id="jobModal" style="display:none;">
177
+ <div class="modal">
178
+ <div class="modal-header">
179
+ <h3 id="jobModalTitle">Job Details</h3>
180
+ <button class="modal-close" onclick="closeJobModal()">&times;</button>
181
+ </div>
182
+ <div class="modal-body" id="jobModalBody"></div>
183
+ <div class="modal-footer" id="jobModalFooter"></div>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Deliver Modal -->
188
+ <div class="modal-overlay" id="deliverModal" style="display:none;">
189
+ <div class="modal">
190
+ <div class="modal-header">
191
+ <h3>Deliver Work</h3>
192
+ <button class="modal-close" onclick="closeDeliverModal()">&times;</button>
193
+ </div>
194
+ <div class="modal-body">
195
+ <label>Deliverables URL *</label>
196
+ <input type="url" id="deliverUrl" placeholder="https://..." class="modal-input">
197
+ <label>Notes (optional)</label>
198
+ <textarea id="deliverNotes" placeholder="Summary of work delivered..." class="modal-input" rows="3"></textarea>
199
+ </div>
200
+ <div class="modal-footer">
201
+ <button class="btn btn-primary" id="deliverSubmitBtn" onclick="submitDelivery()">Submit Delivery</button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
137
206
  <script src="/app.js"></script>
138
207
  </body>
139
208
  </html>
@@ -435,6 +435,22 @@ body {
435
435
  background: var(--text-muted);
436
436
  }
437
437
 
438
+ /* ─── HYRVE Marketplace ─────────────────────────────────────────── */
439
+
440
+ .hyrve-panel { grid-column: 1 / -1; }
441
+ .hyrve-actions { display: flex; gap: 8px; margin-top: 12px; }
442
+ .hyrve-section { margin-bottom: 16px; }
443
+ .hyrve-section h3 { font-size: 13px; color: #8B9CC0; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
444
+ .btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; text-decoration: none; }
445
+ .btn-primary { background: #16C784; color: #fff; }
446
+ .btn-primary:hover { background: #12A96D; }
447
+ .btn-secondary { background: #2A3A5C; color: #C5D0E6; }
448
+ .btn-secondary:hover { background: #354B75; }
449
+ .btn-outline { background: transparent; border: 1px solid #2A3A5C; color: #8B9CC0; }
450
+ .btn-outline:hover { border-color: #16C784; color: #16C784; }
451
+ .badge-success { background: #16C784 !important; }
452
+ .badge-warning { background: #F5A623 !important; }
453
+
438
454
  /* ─── Responsive ─────────────────────────────────────────────────── */
439
455
 
440
456
  @media (max-width: 768px) {
@@ -461,4 +477,43 @@ body {
461
477
  align-items: flex-start;
462
478
  gap: 8px;
463
479
  }
480
+
481
+ .wallet-cards {
482
+ grid-template-columns: 1fr !important;
483
+ }
464
484
  }
485
+
486
+ /* ─── Wallet ─────────────────────────────────────────────────────── */
487
+
488
+ .wallet-panel { grid-column: 1 / -1; }
489
+ .wallet-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px; }
490
+ .wallet-card { background: #1A2744; border-radius: 8px; padding: 16px; text-align: center; }
491
+ .wallet-card .card-label { font-size: 11px; color: #5A6678; text-transform: uppercase; letter-spacing: 0.5px; }
492
+ .wallet-card .card-value { font-size: 24px; font-weight: 700; margin-top: 4px; }
493
+ .wallet-available .card-value { color: #16C784; }
494
+ .wallet-pending .card-value { color: #F5A623; }
495
+ .wallet-total .card-value { color: #C5D0E6; }
496
+
497
+ /* ─── Modal ──────────────────────────────────────────────────────── */
498
+
499
+ .modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 1000; align-items: center; justify-content: center; }
500
+ .modal { background: #1A2744; border-radius: 12px; width: 90%; max-width: 500px; max-height: 80vh; overflow-y: auto; }
501
+ .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #2A3A5C; }
502
+ .modal-header h3 { margin: 0; color: #C5D0E6; font-size: 16px; }
503
+ .modal-close { background: none; border: none; color: #5A6678; font-size: 24px; cursor: pointer; }
504
+ .modal-close:hover { color: #C5D0E6; }
505
+ .modal-body { padding: 20px; }
506
+ .modal-body label { display: block; font-size: 12px; color: #5A6678; margin-bottom: 4px; margin-top: 12px; text-transform: uppercase; }
507
+ .modal-input { width: 100%; padding: 10px 12px; background: #0F1A2E; border: 1px solid #2A3A5C; border-radius: 6px; color: #C5D0E6; font-size: 14px; box-sizing: border-box; }
508
+ .modal-input:focus { outline: none; border-color: #16C784; }
509
+ .modal-footer { display: flex; gap: 8px; padding: 16px 20px; border-top: 1px solid #2A3A5C; }
510
+
511
+ /* ─── Order Status Badges ────────────────────────────────────────── */
512
+
513
+ .order-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
514
+ .badge-info { background: rgba(52,152,219,0.2); color: #3498DB; }
515
+ .badge-danger { background: rgba(231,76,60,0.2); color: #E74C3C; }
516
+
517
+ /* ─── Small Buttons ──────────────────────────────────────────────── */
518
+
519
+ .btn-sm { padding: 4px 10px; font-size: 11px; margin-left: 8px; }
@@ -6,6 +6,7 @@ import { VERSION } from '../utils/version.js';
6
6
  import { listMissions, getMissionStats, getMissionTrail } from '../engine/mission-runner.js';
7
7
  import { getTotal, getMonthly, getWeekly, getToday, getHistory, getByService, getDailyTotals } from '../engine/earnings-tracker.js';
8
8
  import { listInstalledSkills, listAvailableSkills } from '../integrations/openclaw-bridge.js';
9
+ import { listAvailableJobs, getAgentProfile, listOrders, registerAgent, syncStatus, acceptJob, deliverJob, getWallet } from '../integrations/hyrve-bridge.js';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -240,6 +241,139 @@ export function createDashboardServer() {
240
241
  }
241
242
  });
242
243
 
244
+ // ─── HYRVE Marketplace Routes ──────────────────────────────────────
245
+
246
+ /**
247
+ * GET /api/hyrve
248
+ * Returns HYRVE marketplace data (jobs, orders, profile).
249
+ */
250
+ app.get('/api/hyrve', async (req, res) => {
251
+ try {
252
+ const config = await loadConfig();
253
+
254
+ if (!config.hyrve?.registered) {
255
+ return res.json({
256
+ registered: false,
257
+ agent_id: null,
258
+ jobs: [],
259
+ orders: [],
260
+ profile: null,
261
+ });
262
+ }
263
+
264
+ // Parallel fetch - use both bridge functions and direct public API
265
+ const [jobsResult, ordersResult, profileResult] = await Promise.allSettled([
266
+ listAvailableJobs(),
267
+ listOrders({ status: 'all' }),
268
+ getAgentProfile(),
269
+ ]);
270
+
271
+ // Extract jobs from bridge result (jobs field) or fallback to empty
272
+ const jobsData = jobsResult.status === 'fulfilled'
273
+ ? (jobsResult.value.jobs || jobsResult.value.data || [])
274
+ : [];
275
+
276
+ // Extract orders
277
+ const ordersData = ordersResult.status === 'fulfilled'
278
+ ? (ordersResult.value.orders || ordersResult.value.data || [])
279
+ : [];
280
+
281
+ // Extract profile
282
+ const profileData = profileResult.status === 'fulfilled'
283
+ ? (profileResult.value.agent || profileResult.value.data || profileResult.value)
284
+ : null;
285
+
286
+ res.json({
287
+ registered: true,
288
+ agent_id: config.hyrve.agent_id,
289
+ api_url: config.hyrve.api_url || 'https://api.hyrveai.com/v1',
290
+ dashboard_url: config.hyrve.dashboard_url || 'https://app.hyrveai.com',
291
+ jobs: jobsData,
292
+ orders: ordersData,
293
+ profile: profileData,
294
+ });
295
+ } catch (err) {
296
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
297
+ }
298
+ });
299
+
300
+ /**
301
+ * POST /api/hyrve/register
302
+ * Register this CashClaw agent on HYRVE marketplace.
303
+ */
304
+ app.post('/api/hyrve/register', async (req, res) => {
305
+ try {
306
+ const config = await loadConfig();
307
+ const result = await registerAgent(config);
308
+
309
+ if (result.success) {
310
+ config.hyrve = config.hyrve || {};
311
+ config.hyrve.registered = true;
312
+ config.hyrve.agent_id = result.data.agent_id;
313
+ config.hyrve.api_key = result.data.api_key;
314
+ config.hyrve.enabled = true;
315
+ await saveConfig(config);
316
+ }
317
+
318
+ res.json(result);
319
+ } catch (err) {
320
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
321
+ }
322
+ });
323
+
324
+ /**
325
+ * POST /api/hyrve/sync
326
+ * Sync status heartbeat with HYRVE.
327
+ */
328
+ app.post('/api/hyrve/sync', async (req, res) => {
329
+ try {
330
+ const result = await syncStatus();
331
+ res.json(result);
332
+ } catch (err) {
333
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
334
+ }
335
+ });
336
+
337
+ /**
338
+ * POST /api/hyrve/jobs/:id/accept
339
+ * Accept a job from the HYRVE marketplace.
340
+ */
341
+ app.post('/api/hyrve/jobs/:id/accept', async (req, res) => {
342
+ try {
343
+ const result = await acceptJob(req.params.id);
344
+ res.json(result);
345
+ } catch (err) {
346
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
347
+ }
348
+ });
349
+
350
+ /**
351
+ * POST /api/hyrve/orders/:id/deliver
352
+ * Deliver work for a HYRVE order.
353
+ */
354
+ app.post('/api/hyrve/orders/:id/deliver', async (req, res) => {
355
+ try {
356
+ const { deliverables, notes } = req.body;
357
+ const result = await deliverJob(req.params.id, { deliverables, notes });
358
+ res.json(result);
359
+ } catch (err) {
360
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
361
+ }
362
+ });
363
+
364
+ /**
365
+ * GET /api/hyrve/wallet
366
+ * Get wallet balances and recent transactions.
367
+ */
368
+ app.get('/api/hyrve/wallet', async (req, res) => {
369
+ try {
370
+ const result = await getWallet();
371
+ res.json(result);
372
+ } catch (err) {
373
+ res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message } });
374
+ }
375
+ });
376
+
243
377
  /**
244
378
  * GET /api/health
245
379
  * Simple health check endpoint.
@@ -101,10 +101,24 @@ export async function registerAgent(config) {
101
101
  };
102
102
 
103
103
  try {
104
- const response = await fetch(`${apiUrl}/agents/register`, {
104
+ // Use self-register endpoint (no auth required for initial registration)
105
+ const selfRegPayload = {
106
+ agent_name: payload.agent_name,
107
+ description: `CashClaw agent: ${enabledServices.map(s => s.type).join(', ')}`,
108
+ capabilities: enabledServices.map(s => s.type),
109
+ pricing_model: 'per_task',
110
+ base_price_usd: enabledServices[0]?.pricing?.basic || 5,
111
+ owner_email: payload.email,
112
+ owner_name: payload.owner_name,
113
+ };
114
+
115
+ const response = await fetch(`${apiUrl}/agents/self-register`, {
105
116
  method: 'POST',
106
- headers: await getHeaders(config),
107
- body: JSON.stringify(payload),
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ 'User-Agent': `CashClaw/${VERSION}`,
120
+ },
121
+ body: JSON.stringify(selfRegPayload),
108
122
  });
109
123
 
110
124
  if (!response.ok) {
@@ -115,7 +129,12 @@ export async function registerAgent(config) {
115
129
  const data = await response.json();
116
130
  return {
117
131
  success: true,
118
- agent_id: data.agent_id || data.id,
132
+ data: {
133
+ agent_id: data.agent_id || data.id,
134
+ api_key: data.api_key || null,
135
+ agent_slug: data.agent_slug || null,
136
+ dashboard_url: data.dashboard_url || null,
137
+ },
119
138
  message: data.message || 'Agent registered successfully',
120
139
  };
121
140
  } catch (err) {
@@ -186,11 +205,10 @@ export async function listAvailableJobs() {
186
205
  .map(([key]) => key);
187
206
 
188
207
  try {
189
- const params = new URLSearchParams({
190
- service_types: enabledTypes.join(','),
191
- currency: config.agent?.currency || 'USD',
192
- limit: '20',
193
- });
208
+ const params = new URLSearchParams({ limit: '20' });
209
+ if (enabledTypes.length > 0) {
210
+ params.set('service_types', enabledTypes.join(','));
211
+ }
194
212
 
195
213
  const response = await fetch(`${apiUrl}/jobs?${params}`, {
196
214
  headers: await getHeaders(config),
@@ -411,3 +429,34 @@ export async function listOrders(options = {}) {
411
429
  };
412
430
  }
413
431
  }
432
+
433
+ /**
434
+ * Get the agent's wallet data from the HYRVE marketplace.
435
+ * Returns available balance, pending balance, total earned, and recent transactions.
436
+ * @returns {object} Wallet data with balances and transactions
437
+ */
438
+ export async function getWallet() {
439
+ const config = await loadConfig();
440
+ const apiUrl = await getApiUrl();
441
+ const check = checkBridgeConfig(config);
442
+ if (!check.configured) {
443
+ return { success: false, wallet: null, transactions: [], message: check.message };
444
+ }
445
+ try {
446
+ const response = await fetch(`${apiUrl}/wallet`, {
447
+ headers: await getHeaders(config),
448
+ });
449
+ if (!response.ok) {
450
+ const errMsg = await parseErrorResponse(response);
451
+ throw new Error(`Wallet fetch failed (${response.status}): ${errMsg}`);
452
+ }
453
+ const data = await response.json();
454
+ return {
455
+ success: true,
456
+ wallet: data.wallet || { available: 0, pending: 0, total_earned: 0 },
457
+ transactions: data.transactions || [],
458
+ };
459
+ } catch (err) {
460
+ return { success: false, wallet: null, transactions: [], message: `Wallet unavailable: ${err.message}` };
461
+ }
462
+ }