clawpowers 1.1.0 → 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 +18 -2
- 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/economic-code-optimization/SKILL.md +265 -0
- package/skills/market-intelligence/SKILL.md +35 -0
- package/skills/security-audit/SKILL.md +45 -0
- package/skills/using-clawpowers/SKILL.md +7 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ ClawPowers gives your coding agent superpowers that go beyond instructions. Whil
|
|
|
27
27
|
| Windows native support | ✅ | ❌ |
|
|
28
28
|
| Zero dependencies | ✅ | ✅ |
|
|
29
29
|
|
|
30
|
-
**
|
|
30
|
+
**25 skills.** 14 cover everything static frameworks do (TDD, subagent dev, debugging, planning, code review, git worktrees). 6 go where they can't — payments, security, content, prospecting, market intelligence, and metacognitive learning. 4 are things no other framework even attempts — self-healing code, agents that rewrite their own methodology, cross-project knowledge transfer, and property-based formal verification.
|
|
31
31
|
|
|
32
32
|
## Requirements
|
|
33
33
|
|
|
@@ -214,11 +214,23 @@ Static frameworks stop at coding methodology. ClawPowers includes skills for:
|
|
|
214
214
|
| `market-intelligence` | Competitive analysis, trend detection, opportunity scoring | Requires web access, data aggregation, persistent tracking |
|
|
215
215
|
| `prospecting` | Lead generation, contact enrichment, CRM sync | Requires API calls (Exa, Apollo), structured output |
|
|
216
216
|
|
|
217
|
+
### RSI Intelligence Layer (4 skills)
|
|
218
|
+
|
|
219
|
+
These skills don't exist in any other framework. They require runtime execution, persistent state, and self-modification capabilities that static prompt collections can never deliver.
|
|
220
|
+
|
|
221
|
+
| Skill | What It Does | Why This Changes Everything |
|
|
222
|
+
|-------|-------------|----------------------------|
|
|
223
|
+
| `meta-skill-evolution` | Every 50 tasks, analyzes outcome patterns, identifies the weakest skill, surgically rewrites its methodology, version bumps | Your agent's coding discipline improves autonomously over time. After 30 days it's measurably better than any static install |
|
|
224
|
+
| `self-healing-code` | On test failure: captures error → builds hypothesis tree → generates 2+ patches → applies with coverage guard → auto-commits winner | 3-cycle max with rollback. Turns red tests into green tests without human intervention |
|
|
225
|
+
| `cross-project-knowledge` | Persistent pattern library across ALL repos. Bug fixes, architecture decisions, and performance optimizations transfer between projects | Agent working on Project B benefits from everything learned on Projects A, C, D. Knowledge compounds |
|
|
226
|
+
| `formal-verification-lite` | Property-based testing with fast-check (JS), Hypothesis (Python), QuickCheck (Haskell). 5 property templates, 1000+ examples per property | Goes beyond "tests pass" to "tests actually prove correctness." Catches edge cases unit tests miss |
|
|
227
|
+
| `economic-code-optimization` | Autonomously spends micro-budgets on premium models, cloud GPUs, expert reviews when ROI justifies it. Tracks every cent and learns optimal spend ratios | Agents literally invest in their own performance. Spending efficiency improves over time via RSI feedback loop |
|
|
228
|
+
|
|
217
229
|
## Architecture
|
|
218
230
|
|
|
219
231
|
```
|
|
220
232
|
clawpowers/
|
|
221
|
-
├── skills/ #
|
|
233
|
+
├── skills/ # 25 skill directories, each with SKILL.md
|
|
222
234
|
├── runtime/
|
|
223
235
|
│ ├── persistence/ # Cross-session state (store.js + store.sh)
|
|
224
236
|
│ ├── metrics/ # Outcome tracking (collector.js + collector.sh)
|
|
@@ -382,6 +394,10 @@ Built by [AI Agent Economy](https://github.com/up2itnow0822) — the team behind
|
|
|
382
394
|
|
|
383
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.
|
|
384
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
|
+
|
|
385
401
|
## License
|
|
386
402
|
|
|
387
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 };
|