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 +19 -0
- package/README.md +24 -0
- package/package.json +1 -1
- package/src/cli/commands/hyrve.js +79 -1
- package/src/dashboard/public/app.js +251 -0
- package/src/dashboard/public/index.html +71 -2
- package/src/dashboard/public/style.css +55 -0
- package/src/dashboard/server.js +134 -0
- package/src/integrations/hyrve-bridge.js +58 -9
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
|
@@ -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 {
|
|
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 || '') + ' · ' + 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 || '') + ' · ' + 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.
|
|
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.
|
|
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()">×</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()">×</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; }
|
package/src/dashboard/server.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|