clawpowers 1.1.1 → 1.1.3
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/README.md +48 -2
- package/bin/clawpowers.js +254 -19
- package/docs/launch-images/25-skills-breakdown.jpg +0 -0
- package/docs/launch-images/clawpowers-vs-superpowers.jpg +0 -0
- package/docs/launch-images/economic-code-optimization.jpg +0 -0
- package/docs/launch-images/native-vs-bridge-2.jpg +0 -0
- package/docs/launch-images/native-vs-bridge.jpg +0 -0
- package/docs/launch-images/ultimate-stack.jpg +0 -0
- package/docs/quickstart-first-transaction.md +204 -0
- package/package.json +1 -1
- package/runtime/demo/README.md +78 -0
- package/runtime/demo/x402-mock-server.js +230 -0
- package/runtime/init.js +40 -2
- package/runtime/init.sh +34 -1
- package/runtime/payments/ledger.js +305 -0
- package/runtime/payments/ledger.sh +262 -0
- package/runtime/payments/pipeline.js +459 -0
- package/skill.json +38 -6
- package/skills/agent-bounties/SKILL.md +553 -0
- package/skills/agent-payments/SKILL.md +68 -0
- package/skills/market-intelligence/SKILL.md +35 -0
- package/skills/prospecting/SKILL.md +141 -0
- package/skills/security-audit/SKILL.md +45 -0
- package/skills/using-clawpowers/SKILL.md +9 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# x402 Demo — Agent Payments in 60 Seconds
|
|
2
|
+
|
|
3
|
+
HTTP has a 402 status code that basically nobody used — until agents needed to pay.
|
|
4
|
+
|
|
5
|
+
This demo runs a local mock x402 merchant so you can see the full payment flow without any real money.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Terminal 1: Start the mock merchant
|
|
11
|
+
npx clawpowers demo x402
|
|
12
|
+
|
|
13
|
+
# Terminal 2: Hit the paid endpoint
|
|
14
|
+
curl http://localhost:PORT/api/premium-data
|
|
15
|
+
# → Returns 402 with payment requirements
|
|
16
|
+
|
|
17
|
+
# Simulate payment
|
|
18
|
+
curl -H "x-payment: mock-proof" http://localhost:PORT/api/premium-data
|
|
19
|
+
# → Returns 200 with data
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What's Happening
|
|
23
|
+
|
|
24
|
+
1. Your agent calls a premium API
|
|
25
|
+
2. The server returns **HTTP 402 Payment Required** with x402 payment requirements (amount, asset, recipient, network)
|
|
26
|
+
3. ClawPowers evaluates the payment against your spending policy (limits, allowlist, mode)
|
|
27
|
+
4. In **dry-run mode**: logs what would happen, no funds move
|
|
28
|
+
5. In **live mode**: signs a payment proof via `agentwallet-sdk`, sends payment, retries the request
|
|
29
|
+
6. Server validates payment proof and returns the data
|
|
30
|
+
|
|
31
|
+
## The x402 Flow
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Agent Mock Merchant
|
|
35
|
+
| |
|
|
36
|
+
|-- GET /api/premium-data ->|
|
|
37
|
+
|<- 402 + requirements -----|
|
|
38
|
+
| |
|
|
39
|
+
| [evaluate policy] |
|
|
40
|
+
| [sign payment proof] |
|
|
41
|
+
| |
|
|
42
|
+
|-- GET + x-payment ------->|
|
|
43
|
+
|<- 200 + data -------------|
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Payment Requirements Format
|
|
47
|
+
|
|
48
|
+
The 402 response includes a JSON body:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"x402Version": 1,
|
|
53
|
+
"accepts": [{
|
|
54
|
+
"scheme": "exact",
|
|
55
|
+
"network": "base-sepolia",
|
|
56
|
+
"maxAmountRequired": "100000",
|
|
57
|
+
"resource": "https://localhost:PORT/api/premium-data",
|
|
58
|
+
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
59
|
+
"payTo": "0xff86829393C6C26A4EC122bE0Cc3E466Ef876AdD"
|
|
60
|
+
}],
|
|
61
|
+
"error": "Payment Required"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Check Payment Logs
|
|
66
|
+
|
|
67
|
+
After running the demo, inspect the payment decisions:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx clawpowers payments log
|
|
71
|
+
npx clawpowers payments summary
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Next Steps
|
|
75
|
+
|
|
76
|
+
- Read the [agent-payments skill](../../skills/agent-payments/SKILL.md) for full methodology
|
|
77
|
+
- Run `npx clawpowers payments setup` to configure spending limits
|
|
78
|
+
- See the [Security Model](../../README.md#security-model) for guardrails
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// runtime/demo/x402-mock-server.js — x402 Mock Merchant Demo Server
|
|
3
|
+
//
|
|
4
|
+
// Starts a local HTTP server that demonstrates the x402 payment-required flow:
|
|
5
|
+
// 1. GET /api/premium-data without payment header → 402 Payment Required
|
|
6
|
+
// 2. GET /api/premium-data with 'x-payment: mock-proof' header → 200 OK with data
|
|
7
|
+
//
|
|
8
|
+
// This server is for DEMO and DEVELOPMENT ONLY — it does not process real payments.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// node runtime/demo/x402-mock-server.js
|
|
12
|
+
// npx clawpowers demo x402
|
|
13
|
+
//
|
|
14
|
+
// The server picks a random available port and prints instructions on startup.
|
|
15
|
+
// Press Ctrl+C to stop.
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const http = require('http');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
// Track requests for demo visibility
|
|
22
|
+
let requestCount = 0;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns an ISO 8601 timestamp without milliseconds.
|
|
26
|
+
*
|
|
27
|
+
* @returns {string} e.g. "2026-03-22T21:42:00Z"
|
|
28
|
+
*/
|
|
29
|
+
function isoTimestamp() {
|
|
30
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Logs a request to stdout with timestamp and key details.
|
|
35
|
+
* Allows the demo runner to see exactly what is happening.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} method - HTTP method (GET, POST, etc.)
|
|
38
|
+
* @param {string} url - Request URL path
|
|
39
|
+
* @param {number} status - Response HTTP status code
|
|
40
|
+
* @param {string} [note=''] - Optional human-readable note about this request
|
|
41
|
+
*/
|
|
42
|
+
function logRequest(method, url, status, note = '') {
|
|
43
|
+
const ts = isoTimestamp();
|
|
44
|
+
const noteStr = note ? ` — ${note}` : '';
|
|
45
|
+
console.log(` [${ts}] ${method} ${url} → ${status}${noteStr}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds the x402 Payment Required response body for the mock merchant.
|
|
50
|
+
* This mimics the response a real x402-compliant API would return.
|
|
51
|
+
*
|
|
52
|
+
* @param {number} port - The port this server is running on (embedded in the resource URL).
|
|
53
|
+
* @returns {object} x402 payment requirements object.
|
|
54
|
+
*/
|
|
55
|
+
function buildPaymentRequired(port) {
|
|
56
|
+
return {
|
|
57
|
+
x402Version: 1,
|
|
58
|
+
accepts: [
|
|
59
|
+
{
|
|
60
|
+
scheme: 'exact',
|
|
61
|
+
network: 'base-sepolia',
|
|
62
|
+
maxAmountRequired: '100000',
|
|
63
|
+
resource: `http://localhost:${port}/api/premium-data`,
|
|
64
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
65
|
+
payTo: '0xff86829393C6C26A4EC122bE0Cc3E466Ef876AdD',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
error: 'Payment Required',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds the mock premium data response returned after (simulated) payment.
|
|
74
|
+
*
|
|
75
|
+
* @returns {object} Mock API data payload.
|
|
76
|
+
*/
|
|
77
|
+
function buildPremiumData() {
|
|
78
|
+
return {
|
|
79
|
+
status: 'ok',
|
|
80
|
+
data: {
|
|
81
|
+
market: 'AGENT/USDC',
|
|
82
|
+
price: '4.20',
|
|
83
|
+
volume_24h: '1234567.89',
|
|
84
|
+
change_24h: '+12.3%',
|
|
85
|
+
source: 'mock-premium-api',
|
|
86
|
+
paid_with: 'x402',
|
|
87
|
+
timestamp: isoTimestamp(),
|
|
88
|
+
},
|
|
89
|
+
message: 'Payment accepted. Here is your premium data.',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Main HTTP request handler for the mock x402 merchant.
|
|
95
|
+
*
|
|
96
|
+
* Routes:
|
|
97
|
+
* GET /api/premium-data — Returns 402 or 200 depending on x-payment header
|
|
98
|
+
* GET / — Returns a simple HTML help page
|
|
99
|
+
* All others — Returns 404
|
|
100
|
+
*
|
|
101
|
+
* @param {http.IncomingMessage} req - Incoming HTTP request
|
|
102
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
103
|
+
*/
|
|
104
|
+
function handleRequest(req, res) {
|
|
105
|
+
requestCount++;
|
|
106
|
+
const url = req.url || '/';
|
|
107
|
+
const method = req.method || 'GET';
|
|
108
|
+
|
|
109
|
+
// CORS headers — allow curl and browser requests during demo
|
|
110
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
111
|
+
res.setHeader('Access-Control-Allow-Headers', 'x-payment, content-type');
|
|
112
|
+
|
|
113
|
+
if (url === '/api/premium-data' && method === 'GET') {
|
|
114
|
+
const paymentHeader = req.headers['x-payment'];
|
|
115
|
+
|
|
116
|
+
if (!paymentHeader) {
|
|
117
|
+
// No payment header — return 402 Payment Required with x402 spec body
|
|
118
|
+
const body = JSON.stringify(buildPaymentRequired(server.address().port), null, 2);
|
|
119
|
+
res.setHeader('Content-Type', 'application/json');
|
|
120
|
+
res.writeHead(402);
|
|
121
|
+
res.end(body);
|
|
122
|
+
logRequest(method, url, 402, 'No x-payment header — returning payment requirements');
|
|
123
|
+
} else {
|
|
124
|
+
// Payment header present — simulate successful payment verification
|
|
125
|
+
const body = JSON.stringify(buildPremiumData(), null, 2);
|
|
126
|
+
res.setHeader('Content-Type', 'application/json');
|
|
127
|
+
res.writeHead(200);
|
|
128
|
+
res.end(body);
|
|
129
|
+
logRequest(method, url, 200, `x-payment: ${paymentHeader} — payment accepted, returning data`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
} else if (url === '/' && method === 'GET') {
|
|
133
|
+
// Help page — shows curl examples
|
|
134
|
+
const port = server.address().port;
|
|
135
|
+
const body = [
|
|
136
|
+
'<html><body><pre>',
|
|
137
|
+
'x402 Mock Merchant Demo',
|
|
138
|
+
'=======================',
|
|
139
|
+
'',
|
|
140
|
+
'Try these curl commands:',
|
|
141
|
+
'',
|
|
142
|
+
`# Step 1: Request without payment (returns 402)`,
|
|
143
|
+
`curl -i http://localhost:${port}/api/premium-data`,
|
|
144
|
+
'',
|
|
145
|
+
`# Step 2: Request with payment header (returns 200 + data)`,
|
|
146
|
+
`curl -i -H "x-payment: mock-proof" http://localhost:${port}/api/premium-data`,
|
|
147
|
+
'</pre></body></html>',
|
|
148
|
+
].join('\n');
|
|
149
|
+
res.setHeader('Content-Type', 'text/html');
|
|
150
|
+
res.writeHead(200);
|
|
151
|
+
res.end(body);
|
|
152
|
+
logRequest(method, url, 200, 'help page');
|
|
153
|
+
|
|
154
|
+
} else {
|
|
155
|
+
res.setHeader('Content-Type', 'application/json');
|
|
156
|
+
res.writeHead(404);
|
|
157
|
+
res.end(JSON.stringify({ error: 'Not Found', path: url }));
|
|
158
|
+
logRequest(method, url, 404);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Finds a random available port by binding to port 0 and reading what the OS assigned.
|
|
164
|
+
* Returns a Promise that resolves to the chosen port number.
|
|
165
|
+
*
|
|
166
|
+
* @returns {Promise<number>} Available port number.
|
|
167
|
+
*/
|
|
168
|
+
function getAvailablePort() {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const tmp = http.createServer();
|
|
171
|
+
tmp.listen(0, '127.0.0.1', () => {
|
|
172
|
+
const port = tmp.address().port;
|
|
173
|
+
tmp.close(() => resolve(port));
|
|
174
|
+
});
|
|
175
|
+
tmp.on('error', reject);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The server object is referenced inside handleRequest for the port — declare before use
|
|
180
|
+
/** @type {http.Server} */
|
|
181
|
+
const server = http.createServer(handleRequest);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Main entry point: picks a port, starts the server, and prints instructions.
|
|
185
|
+
* Waits for Ctrl+C (SIGINT) or SIGTERM to shut down cleanly.
|
|
186
|
+
*/
|
|
187
|
+
async function main() {
|
|
188
|
+
let port;
|
|
189
|
+
try {
|
|
190
|
+
port = await getAvailablePort();
|
|
191
|
+
} catch (_) {
|
|
192
|
+
// If dynamic port detection fails, fall back to a fixed high port
|
|
193
|
+
port = 18402;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
server.listen(port, '127.0.0.1', () => {
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log('╔══════════════════════════════════════════════════════════════╗');
|
|
199
|
+
console.log('║ x402 Mock Merchant — Demo Server ║');
|
|
200
|
+
console.log('╚══════════════════════════════════════════════════════════════╝');
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(` Mock x402 merchant running on http://localhost:${port}`);
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(' Try:');
|
|
205
|
+
console.log(` curl http://localhost:${port}/api/premium-data`);
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(' The server will return 402 (Payment Required) on first request.');
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(` curl -H 'x-payment: mock-proof' http://localhost:${port}/api/premium-data`);
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log(" Send with header 'x-payment: mock-proof' to simulate payment.");
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(' Request log:');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Handle Ctrl+C and process termination signals cleanly
|
|
217
|
+
const shutdown = () => {
|
|
218
|
+
console.log('');
|
|
219
|
+
console.log(` Shutting down after ${requestCount} request(s). Goodbye.`);
|
|
220
|
+
server.close(() => process.exit(0));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
process.on('SIGINT', shutdown);
|
|
224
|
+
process.on('SIGTERM', shutdown);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main().catch((err) => {
|
|
228
|
+
process.stderr.write(`Error starting mock server: ${err.message}\n`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
});
|
package/runtime/init.js
CHANGED
|
@@ -13,7 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
|
|
16
|
-
const VERSION = '1.
|
|
16
|
+
const VERSION = '1.1.1';
|
|
17
17
|
|
|
18
18
|
// Runtime root — override with CLAWPOWERS_DIR env var for testing or custom locations
|
|
19
19
|
const CLAWPOWERS_DIR = process.env.CLAWPOWERS_DIR || path.join(os.homedir(), '.clawpowers');
|
|
@@ -96,6 +96,43 @@ function writeReadme() {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Default configuration written to ~/.clawpowers/config.json on first init.
|
|
101
|
+
* Users can edit this file to enable payments, telemetry, or change skill behavior.
|
|
102
|
+
* Never overwritten once created — user settings are always preserved.
|
|
103
|
+
*/
|
|
104
|
+
const DEFAULT_CONFIG = {
|
|
105
|
+
version: VERSION,
|
|
106
|
+
payments: {
|
|
107
|
+
enabled: false,
|
|
108
|
+
mode: 'dry_run',
|
|
109
|
+
per_tx_limit_usd: 0,
|
|
110
|
+
daily_limit_usd: 0,
|
|
111
|
+
weekly_limit_usd: 0,
|
|
112
|
+
allowlist: [],
|
|
113
|
+
require_approval_above_usd: 0,
|
|
114
|
+
},
|
|
115
|
+
telemetry: {
|
|
116
|
+
enabled: false,
|
|
117
|
+
},
|
|
118
|
+
skills: {
|
|
119
|
+
auto_load: true,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Writes the default config.json to CLAWPOWERS_DIR on first initialization.
|
|
125
|
+
* No-op if config.json already exists — user settings are always preserved.
|
|
126
|
+
* The config file is written with mode 0o600 (owner read/write only).
|
|
127
|
+
*/
|
|
128
|
+
function writeConfig() {
|
|
129
|
+
const configFile = path.join(CLAWPOWERS_DIR, 'config.json');
|
|
130
|
+
if (!fs.existsSync(configFile)) {
|
|
131
|
+
const content = JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n';
|
|
132
|
+
fs.writeFileSync(configFile, content, { mode: 0o600 });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
/**
|
|
100
137
|
* Updates the version stamp in .version after initialization.
|
|
101
138
|
* Currently a no-op placeholder for actual schema migrations; the version
|
|
@@ -139,6 +176,7 @@ function main() {
|
|
|
139
176
|
const created = createStructure();
|
|
140
177
|
writeVersion();
|
|
141
178
|
writeReadme();
|
|
179
|
+
writeConfig();
|
|
142
180
|
|
|
143
181
|
// Only run migrations when .version exists (i.e., after writeVersion)
|
|
144
182
|
if (fs.existsSync(path.join(CLAWPOWERS_DIR, '.version'))) {
|
|
@@ -169,4 +207,4 @@ if (require.main === module) {
|
|
|
169
207
|
}
|
|
170
208
|
}
|
|
171
209
|
|
|
172
|
-
module.exports = { main, CLAWPOWERS_DIR, VERSION };
|
|
210
|
+
module.exports = { main, CLAWPOWERS_DIR, VERSION, writeConfig, DEFAULT_CONFIG };
|
package/runtime/init.sh
CHANGED
|
@@ -11,7 +11,7 @@ set -euo pipefail
|
|
|
11
11
|
|
|
12
12
|
# Runtime root — override with CLAWPOWERS_DIR env var for testing or custom locations
|
|
13
13
|
CLAWPOWERS_DIR="${CLAWPOWERS_DIR:-$HOME/.clawpowers}"
|
|
14
|
-
VERSION="1.
|
|
14
|
+
VERSION="1.1.1"
|
|
15
15
|
|
|
16
16
|
## === Directory Setup ===
|
|
17
17
|
|
|
@@ -93,6 +93,38 @@ EOF
|
|
|
93
93
|
fi
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
## === Config File ===
|
|
97
|
+
|
|
98
|
+
# Writes the default config.json on first initialization.
|
|
99
|
+
# No-op if config.json already exists — user settings are always preserved.
|
|
100
|
+
# The file is written with mode 600 (owner read/write only).
|
|
101
|
+
write_config() {
|
|
102
|
+
local config_file="$CLAWPOWERS_DIR/config.json"
|
|
103
|
+
if [[ ! -f "$config_file" ]]; then
|
|
104
|
+
cat > "$config_file" << EOF
|
|
105
|
+
{
|
|
106
|
+
"version": "$VERSION",
|
|
107
|
+
"payments": {
|
|
108
|
+
"enabled": false,
|
|
109
|
+
"mode": "dry_run",
|
|
110
|
+
"per_tx_limit_usd": 0,
|
|
111
|
+
"daily_limit_usd": 0,
|
|
112
|
+
"weekly_limit_usd": 0,
|
|
113
|
+
"allowlist": [],
|
|
114
|
+
"require_approval_above_usd": 0
|
|
115
|
+
},
|
|
116
|
+
"telemetry": {
|
|
117
|
+
"enabled": false
|
|
118
|
+
},
|
|
119
|
+
"skills": {
|
|
120
|
+
"auto_load": true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
EOF
|
|
124
|
+
chmod 600 "$config_file"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
## === Migrations ===
|
|
97
129
|
|
|
98
130
|
# Updates the version stamp in .version to the current version.
|
|
@@ -120,6 +152,7 @@ main() {
|
|
|
120
152
|
|
|
121
153
|
write_version
|
|
122
154
|
write_readme
|
|
155
|
+
write_config
|
|
123
156
|
|
|
124
157
|
# Migrations only apply when the version file exists (guaranteed after write_version)
|
|
125
158
|
if [[ -f "$CLAWPOWERS_DIR/.version" ]]; then
|