clawpowers 1.1.1 → 1.1.2
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 +4 -0
- package/bin/clawpowers.js +227 -8
- 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/package.json +1 -1
- 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/skill.json +14 -3
- package/skills/agent-payments/SKILL.md +68 -0
- package/skills/market-intelligence/SKILL.md +35 -0
- package/skills/security-audit/SKILL.md +45 -0
- package/skills/using-clawpowers/SKILL.md +3 -0
package/README.md
CHANGED
|
@@ -394,6 +394,10 @@ Built by [AI Agent Economy](https://github.com/up2itnow0822) — the team behind
|
|
|
394
394
|
|
|
395
395
|
We welcome contributions. Unlike some frameworks, we don't dismiss legitimate skill proposals with one-word responses. Open an issue or PR — every submission gets a genuine technical review.
|
|
396
396
|
|
|
397
|
+
## Patent Notice
|
|
398
|
+
|
|
399
|
+
**Patent Pending** — The underlying financial infrastructure (agentwallet-sdk, agentpay-mcp) is covered by USPTO provisional patent application filed March 2026: "Non-Custodial Multi-Chain Financial Infrastructure System for Autonomous AI Agents."
|
|
400
|
+
|
|
397
401
|
## License
|
|
398
402
|
|
|
399
403
|
MIT
|
package/bin/clawpowers.js
CHANGED
|
@@ -25,6 +25,7 @@ const ANALYZE_JS = path.join(REPO_ROOT, 'runtime', 'feedback', 'analyze.js');
|
|
|
25
25
|
const STORE_JS = path.join(REPO_ROOT, 'runtime', 'persistence', 'store.js');
|
|
26
26
|
const COLLECTOR_JS = path.join(REPO_ROOT, 'runtime', 'metrics', 'collector.js');
|
|
27
27
|
const SESSION_JS = path.join(REPO_ROOT, 'hooks', 'session-start.js');
|
|
28
|
+
const LEDGER_JS = path.join(REPO_ROOT, 'runtime', 'payments', 'ledger.js');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Prints the top-level command usage to stdout.
|
|
@@ -41,6 +42,8 @@ Commands:
|
|
|
41
42
|
metrics <cmd> Record or query skill execution metrics
|
|
42
43
|
analyze [opts] RSI feedback analysis of skill performance
|
|
43
44
|
store <cmd> Key-value state store operations
|
|
45
|
+
payments <cmd> Payment setup, log, and summary commands
|
|
46
|
+
demo <cmd> Run interactive demos (e.g. x402 mock merchant)
|
|
44
47
|
|
|
45
48
|
Examples:
|
|
46
49
|
npx clawpowers init
|
|
@@ -50,6 +53,10 @@ Examples:
|
|
|
50
53
|
npx clawpowers analyze --skill systematic-debugging
|
|
51
54
|
npx clawpowers store set "my:key" "my value"
|
|
52
55
|
npx clawpowers store get "my:key"
|
|
56
|
+
npx clawpowers payments setup
|
|
57
|
+
npx clawpowers payments log
|
|
58
|
+
npx clawpowers payments summary
|
|
59
|
+
npx clawpowers demo x402
|
|
53
60
|
|
|
54
61
|
Run 'npx clawpowers <command> help' for command-specific help.`);
|
|
55
62
|
}
|
|
@@ -356,6 +363,216 @@ function delegateToNode(script, args) {
|
|
|
356
363
|
process.exit(result.status || 0);
|
|
357
364
|
}
|
|
358
365
|
|
|
366
|
+
/**
|
|
367
|
+
* `clawpowers payments setup` — Interactive wallet activation wizard.
|
|
368
|
+
*
|
|
369
|
+
* Guides the user through enabling or configuring the payment subsystem.
|
|
370
|
+
* Never asks for private keys — those belong in .env.
|
|
371
|
+
* Reads and writes ~/.clawpowers/config.json (created by init if missing).
|
|
372
|
+
*
|
|
373
|
+
* Wizard steps:
|
|
374
|
+
* 1. Explain that payments are optional
|
|
375
|
+
* 2. Ask user to choose a mode: disabled / dry run / live
|
|
376
|
+
* 3. If dry run: update config payments.mode = "dry_run", payments.enabled = true
|
|
377
|
+
* 4. If live: ask for per_tx_limit and daily_limit, then update config
|
|
378
|
+
* 5. Confirm where logs are stored
|
|
379
|
+
*/
|
|
380
|
+
function cmdPaymentsSetup() {
|
|
381
|
+
const readline = require('readline');
|
|
382
|
+
const CLAWPOWERS_DIR = process.env.CLAWPOWERS_DIR || path.join(os.homedir(), '.clawpowers');
|
|
383
|
+
const configFile = path.join(CLAWPOWERS_DIR, 'config.json');
|
|
384
|
+
|
|
385
|
+
// Load existing config or fall back to defaults
|
|
386
|
+
let config = {};
|
|
387
|
+
if (fs.existsSync(configFile)) {
|
|
388
|
+
try {
|
|
389
|
+
config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
390
|
+
} catch (_) {
|
|
391
|
+
config = {};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Ensure payments section exists with safe defaults
|
|
395
|
+
if (!config.payments) {
|
|
396
|
+
config.payments = {
|
|
397
|
+
enabled: false,
|
|
398
|
+
mode: 'dry_run',
|
|
399
|
+
per_tx_limit_usd: 0,
|
|
400
|
+
daily_limit_usd: 0,
|
|
401
|
+
weekly_limit_usd: 0,
|
|
402
|
+
allowlist: [],
|
|
403
|
+
require_approval_above_usd: 0,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const logsPath = path.join(CLAWPOWERS_DIR, 'logs', 'payments.jsonl');
|
|
408
|
+
|
|
409
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
410
|
+
|
|
411
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
412
|
+
|
|
413
|
+
async function run() {
|
|
414
|
+
console.log('');
|
|
415
|
+
console.log('╔══════════════════════════════════════════════════╗');
|
|
416
|
+
console.log('║ ClawPowers Payment Setup Wizard ║');
|
|
417
|
+
console.log('╚══════════════════════════════════════════════════╝');
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log('Payments are optional. ClawPowers works without a wallet.');
|
|
420
|
+
console.log('When enabled, agents can autonomously pay for premium APIs');
|
|
421
|
+
console.log('and services using the x402 protocol.');
|
|
422
|
+
console.log('');
|
|
423
|
+
console.log('⚠ Private keys belong in .env — this wizard never asks for them.');
|
|
424
|
+
console.log('');
|
|
425
|
+
console.log('Choose a mode:');
|
|
426
|
+
console.log(' [1] Keep disabled (default — no payments, no wallet required)');
|
|
427
|
+
console.log(' [2] Enable Dry Run (detect payment gates, log what WOULD happen)');
|
|
428
|
+
console.log(' [3] Enable Live (real payments within configured limits)');
|
|
429
|
+
console.log('');
|
|
430
|
+
|
|
431
|
+
const choice = (await ask('Enter choice [1/2/3]: ')).trim();
|
|
432
|
+
|
|
433
|
+
if (choice === '1' || choice === '') {
|
|
434
|
+
config.payments.enabled = false;
|
|
435
|
+
config.payments.mode = 'dry_run';
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log('✓ Payments remain disabled. No wallet required.');
|
|
438
|
+
|
|
439
|
+
} else if (choice === '2') {
|
|
440
|
+
config.payments.enabled = true;
|
|
441
|
+
config.payments.mode = 'dry_run';
|
|
442
|
+
console.log('');
|
|
443
|
+
console.log('✓ Dry Run mode enabled.');
|
|
444
|
+
console.log(' Agents will detect payment requirements and log what would happen.');
|
|
445
|
+
console.log(' No funds will move. Review the log after 10+ cycles, then');
|
|
446
|
+
console.log(' run this wizard again to go live with confidence.');
|
|
447
|
+
|
|
448
|
+
} else if (choice === '3') {
|
|
449
|
+
console.log('');
|
|
450
|
+
console.log('Live payment mode requires spending limits to protect your wallet.');
|
|
451
|
+
console.log('');
|
|
452
|
+
|
|
453
|
+
const perTxRaw = (await ask('Per-transaction limit (USD, e.g. 0.10): ')).trim();
|
|
454
|
+
const dailyRaw = (await ask('Daily limit (USD, e.g. 5.00): ')).trim();
|
|
455
|
+
|
|
456
|
+
const perTxLimit = parseFloat(perTxRaw) || 0;
|
|
457
|
+
const dailyLimit = parseFloat(dailyRaw) || 0;
|
|
458
|
+
|
|
459
|
+
config.payments.enabled = true;
|
|
460
|
+
config.payments.mode = 'live';
|
|
461
|
+
config.payments.per_tx_limit_usd = perTxLimit;
|
|
462
|
+
config.payments.daily_limit_usd = dailyLimit;
|
|
463
|
+
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log(`✓ Live payments enabled.`);
|
|
466
|
+
console.log(` Per-transaction limit: $${perTxLimit.toFixed(2)}`);
|
|
467
|
+
console.log(` Daily limit: $${dailyLimit.toFixed(2)}`);
|
|
468
|
+
console.log('');
|
|
469
|
+
console.log(' Add your private key or mnemonic to ~/.clawpowers/.env');
|
|
470
|
+
console.log(' (never commit this file — it is gitignored by default)');
|
|
471
|
+
|
|
472
|
+
} else {
|
|
473
|
+
console.log('');
|
|
474
|
+
console.log('Invalid choice. No changes made.');
|
|
475
|
+
rl.close();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Save updated config
|
|
480
|
+
if (!fs.existsSync(CLAWPOWERS_DIR)) {
|
|
481
|
+
fs.mkdirSync(CLAWPOWERS_DIR, { recursive: true, mode: 0o700 });
|
|
482
|
+
}
|
|
483
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
484
|
+
|
|
485
|
+
console.log('');
|
|
486
|
+
console.log(`✓ Configuration saved to: ${configFile}`);
|
|
487
|
+
console.log(` All payment logs are stored locally at: ${logsPath}`);
|
|
488
|
+
console.log('');
|
|
489
|
+
console.log(' Review logs with: npx clawpowers payments log');
|
|
490
|
+
console.log(' See summary with: npx clawpowers payments summary');
|
|
491
|
+
console.log('');
|
|
492
|
+
|
|
493
|
+
rl.close();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
run().catch((err) => {
|
|
497
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
498
|
+
rl.close();
|
|
499
|
+
process.exit(1);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* `clawpowers payments <subcmd> [args]` — Payment ledger and setup commands.
|
|
505
|
+
*
|
|
506
|
+
* Subcommands:
|
|
507
|
+
* setup — Interactive payment mode wizard
|
|
508
|
+
* log — Show recent payment decisions
|
|
509
|
+
* summary — Show payment totals by skill, chain, outcome
|
|
510
|
+
*
|
|
511
|
+
* @param {string[]} args - Remaining argv after 'payments'.
|
|
512
|
+
*/
|
|
513
|
+
function cmdPayments(args) {
|
|
514
|
+
const [subcmd, ...rest] = args;
|
|
515
|
+
|
|
516
|
+
if (!subcmd || subcmd === 'help' || subcmd === '--help' || subcmd === '-h') {
|
|
517
|
+
console.log(`Usage: clawpowers payments <command> [options]
|
|
518
|
+
|
|
519
|
+
Commands:
|
|
520
|
+
setup Interactive payment mode wizard
|
|
521
|
+
log [--limit <n>] Show recent payment decisions (default: last 20)
|
|
522
|
+
summary Show totals by skill, chain, and outcome`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
switch (subcmd) {
|
|
527
|
+
case 'setup':
|
|
528
|
+
cmdPaymentsSetup();
|
|
529
|
+
break;
|
|
530
|
+
case 'log':
|
|
531
|
+
requireModule(LEDGER_JS).cmdLog ? requireModule(LEDGER_JS).cmdLog(rest)
|
|
532
|
+
: delegateToNode(LEDGER_JS, ['log', ...rest]);
|
|
533
|
+
break;
|
|
534
|
+
case 'summary':
|
|
535
|
+
requireModule(LEDGER_JS).cmdSummary ? requireModule(LEDGER_JS).cmdSummary()
|
|
536
|
+
: delegateToNode(LEDGER_JS, ['summary']);
|
|
537
|
+
break;
|
|
538
|
+
default:
|
|
539
|
+
process.stderr.write(`Unknown payments subcommand: ${subcmd}\n`);
|
|
540
|
+
process.stderr.write('Run: npx clawpowers payments help\n');
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* `clawpowers demo x402` — Start the x402 mock merchant server for demo purposes.
|
|
547
|
+
* Delegates to runtime/demo/x402-mock-server.js.
|
|
548
|
+
*
|
|
549
|
+
* @param {string[]} args - Remaining argv after 'demo'.
|
|
550
|
+
*/
|
|
551
|
+
function cmdDemo(args) {
|
|
552
|
+
const [subcmd] = args;
|
|
553
|
+
|
|
554
|
+
if (!subcmd || subcmd === 'help' || subcmd === '--help' || subcmd === '-h') {
|
|
555
|
+
console.log(`Usage: clawpowers demo <command>
|
|
556
|
+
|
|
557
|
+
Commands:
|
|
558
|
+
x402 Start the x402 mock merchant server`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (subcmd === 'x402') {
|
|
563
|
+
const MOCK_SERVER_JS = path.join(REPO_ROOT, 'runtime', 'demo', 'x402-mock-server.js');
|
|
564
|
+
if (!fs.existsSync(MOCK_SERVER_JS)) {
|
|
565
|
+
process.stderr.write(`Error: runtime module not found: ${MOCK_SERVER_JS}\n`);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
delegateToNode(MOCK_SERVER_JS, []);
|
|
569
|
+
} else {
|
|
570
|
+
process.stderr.write(`Unknown demo subcommand: ${subcmd}\n`);
|
|
571
|
+
process.stderr.write('Run: npx clawpowers demo help\n');
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
359
576
|
// ============================================================
|
|
360
577
|
// Main dispatch — parse the first positional argument as the command
|
|
361
578
|
// ============================================================
|
|
@@ -363,16 +580,18 @@ const [cmd, ...args] = process.argv.slice(2);
|
|
|
363
580
|
|
|
364
581
|
try {
|
|
365
582
|
switch (cmd) {
|
|
366
|
-
case 'init':
|
|
367
|
-
case 'status':
|
|
368
|
-
case 'update':
|
|
369
|
-
case 'inject':
|
|
370
|
-
case 'metrics':
|
|
371
|
-
case 'analyze':
|
|
372
|
-
case 'store':
|
|
583
|
+
case 'init': cmdInit(); break;
|
|
584
|
+
case 'status': cmdStatus(); break;
|
|
585
|
+
case 'update': cmdUpdate(); break;
|
|
586
|
+
case 'inject': cmdInject(); break;
|
|
587
|
+
case 'metrics': cmdMetrics(args); break;
|
|
588
|
+
case 'analyze': cmdAnalyze(args); break;
|
|
589
|
+
case 'store': cmdStore(args); break;
|
|
590
|
+
case 'payments': cmdPayments(args); break;
|
|
591
|
+
case 'demo': cmdDemo(args); break;
|
|
373
592
|
case 'help':
|
|
374
593
|
case '-h':
|
|
375
|
-
case '--help':
|
|
594
|
+
case '--help': printUsage(); break;
|
|
376
595
|
// No command or empty string: show usage and exit 1 (non-zero for scripts)
|
|
377
596
|
case undefined:
|
|
378
597
|
case '':
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawpowers",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "The skills framework that actually does something — runtime execution, persistent memory, self-improvement, and autonomous payments for coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AI Agent Economy <https://github.com/up2itnow0822>",
|
|
@@ -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
|