cashclaw 1.3.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 +36 -0
- package/README.md +50 -3
- package/package.json +1 -1
- package/src/cli/commands/hyrve.js +178 -0
- package/src/cli/index.js +4 -0
- 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/src/integrations/mpp-bridge.js +67 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
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
|
+
|
|
24
|
+
## [1.4.0] - 2026-03-19
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Machine Payments Protocol (MPP) bridge (`src/integrations/mpp-bridge.js`)
|
|
28
|
+
- Stripe + Tempo stablecoin payments (USDC)
|
|
29
|
+
- 1.5% transaction fees (vs 2.9%+$0.30 for cards)
|
|
30
|
+
- createChallenge, verifyCredential, getStatus functions
|
|
31
|
+
- `cashclaw hyrve` subcommand suite
|
|
32
|
+
- `hyrve status` -- connection status + MPP availability
|
|
33
|
+
- `hyrve jobs` -- list available marketplace jobs
|
|
34
|
+
- `hyrve wallet` -- wallet balance check
|
|
35
|
+
- `hyrve dashboard` -- open app.hyrveai.com in browser
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Updated README with MPP section and hyrve commands
|
|
39
|
+
- Stats: 111 stars, 34 forks, 3,000+ registered users
|
|
40
|
+
|
|
5
41
|
## [1.3.0] - 2026-03-19
|
|
6
42
|
|
|
7
43
|
### Added
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://www.npmjs.com/package/cashclaw"><img src="https://img.shields.io/npm/v/cashclaw?color=crimson&label=npm" alt="npm version" /></a>
|
|
17
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
17
|
+
<img src="https://img.shields.io/badge/version-1.4.0-blue" alt="v1.4.0" />
|
|
18
18
|
<a href="https://github.com/ertugrulakben/cashclaw/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license" /></a>
|
|
19
19
|
<a href="https://github.com/ertugrulakben/cashclaw/stargazers"><img src="https://img.shields.io/github/stars/ertugrulakben/cashclaw?style=social" alt="stars" /></a>
|
|
20
20
|
<a href="https://hyrveai.com"><img src="https://img.shields.io/badge/marketplace-HYRVE%20AI-ff6b35" alt="HYRVE AI" /></a>
|
|
@@ -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
|
|
@@ -113,7 +124,7 @@ cashclaw audit --url "https://your-client.com" --tier standard
|
|
|
113
124
|
|
|
114
125
|
## HYRVE AI Integration
|
|
115
126
|
|
|
116
|
-
CashClaw v1.
|
|
127
|
+
CashClaw v1.4.0 connects directly to the **live HYRVE AI marketplace** via authenticated API.
|
|
117
128
|
|
|
118
129
|
| Component | URL |
|
|
119
130
|
|-----------|-----|
|
|
@@ -185,6 +196,26 @@ When connected to HYRVE AI, your agent automatically:
|
|
|
185
196
|
|
|
186
197
|
No cold outreach needed. Clients come to you.
|
|
187
198
|
|
|
199
|
+
### Machine Payments Protocol (MPP)
|
|
200
|
+
|
|
201
|
+
CashClaw v1.4.0 supports Stripe's new [Machine Payments Protocol](https://mpp.dev) -- enabling agents to pay each other autonomously using USDC stablecoins.
|
|
202
|
+
|
|
203
|
+
- **1.5% fees** (vs 2.9%+$0.30 for cards)
|
|
204
|
+
- HTTP 402 Payment Required flow
|
|
205
|
+
- Agent-to-agent micropayments
|
|
206
|
+
- Stripe Dashboard compatible
|
|
207
|
+
|
|
208
|
+
Reference: [stripe-samples/machine-payments](https://github.com/stripe-samples/machine-payments)
|
|
209
|
+
|
|
210
|
+
### HYRVE Marketplace Commands
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
cashclaw hyrve status # Check connection to HYRVE AI
|
|
214
|
+
cashclaw hyrve jobs # List available marketplace jobs
|
|
215
|
+
cashclaw hyrve wallet # Check wallet balance
|
|
216
|
+
cashclaw hyrve dashboard # Open HYRVE dashboard in browser
|
|
217
|
+
```
|
|
218
|
+
|
|
188
219
|
## Mission Audit Trail
|
|
189
220
|
|
|
190
221
|
Every mission is logged end-to-end. No invoice goes out without proof.
|
|
@@ -354,8 +385,11 @@ cashclaw/
|
|
|
354
385
|
bin/ # CLI entry point
|
|
355
386
|
src/ # Core engine source
|
|
356
387
|
integrations/
|
|
357
|
-
hyrve-bridge.js # HYRVE AI marketplace bridge (v1.
|
|
388
|
+
hyrve-bridge.js # HYRVE AI marketplace bridge (v1.4.0)
|
|
389
|
+
mpp-bridge.js # Machine Payments Protocol bridge (v1.4.0)
|
|
358
390
|
cli/
|
|
391
|
+
commands/
|
|
392
|
+
hyrve.js # HYRVE AI subcommands (v1.4.0)
|
|
359
393
|
utils/
|
|
360
394
|
config.js # Configuration management
|
|
361
395
|
skills/
|
|
@@ -380,6 +414,19 @@ cashclaw/
|
|
|
380
414
|
README.md
|
|
381
415
|
```
|
|
382
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
|
+
|
|
383
430
|
## Built By
|
|
384
431
|
|
|
385
432
|
Built by [Ertugrul Akben](https://ertugrulakben.com) and the team at [EAGM Group](https://eagmgroup.com).
|
package/package.json
CHANGED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { loadConfig } from '../utils/config.js';
|
|
7
|
+
import { showMiniBanner } from '../utils/banner.js';
|
|
8
|
+
import { listAvailableJobs, listOrders, acceptJob, deliverJob, getAgentProfile, getWallet } from '../../integrations/hyrve-bridge.js';
|
|
9
|
+
import MppBridge from '../../integrations/mpp-bridge.js';
|
|
10
|
+
|
|
11
|
+
export function createHyrveCommand() {
|
|
12
|
+
const hyrve = new Command('hyrve')
|
|
13
|
+
.description('HYRVE AI Marketplace commands');
|
|
14
|
+
|
|
15
|
+
hyrve
|
|
16
|
+
.command('status')
|
|
17
|
+
.description('Check HYRVE connection status')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const config = await loadConfig();
|
|
20
|
+
const spinner = ora('Checking HYRVE connection...').start();
|
|
21
|
+
try {
|
|
22
|
+
const mpp = new MppBridge(config);
|
|
23
|
+
|
|
24
|
+
const [apiStatus, mppStatus] = await Promise.all([
|
|
25
|
+
fetch(`${config?.hyrve?.api_url || 'https://api.hyrveai.com/v1'}/health`)
|
|
26
|
+
.then(r => r.json()).catch(() => ({ status: 'error' })),
|
|
27
|
+
mpp.getStatus(),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
spinner.stop();
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(chalk.bold(' HYRVE AI Connection Status'));
|
|
33
|
+
console.log(chalk.dim(' ─────────────────────────'));
|
|
34
|
+
console.log(` API: ${apiStatus.status === 'ok' ? chalk.green('● Connected') : chalk.red('● Disconnected')}`);
|
|
35
|
+
console.log(` API URL: ${chalk.dim(config?.hyrve?.api_url || 'https://api.hyrveai.com/v1')}`);
|
|
36
|
+
console.log(` Agent ID: ${config?.hyrve?.agent_id ? chalk.cyan(config.hyrve.agent_id) : chalk.yellow('Not registered')}`);
|
|
37
|
+
console.log(` API Key: ${config?.hyrve?.api_key ? chalk.green('● Set') : chalk.yellow('● Not set')}`);
|
|
38
|
+
console.log(` MPP: ${mppStatus.connected ? chalk.green('● Available (USDC, 1.5% fee)') : chalk.yellow('● Pending')}`);
|
|
39
|
+
console.log(` Dashboard: ${chalk.dim('https://app.hyrveai.com')}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
spinner.fail('Connection check failed: ' + err.message);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
hyrve
|
|
47
|
+
.command('jobs')
|
|
48
|
+
.description('List available jobs on HYRVE marketplace')
|
|
49
|
+
.action(async () => {
|
|
50
|
+
const spinner = ora('Fetching available jobs...').start();
|
|
51
|
+
try {
|
|
52
|
+
const result = await listAvailableJobs();
|
|
53
|
+
spinner.stop();
|
|
54
|
+
|
|
55
|
+
if (!result.jobs || result.jobs.length === 0) {
|
|
56
|
+
console.log(chalk.yellow('\n No matching jobs found.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.bold(`\n Available Jobs (${result.jobs.length})\n`));
|
|
61
|
+
for (const job of result.jobs) {
|
|
62
|
+
console.log(` ${chalk.cyan(job.title)}`);
|
|
63
|
+
console.log(` ${chalk.dim(job.description?.substring(0, 80))}...`);
|
|
64
|
+
console.log(` Budget: ${chalk.green('$' + job.budget_usd)} | Category: ${job.category} | ID: ${chalk.dim(job.id)}`);
|
|
65
|
+
console.log('');
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
spinner.fail('Failed: ' + err.message);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
hyrve
|
|
73
|
+
.command('wallet')
|
|
74
|
+
.description('Check HYRVE wallet balance')
|
|
75
|
+
.action(async () => {
|
|
76
|
+
const spinner = ora('Fetching wallet...').start();
|
|
77
|
+
try {
|
|
78
|
+
const result = await listOrders({ status: 'completed', limit: 5 });
|
|
79
|
+
spinner.stop();
|
|
80
|
+
console.log(chalk.bold('\n HYRVE Wallet'));
|
|
81
|
+
console.log(chalk.dim(' ──────────────'));
|
|
82
|
+
console.log(` Open dashboard for details: ${chalk.cyan('https://app.hyrveai.com/wallet')}`);
|
|
83
|
+
console.log('');
|
|
84
|
+
} catch (err) {
|
|
85
|
+
spinner.fail('Failed: ' + err.message);
|
|
86
|
+
}
|
|
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
|
+
|
|
166
|
+
hyrve
|
|
167
|
+
.command('dashboard')
|
|
168
|
+
.description('Open HYRVE AI dashboard in browser')
|
|
169
|
+
.action(async () => {
|
|
170
|
+
const url = 'https://app.hyrveai.com';
|
|
171
|
+
console.log(chalk.cyan(`\n Opening ${url}...\n`));
|
|
172
|
+
const { exec } = await import('child_process');
|
|
173
|
+
const cmd = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
|
|
174
|
+
exec(cmd);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return hyrve;
|
|
178
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { runAudit } from './commands/audit.js';
|
|
|
9
9
|
import { listMissions, createMission, startMission, completeMission, cancelMission, getMission, getMissionTrail, exportMissionProof } from '../engine/mission-runner.js';
|
|
10
10
|
import { getTotal, getMonthly, getWeekly, getToday, getHistory, getByService } from '../engine/earnings-tracker.js';
|
|
11
11
|
import { listInstalledSkills, listAvailableSkills, installSkills } from '../integrations/openclaw-bridge.js';
|
|
12
|
+
import { createHyrveCommand } from './commands/hyrve.js';
|
|
12
13
|
import Table from 'cli-table3';
|
|
13
14
|
import fs from 'fs-extra';
|
|
14
15
|
import path from 'path';
|
|
@@ -564,6 +565,9 @@ program
|
|
|
564
565
|
}
|
|
565
566
|
});
|
|
566
567
|
|
|
568
|
+
// ─── cashclaw hyrve ───────────────────────────────────────────────────
|
|
569
|
+
program.addCommand(createHyrveCommand());
|
|
570
|
+
|
|
567
571
|
// ─── Default action (no command) ───────────────────────────────────────
|
|
568
572
|
program.action(() => {
|
|
569
573
|
showBanner();
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { loadConfig } from '../cli/utils/config.js';
|
|
4
|
+
|
|
5
|
+
const MPP_SPEC_URL = 'https://mpp.dev';
|
|
6
|
+
const STRIPE_MPP_DOCS = 'https://docs.stripe.com/payments/machine';
|
|
7
|
+
|
|
8
|
+
export class MppBridge {
|
|
9
|
+
constructor(config = null) {
|
|
10
|
+
this.config = config || null;
|
|
11
|
+
this.apiUrl = config?.hyrve?.api_url || 'https://api.hyrveai.com/v1';
|
|
12
|
+
this.apiKey = config?.hyrve?.api_key || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getHeaders() {
|
|
16
|
+
const headers = { 'Content-Type': 'application/json', 'User-Agent': 'CashClaw/1.4.0' };
|
|
17
|
+
if (this.apiKey) headers['X-API-Key'] = this.apiKey;
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async createChallenge(agentId, amountUsd, currency = 'usdc') {
|
|
22
|
+
const res = await fetch(`${this.apiUrl}/payments/mpp/challenge`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: this.getHeaders(),
|
|
25
|
+
body: JSON.stringify({ agent_id: agentId, amount_usd: amountUsd, currency }),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const err = await res.json().catch(() => ({ message: 'MPP challenge failed' }));
|
|
29
|
+
throw new Error(err.error?.message || err.message || `HTTP ${res.status}`);
|
|
30
|
+
}
|
|
31
|
+
return res.json();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async verifyCredential(credential) {
|
|
35
|
+
const res = await fetch(`${this.apiUrl}/payments/mpp/verify`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: this.getHeaders(),
|
|
38
|
+
body: JSON.stringify({ credential }),
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const err = await res.json().catch(() => ({ message: 'MPP verify failed' }));
|
|
42
|
+
throw new Error(err.error?.message || err.message || `HTTP ${res.status}`);
|
|
43
|
+
}
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getStatus() {
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${this.apiUrl}/health`, { headers: this.getHeaders() });
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
return {
|
|
52
|
+
connected: res.ok,
|
|
53
|
+
api_status: data.status,
|
|
54
|
+
mpp_enabled: true,
|
|
55
|
+
mpp_spec: MPP_SPEC_URL,
|
|
56
|
+
stripe_docs: STRIPE_MPP_DOCS,
|
|
57
|
+
supported_currencies: ['usdc'],
|
|
58
|
+
supported_networks: ['tempo', 'base', 'solana'],
|
|
59
|
+
fee_rate: '1.5%',
|
|
60
|
+
};
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return { connected: false, error: err.message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default MppBridge;
|