clawpowers 1.1.4 → 2.2.0
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 +126 -0
- package/COMPATIBILITY.md +13 -0
- package/KNOWN_LIMITATIONS.md +19 -0
- package/LICENSE +44 -0
- package/LICENSING.md +10 -0
- package/README.md +378 -210
- package/SECURITY.md +52 -0
- package/dist/index.d.ts +1477 -0
- package/dist/index.js +3464 -0
- package/dist/index.js.map +1 -0
- package/native/Cargo.lock +4863 -0
- package/native/Cargo.toml +73 -0
- package/native/crates/canonical/Cargo.toml +24 -0
- package/native/crates/canonical/src/lib.rs +673 -0
- package/native/crates/compression/Cargo.toml +20 -0
- package/native/crates/compression/benches/compression_bench.rs +42 -0
- package/native/crates/compression/src/lib.rs +393 -0
- package/native/crates/evm-eth/Cargo.toml +13 -0
- package/native/crates/evm-eth/src/lib.rs +105 -0
- package/native/crates/fee/Cargo.toml +15 -0
- package/native/crates/fee/src/lib.rs +281 -0
- package/native/crates/index/Cargo.toml +16 -0
- package/native/crates/index/src/lib.rs +277 -0
- package/native/crates/policy/Cargo.toml +17 -0
- package/native/crates/policy/src/lib.rs +614 -0
- package/native/crates/security/Cargo.toml +22 -0
- package/native/crates/security/src/lib.rs +478 -0
- package/native/crates/tokens/Cargo.toml +13 -0
- package/native/crates/tokens/src/lib.rs +534 -0
- package/native/crates/verification/Cargo.toml +23 -0
- package/native/crates/verification/src/lib.rs +333 -0
- package/native/crates/wallet/Cargo.toml +20 -0
- package/native/crates/wallet/src/lib.rs +261 -0
- package/native/crates/x402/Cargo.toml +30 -0
- package/native/crates/x402/src/lib.rs +423 -0
- package/native/ffi/Cargo.toml +34 -0
- package/native/ffi/build.rs +4 -0
- package/native/ffi/index.node +0 -0
- package/native/ffi/src/lib.rs +352 -0
- package/native/ffi/tests/integration.rs +354 -0
- package/native/pyo3/Cargo.toml +26 -0
- package/native/pyo3/pyproject.toml +16 -0
- package/native/pyo3/src/lib.rs +407 -0
- package/native/pyo3/tests/test_smoke.py +180 -0
- package/native/wasm/Cargo.toml +44 -0
- package/native/wasm/pkg/.gitignore +6 -0
- package/native/wasm/pkg/clawpowers_wasm.d.ts +208 -0
- package/native/wasm/pkg/clawpowers_wasm.js +872 -0
- package/native/wasm/pkg/clawpowers_wasm_bg.wasm +0 -0
- package/native/wasm/pkg/clawpowers_wasm_bg.wasm.d.ts +40 -0
- package/native/wasm/pkg/package.json +17 -0
- package/native/wasm/pkg-node/.gitignore +6 -0
- package/native/wasm/pkg-node/clawpowers_wasm.d.ts +143 -0
- package/native/wasm/pkg-node/clawpowers_wasm.js +798 -0
- package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm +0 -0
- package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm.d.ts +40 -0
- package/native/wasm/pkg-node/package.json +13 -0
- package/native/wasm/src/lib.rs +433 -0
- package/package.json +71 -44
- package/src/skills/catalog.ts +435 -0
- package/src/skills/executor.ts +56 -0
- package/src/skills/index.ts +3 -0
- package/src/skills/itp/SKILL.md +112 -0
- package/src/skills/loader.ts +193 -0
- package/.claude-plugin/manifest.json +0 -19
- package/.codex/INSTALL.md +0 -36
- package/.cursor-plugin/manifest.json +0 -21
- package/.opencode/INSTALL.md +0 -52
- package/ARCHITECTURE.md +0 -69
- package/bin/clawpowers.js +0 -625
- package/bin/clawpowers.sh +0 -91
- package/docs/demo/clawpowers-demo.cast +0 -197
- package/docs/demo/clawpowers-demo.gif +0 -0
- 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/post1-hero-lobster.jpg +0 -0
- package/docs/launch-images/post2-dashboard.jpg +0 -0
- package/docs/launch-images/post3-superpowers.jpg +0 -0
- package/docs/launch-images/post4-before-after.jpg +0 -0
- package/docs/launch-images/post5-install-now.jpg +0 -0
- package/docs/launch-images/ultimate-stack.jpg +0 -0
- package/docs/launch-posts.md +0 -76
- package/docs/quickstart-first-transaction.md +0 -204
- package/gemini-extension.json +0 -32
- package/hooks/session-start +0 -205
- package/hooks/session-start.cmd +0 -43
- package/hooks/session-start.js +0 -163
- package/runtime/demo/README.md +0 -78
- package/runtime/demo/x402-mock-server.js +0 -230
- package/runtime/feedback/analyze.js +0 -621
- package/runtime/feedback/analyze.sh +0 -546
- package/runtime/init.js +0 -210
- package/runtime/init.sh +0 -178
- package/runtime/metrics/collector.js +0 -361
- package/runtime/metrics/collector.sh +0 -308
- package/runtime/payments/ledger.js +0 -305
- package/runtime/payments/ledger.sh +0 -262
- package/runtime/payments/pipeline.js +0 -455
- package/runtime/persistence/store.js +0 -433
- package/runtime/persistence/store.sh +0 -303
- package/skill.json +0 -106
- package/skills/agent-bounties/SKILL.md +0 -553
- package/skills/agent-payments/SKILL.md +0 -479
- package/skills/brainstorming/SKILL.md +0 -233
- package/skills/content-pipeline/SKILL.md +0 -282
- package/skills/cross-project-knowledge/SKILL.md +0 -345
- package/skills/dispatching-parallel-agents/SKILL.md +0 -305
- package/skills/economic-code-optimization/SKILL.md +0 -265
- package/skills/executing-plans/SKILL.md +0 -255
- package/skills/finishing-a-development-branch/SKILL.md +0 -260
- package/skills/formal-verification-lite/SKILL.md +0 -441
- package/skills/learn-how-to-learn/SKILL.md +0 -235
- package/skills/market-intelligence/SKILL.md +0 -323
- package/skills/meta-skill-evolution/SKILL.md +0 -325
- package/skills/prospecting/SKILL.md +0 -454
- package/skills/receiving-code-review/SKILL.md +0 -225
- package/skills/requesting-code-review/SKILL.md +0 -206
- package/skills/security-audit/SKILL.md +0 -353
- package/skills/self-healing-code/SKILL.md +0 -369
- package/skills/subagent-driven-development/SKILL.md +0 -244
- package/skills/systematic-debugging/SKILL.md +0 -355
- package/skills/test-driven-development/SKILL.md +0 -416
- package/skills/using-clawpowers/SKILL.md +0 -160
- package/skills/using-git-worktrees/SKILL.md +0 -261
- package/skills/validator/SKILL.md +0 -281
- package/skills/verification-before-completion/SKILL.md +0 -254
- package/skills/writing-plans/SKILL.md +0 -276
- package/skills/writing-skills/SKILL.md +0 -260
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// runtime/payments/ledger.js — Payment decision ledger
|
|
3
|
-
//
|
|
4
|
-
// Records every payment decision (approve, deny, dry-run) to a persistent
|
|
5
|
-
// JSONL log at ~/.clawpowers/logs/payments.jsonl. Provides CLI commands to
|
|
6
|
-
// review recent decisions and summarize spending by skill, chain, and outcome.
|
|
7
|
-
//
|
|
8
|
-
// Usage (CLI):
|
|
9
|
-
// node ledger.js log [--limit <n>]
|
|
10
|
-
// node ledger.js summary
|
|
11
|
-
//
|
|
12
|
-
// Usage (module):
|
|
13
|
-
// const { logPaymentDecision, getPaymentSummary } = require('./ledger');
|
|
14
|
-
// await logPaymentDecision({ skill: 'agent-payments', url: '...', ... });
|
|
15
|
-
// const summary = getPaymentSummary();
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const path = require('path');
|
|
20
|
-
const os = require('os');
|
|
21
|
-
|
|
22
|
-
// Logs directory — contains payments.jsonl and other audit logs
|
|
23
|
-
const LOGS_DIR = path.join(
|
|
24
|
-
process.env.CLAWPOWERS_DIR || path.join(os.homedir(), '.clawpowers'),
|
|
25
|
-
'logs'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
/** Absolute path to the payment ledger file. */
|
|
29
|
-
const LEDGER_FILE = path.join(LOGS_DIR, 'payments.jsonl');
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Creates the logs directory if it doesn't already exist.
|
|
33
|
-
* Mode 0o700 restricts access to the current user only.
|
|
34
|
-
*/
|
|
35
|
-
function ensureLogsDir() {
|
|
36
|
-
if (!fs.existsSync(LOGS_DIR)) {
|
|
37
|
-
fs.mkdirSync(LOGS_DIR, { recursive: true, mode: 0o700 });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Returns an ISO 8601 timestamp without milliseconds.
|
|
43
|
-
*
|
|
44
|
-
* @returns {string} e.g. "2026-03-22T21:42:00Z"
|
|
45
|
-
*/
|
|
46
|
-
function isoTimestamp() {
|
|
47
|
-
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Records a payment decision to the JSONL ledger.
|
|
52
|
-
*
|
|
53
|
-
* Each entry captures what happened at a payment gate: the skill that
|
|
54
|
-
* encountered the payment requirement, the URL that required payment,
|
|
55
|
-
* the amount and asset, the policy evaluation result, and whether payment
|
|
56
|
-
* would have been (or was) made.
|
|
57
|
-
*
|
|
58
|
-
* The entry is appended as a single JSON line to `~/.clawpowers/logs/payments.jsonl`.
|
|
59
|
-
* The file is created if it doesn't exist.
|
|
60
|
-
*
|
|
61
|
-
* @param {object} entry - Payment decision details.
|
|
62
|
-
* @param {string} [entry.skill='unknown'] - Skill that triggered the payment gate.
|
|
63
|
-
* @param {string} [entry.type='decision'] - Entry type: 'decision' | 'payment' | 'denial'.
|
|
64
|
-
* @param {string} [entry.url=''] - Resource URL that required payment.
|
|
65
|
-
* @param {string} [entry.required_amount='0'] - Amount required (in smallest asset unit).
|
|
66
|
-
* @param {string} [entry.asset='USDC'] - Asset symbol (e.g. 'USDC', 'ETH').
|
|
67
|
-
* @param {string} [entry.chain='base'] - Chain name (e.g. 'base', 'base-sepolia').
|
|
68
|
-
* @param {string} [entry.policy_result='dry_run'] - Policy outcome: 'dry_run' | 'approved' | 'denied' | 'disabled'.
|
|
69
|
-
* @param {string} [entry.reason=''] - Human-readable reason for the policy result.
|
|
70
|
-
* @param {boolean} [entry.would_have_paid=false] - Whether payment would have succeeded if live.
|
|
71
|
-
* @returns {object} The complete entry as written to the ledger (including timestamp).
|
|
72
|
-
*/
|
|
73
|
-
function logPaymentDecision(entry) {
|
|
74
|
-
ensureLogsDir();
|
|
75
|
-
|
|
76
|
-
/** @type {object} Full ledger record with timestamp and defaults applied. */
|
|
77
|
-
const record = {
|
|
78
|
-
timestamp: isoTimestamp(),
|
|
79
|
-
skill: entry.skill || 'unknown',
|
|
80
|
-
type: entry.type || 'decision',
|
|
81
|
-
url: entry.url || '',
|
|
82
|
-
required_amount: entry.required_amount || '0',
|
|
83
|
-
asset: entry.asset || 'USDC',
|
|
84
|
-
chain: entry.chain || 'base',
|
|
85
|
-
policy_result: entry.policy_result || 'dry_run',
|
|
86
|
-
reason: entry.reason || '',
|
|
87
|
-
would_have_paid: Boolean(entry.would_have_paid),
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Append as a single JSON line — safe for concurrent appends
|
|
91
|
-
const line = JSON.stringify(record) + '\n';
|
|
92
|
-
fs.appendFileSync(LEDGER_FILE, line);
|
|
93
|
-
try { fs.chmodSync(LEDGER_FILE, 0o600); } catch (_) { /* non-fatal on Windows */ }
|
|
94
|
-
|
|
95
|
-
return record;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Reads all payment decision records from the ledger.
|
|
100
|
-
* Malformed JSON lines are silently skipped.
|
|
101
|
-
*
|
|
102
|
-
* @returns {object[]} Array of parsed ledger records in chronological order.
|
|
103
|
-
*/
|
|
104
|
-
function loadLedger() {
|
|
105
|
-
if (!fs.existsSync(LEDGER_FILE)) return [];
|
|
106
|
-
|
|
107
|
-
const content = fs.readFileSync(LEDGER_FILE, 'utf8');
|
|
108
|
-
const records = [];
|
|
109
|
-
for (const line of content.split('\n')) {
|
|
110
|
-
if (!line.trim()) continue;
|
|
111
|
-
try {
|
|
112
|
-
records.push(JSON.parse(line));
|
|
113
|
-
} catch (_) {
|
|
114
|
-
// Skip malformed lines — don't crash on a single bad record
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return records;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Computes a payment summary aggregated by skill, chain, and policy outcome.
|
|
122
|
-
*
|
|
123
|
-
* Returns totals broken down by:
|
|
124
|
-
* - Skill name: which skills hit payment gates most often
|
|
125
|
-
* - Chain: which networks were targeted for payment
|
|
126
|
-
* - Outcome: how many were dry_run vs approved vs denied vs disabled
|
|
127
|
-
*
|
|
128
|
-
* @returns {{
|
|
129
|
-
* total: number,
|
|
130
|
-
* by_skill: Object.<string, number>,
|
|
131
|
-
* by_chain: Object.<string, number>,
|
|
132
|
-
* by_outcome: Object.<string, number>,
|
|
133
|
-
* would_have_paid: number,
|
|
134
|
-
* records: object[]
|
|
135
|
-
* }} Aggregated summary object.
|
|
136
|
-
*/
|
|
137
|
-
function getPaymentSummary() {
|
|
138
|
-
const records = loadLedger();
|
|
139
|
-
|
|
140
|
-
/** @type {Object.<string, number>} Count per skill name. */
|
|
141
|
-
const by_skill = {};
|
|
142
|
-
/** @type {Object.<string, number>} Count per chain name. */
|
|
143
|
-
const by_chain = {};
|
|
144
|
-
/** @type {Object.<string, number>} Count per policy_result value. */
|
|
145
|
-
const by_outcome = {};
|
|
146
|
-
let would_have_paid = 0;
|
|
147
|
-
|
|
148
|
-
for (const r of records) {
|
|
149
|
-
// Tally by skill
|
|
150
|
-
by_skill[r.skill] = (by_skill[r.skill] || 0) + 1;
|
|
151
|
-
// Tally by chain
|
|
152
|
-
by_chain[r.chain] = (by_chain[r.chain] || 0) + 1;
|
|
153
|
-
// Tally by outcome
|
|
154
|
-
by_outcome[r.policy_result] = (by_outcome[r.policy_result] || 0) + 1;
|
|
155
|
-
// Count would-have-paid scenarios (dry-run hits)
|
|
156
|
-
if (r.would_have_paid) would_have_paid++;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
total: records.length,
|
|
161
|
-
by_skill,
|
|
162
|
-
by_chain,
|
|
163
|
-
by_outcome,
|
|
164
|
-
would_have_paid,
|
|
165
|
-
records,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* `log` CLI command — prints recent payment decisions to stdout.
|
|
171
|
-
* Defaults to the last 20 records. Use --limit to change.
|
|
172
|
-
*
|
|
173
|
-
* @param {string[]} argv - Arguments after 'log'.
|
|
174
|
-
*/
|
|
175
|
-
function cmdLog(argv) {
|
|
176
|
-
// Parse optional --limit flag
|
|
177
|
-
let limit = 20;
|
|
178
|
-
for (let i = 0; i < argv.length; i++) {
|
|
179
|
-
if (argv[i] === '--limit' && argv[i + 1]) {
|
|
180
|
-
limit = parseInt(argv[i + 1], 10);
|
|
181
|
-
i++;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const records = loadLedger();
|
|
186
|
-
if (records.length === 0) {
|
|
187
|
-
console.log('No payment decisions recorded yet.');
|
|
188
|
-
console.log(`Ledger location: ${LEDGER_FILE}`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const slice = records.slice(Math.max(0, records.length - limit));
|
|
193
|
-
console.log(`Recent payment decisions (last ${slice.length} of ${records.length}):`);
|
|
194
|
-
console.log('');
|
|
195
|
-
for (const r of slice) {
|
|
196
|
-
const paid = r.would_have_paid ? '[would pay]' : '[would skip]';
|
|
197
|
-
console.log(` ${r.timestamp} | ${r.skill} | ${r.policy_result} ${paid}`);
|
|
198
|
-
if (r.url) console.log(` URL: ${r.url}`);
|
|
199
|
-
if (r.required_amount !== '0') {
|
|
200
|
-
console.log(` Amount: ${r.required_amount} ${r.asset} on ${r.chain}`);
|
|
201
|
-
}
|
|
202
|
-
if (r.reason) console.log(` Reason: ${r.reason}`);
|
|
203
|
-
console.log('');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* `summary` CLI command — prints aggregated payment totals to stdout.
|
|
209
|
-
* Shows totals by skill, chain, and outcome.
|
|
210
|
-
*/
|
|
211
|
-
function cmdSummary() {
|
|
212
|
-
const summary = getPaymentSummary();
|
|
213
|
-
|
|
214
|
-
if (summary.total === 0) {
|
|
215
|
-
console.log('No payment decisions recorded yet.');
|
|
216
|
-
console.log(`Ledger location: ${LEDGER_FILE}`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
console.log(`Payment Decision Summary`);
|
|
221
|
-
console.log(`========================`);
|
|
222
|
-
console.log(`Total decisions: ${summary.total}`);
|
|
223
|
-
console.log(`Would have paid: ${summary.would_have_paid}`);
|
|
224
|
-
console.log('');
|
|
225
|
-
|
|
226
|
-
console.log('By skill:');
|
|
227
|
-
for (const [skill, count] of Object.entries(summary.by_skill).sort()) {
|
|
228
|
-
console.log(` ${skill}: ${count}`);
|
|
229
|
-
}
|
|
230
|
-
console.log('');
|
|
231
|
-
|
|
232
|
-
console.log('By chain:');
|
|
233
|
-
for (const [chain, count] of Object.entries(summary.by_chain).sort()) {
|
|
234
|
-
console.log(` ${chain}: ${count}`);
|
|
235
|
-
}
|
|
236
|
-
console.log('');
|
|
237
|
-
|
|
238
|
-
console.log('By outcome:');
|
|
239
|
-
for (const [outcome, count] of Object.entries(summary.by_outcome).sort()) {
|
|
240
|
-
const pct = Math.round(count / summary.total * 100);
|
|
241
|
-
console.log(` ${outcome}: ${count} (${pct}%)`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Prints usage information for the ledger CLI to stdout.
|
|
247
|
-
*/
|
|
248
|
-
function printUsage() {
|
|
249
|
-
console.log(`Usage: ledger.js <command> [options]
|
|
250
|
-
|
|
251
|
-
Commands:
|
|
252
|
-
log [--limit <n>] Show recent payment decisions (default: last 20)
|
|
253
|
-
summary Show totals by skill, chain, and outcome
|
|
254
|
-
|
|
255
|
-
Ledger file: ~/.clawpowers/logs/payments.jsonl
|
|
256
|
-
|
|
257
|
-
Examples:
|
|
258
|
-
ledger.js log
|
|
259
|
-
ledger.js log --limit 50
|
|
260
|
-
ledger.js summary`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* CLI dispatch — routes argv to the appropriate command.
|
|
265
|
-
*
|
|
266
|
-
* @param {string[]} argv - Argument array (typically process.argv.slice(2)).
|
|
267
|
-
*/
|
|
268
|
-
function main(argv) {
|
|
269
|
-
const [cmd, ...rest] = argv;
|
|
270
|
-
|
|
271
|
-
switch (cmd) {
|
|
272
|
-
case 'log':
|
|
273
|
-
cmdLog(rest);
|
|
274
|
-
break;
|
|
275
|
-
case 'summary':
|
|
276
|
-
cmdSummary();
|
|
277
|
-
break;
|
|
278
|
-
case 'help':
|
|
279
|
-
case '-h':
|
|
280
|
-
case '--help':
|
|
281
|
-
printUsage();
|
|
282
|
-
break;
|
|
283
|
-
case undefined:
|
|
284
|
-
case '':
|
|
285
|
-
printUsage();
|
|
286
|
-
process.exit(1);
|
|
287
|
-
break;
|
|
288
|
-
default:
|
|
289
|
-
process.stderr.write(`Unknown command: ${cmd}\n`);
|
|
290
|
-
printUsage();
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Guard: only run CLI dispatch when invoked directly, not when require()'d
|
|
296
|
-
if (require.main === module) {
|
|
297
|
-
try {
|
|
298
|
-
main(process.argv.slice(2));
|
|
299
|
-
} catch (err) {
|
|
300
|
-
process.stderr.write(`Error: ${err.message}\n`);
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
module.exports = { logPaymentDecision, getPaymentSummary, LEDGER_FILE, LOGS_DIR };
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# runtime/payments/ledger.sh — Payment decision ledger (bash version)
|
|
3
|
-
#
|
|
4
|
-
# Records payment decisions to ~/.clawpowers/logs/payments.jsonl.
|
|
5
|
-
# Provides CLI commands to review recent decisions and summarize spending.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# bash ledger.sh log [--limit <n>]
|
|
9
|
-
# bash ledger.sh summary
|
|
10
|
-
# bash ledger.sh record --skill <name> --url <url> --amount <amt> \
|
|
11
|
-
# --asset <asset> --chain <chain> \
|
|
12
|
-
# --policy <result> --reason <text> [--would-pay]
|
|
13
|
-
set -euo pipefail
|
|
14
|
-
|
|
15
|
-
# Runtime root — override with CLAWPOWERS_DIR for testing
|
|
16
|
-
CLAWPOWERS_DIR="${CLAWPOWERS_DIR:-$HOME/.clawpowers}"
|
|
17
|
-
LOGS_DIR="$CLAWPOWERS_DIR/logs"
|
|
18
|
-
LEDGER_FILE="$LOGS_DIR/payments.jsonl"
|
|
19
|
-
|
|
20
|
-
## === Helpers ===
|
|
21
|
-
|
|
22
|
-
# Ensures the logs directory exists with correct permissions.
|
|
23
|
-
ensure_logs_dir() {
|
|
24
|
-
if [[ ! -d "$LOGS_DIR" ]]; then
|
|
25
|
-
mkdir -p "$LOGS_DIR"
|
|
26
|
-
chmod 700 "$LOGS_DIR"
|
|
27
|
-
fi
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# Returns an ISO 8601 timestamp (seconds precision).
|
|
31
|
-
iso_timestamp() {
|
|
32
|
-
date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
# Escapes a string for safe embedding in a JSON value.
|
|
36
|
-
# Handles backslash, double-quote, and common control characters.
|
|
37
|
-
json_escape() {
|
|
38
|
-
local s="$1"
|
|
39
|
-
s="${s//\\/\\\\}" # backslash
|
|
40
|
-
s="${s//\"/\\\"}" # double quote
|
|
41
|
-
s="${s//$'\t'/\\t}" # tab
|
|
42
|
-
s="${s//$'\n'/\\n}" # newline
|
|
43
|
-
printf '%s' "$s"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
## === record command ===
|
|
47
|
-
|
|
48
|
-
# Appends a single payment decision JSON line to the ledger.
|
|
49
|
-
#
|
|
50
|
-
# Options:
|
|
51
|
-
# --skill <name> Skill that triggered the payment gate
|
|
52
|
-
# --type <type> Entry type: decision | payment | denial (default: decision)
|
|
53
|
-
# --url <url> Resource URL that required payment
|
|
54
|
-
# --amount <n> Required amount in smallest unit (default: 0)
|
|
55
|
-
# --asset <sym> Asset symbol: USDC, ETH, etc. (default: USDC)
|
|
56
|
-
# --chain <name> Chain name: base, base-sepolia, etc. (default: base)
|
|
57
|
-
# --policy <result> Policy outcome: dry_run | approved | denied | disabled
|
|
58
|
-
# --reason <text> Human-readable reason for the policy result
|
|
59
|
-
# --would-pay Flag: set would_have_paid=true
|
|
60
|
-
cmd_record() {
|
|
61
|
-
local skill="unknown"
|
|
62
|
-
local type="decision"
|
|
63
|
-
local url=""
|
|
64
|
-
local amount="0"
|
|
65
|
-
local asset="USDC"
|
|
66
|
-
local chain="base"
|
|
67
|
-
local policy="dry_run"
|
|
68
|
-
local reason=""
|
|
69
|
-
local would_pay="false"
|
|
70
|
-
|
|
71
|
-
while [[ $# -gt 0 ]]; do
|
|
72
|
-
case "$1" in
|
|
73
|
-
--skill) skill="$2"; shift 2 ;;
|
|
74
|
-
--type) type="$2"; shift 2 ;;
|
|
75
|
-
--url) url="$2"; shift 2 ;;
|
|
76
|
-
--amount) amount="$2"; shift 2 ;;
|
|
77
|
-
--asset) asset="$2"; shift 2 ;;
|
|
78
|
-
--chain) chain="$2"; shift 2 ;;
|
|
79
|
-
--policy) policy="$2"; shift 2 ;;
|
|
80
|
-
--reason) reason="$2"; shift 2 ;;
|
|
81
|
-
--would-pay) would_pay="true"; shift ;;
|
|
82
|
-
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
83
|
-
esac
|
|
84
|
-
done
|
|
85
|
-
|
|
86
|
-
ensure_logs_dir
|
|
87
|
-
|
|
88
|
-
local ts
|
|
89
|
-
ts=$(iso_timestamp)
|
|
90
|
-
|
|
91
|
-
# Build the JSON line manually (no jq dependency required)
|
|
92
|
-
local line
|
|
93
|
-
line="{\"timestamp\":\"$(json_escape "$ts")\","
|
|
94
|
-
line+="\"skill\":\"$(json_escape "$skill")\","
|
|
95
|
-
line+="\"type\":\"$(json_escape "$type")\","
|
|
96
|
-
line+="\"url\":\"$(json_escape "$url")\","
|
|
97
|
-
line+="\"required_amount\":\"$(json_escape "$amount")\","
|
|
98
|
-
line+="\"asset\":\"$(json_escape "$asset")\","
|
|
99
|
-
line+="\"chain\":\"$(json_escape "$chain")\","
|
|
100
|
-
line+="\"policy_result\":\"$(json_escape "$policy")\","
|
|
101
|
-
line+="\"reason\":\"$(json_escape "$reason")\","
|
|
102
|
-
line+="\"would_have_paid\":$would_pay}"
|
|
103
|
-
|
|
104
|
-
echo "$line" >> "$LEDGER_FILE"
|
|
105
|
-
chmod 600 "$LEDGER_FILE" 2>/dev/null || true
|
|
106
|
-
|
|
107
|
-
echo "Recorded: $skill → $policy ($(basename "$LEDGER_FILE"))"
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
## === log command ===
|
|
111
|
-
|
|
112
|
-
# Shows recent payment decisions from the ledger.
|
|
113
|
-
#
|
|
114
|
-
# Options:
|
|
115
|
-
# --limit <n> Maximum records to show (default: 20)
|
|
116
|
-
cmd_log() {
|
|
117
|
-
local limit=20
|
|
118
|
-
|
|
119
|
-
while [[ $# -gt 0 ]]; do
|
|
120
|
-
case "$1" in
|
|
121
|
-
--limit) limit="$2"; shift 2 ;;
|
|
122
|
-
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
123
|
-
esac
|
|
124
|
-
done
|
|
125
|
-
|
|
126
|
-
if [[ ! -f "$LEDGER_FILE" ]]; then
|
|
127
|
-
echo "No payment decisions recorded yet."
|
|
128
|
-
echo "Ledger location: $LEDGER_FILE"
|
|
129
|
-
return
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
local total
|
|
133
|
-
total=$(wc -l < "$LEDGER_FILE" | tr -d ' ')
|
|
134
|
-
|
|
135
|
-
if [[ "$total" -eq 0 ]]; then
|
|
136
|
-
echo "No payment decisions recorded yet."
|
|
137
|
-
return
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
echo "Recent payment decisions (last $limit of $total):"
|
|
141
|
-
echo ""
|
|
142
|
-
|
|
143
|
-
# tail the last N lines and pretty-print each JSON record
|
|
144
|
-
tail -n "$limit" "$LEDGER_FILE" | while IFS= read -r line; do
|
|
145
|
-
[[ -z "$line" ]] && continue
|
|
146
|
-
|
|
147
|
-
# Extract fields using basic shell tools (no jq dependency)
|
|
148
|
-
local ts skill policy would_pay url amount asset chain reason
|
|
149
|
-
ts=$(echo "$line" | grep -o '"timestamp":"[^"]*"' | cut -d'"' -f4)
|
|
150
|
-
skill=$(echo "$line" | grep -o '"skill":"[^"]*"' | cut -d'"' -f4)
|
|
151
|
-
policy=$(echo "$line" | grep -o '"policy_result":"[^"]*"' | cut -d'"' -f4)
|
|
152
|
-
would_pay=$(echo "$line" | grep -o '"would_have_paid":[a-z]*' | cut -d: -f2)
|
|
153
|
-
url=$(echo "$line" | grep -o '"url":"[^"]*"' | cut -d'"' -f4)
|
|
154
|
-
amount=$(echo "$line" | grep -o '"required_amount":"[^"]*"' | cut -d'"' -f4)
|
|
155
|
-
asset=$(echo "$line" | grep -o '"asset":"[^"]*"' | cut -d'"' -f4)
|
|
156
|
-
chain=$(echo "$line" | grep -o '"chain":"[^"]*"' | cut -d'"' -f4)
|
|
157
|
-
reason=$(echo "$line" | grep -o '"reason":"[^"]*"' | cut -d'"' -f4)
|
|
158
|
-
|
|
159
|
-
local paid_label="[would skip]"
|
|
160
|
-
[[ "$would_pay" == "true" ]] && paid_label="[would pay]"
|
|
161
|
-
|
|
162
|
-
echo " $ts | $skill | $policy $paid_label"
|
|
163
|
-
[[ -n "$url" ]] && echo " URL: $url"
|
|
164
|
-
[[ "$amount" != "0" ]] && echo " Amount: $amount $asset on $chain"
|
|
165
|
-
[[ -n "$reason" ]] && echo " Reason: $reason"
|
|
166
|
-
echo ""
|
|
167
|
-
done
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
## === summary command ===
|
|
171
|
-
|
|
172
|
-
# Shows aggregated payment totals by skill, chain, and outcome.
|
|
173
|
-
cmd_summary() {
|
|
174
|
-
if [[ ! -f "$LEDGER_FILE" ]]; then
|
|
175
|
-
echo "No payment decisions recorded yet."
|
|
176
|
-
echo "Ledger location: $LEDGER_FILE"
|
|
177
|
-
return
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
local total
|
|
181
|
-
total=$(grep -c '.' "$LEDGER_FILE" 2>/dev/null || echo 0)
|
|
182
|
-
|
|
183
|
-
if [[ "$total" -eq 0 ]]; then
|
|
184
|
-
echo "No payment decisions recorded yet."
|
|
185
|
-
return
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
local would_have_paid
|
|
189
|
-
would_have_paid=$(grep -c '"would_have_paid":true' "$LEDGER_FILE" 2>/dev/null || echo 0)
|
|
190
|
-
|
|
191
|
-
echo "Payment Decision Summary"
|
|
192
|
-
echo "========================"
|
|
193
|
-
echo "Total decisions: $total"
|
|
194
|
-
echo "Would have paid: $would_have_paid"
|
|
195
|
-
echo ""
|
|
196
|
-
|
|
197
|
-
echo "By skill:"
|
|
198
|
-
grep -o '"skill":"[^"]*"' "$LEDGER_FILE" | \
|
|
199
|
-
cut -d'"' -f4 | sort | uniq -c | sort -rn | \
|
|
200
|
-
while read -r count skill; do echo " $skill: $count"; done
|
|
201
|
-
echo ""
|
|
202
|
-
|
|
203
|
-
echo "By chain:"
|
|
204
|
-
grep -o '"chain":"[^"]*"' "$LEDGER_FILE" | \
|
|
205
|
-
cut -d'"' -f4 | sort | uniq -c | sort -rn | \
|
|
206
|
-
while read -r count chain; do echo " $chain: $count"; done
|
|
207
|
-
echo ""
|
|
208
|
-
|
|
209
|
-
echo "By outcome:"
|
|
210
|
-
grep -o '"policy_result":"[^"]*"' "$LEDGER_FILE" | \
|
|
211
|
-
cut -d'"' -f4 | sort | uniq -c | sort -rn | \
|
|
212
|
-
while read -r count outcome; do echo " $outcome: $count"; done
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
## === usage ===
|
|
216
|
-
|
|
217
|
-
print_usage() {
|
|
218
|
-
echo "Usage: ledger.sh <command> [options]"
|
|
219
|
-
echo ""
|
|
220
|
-
echo "Commands:"
|
|
221
|
-
echo " log [--limit <n>] Show recent payment decisions (default: last 20)"
|
|
222
|
-
echo " summary Show totals by skill, chain, and outcome"
|
|
223
|
-
echo " record [options] Record a payment decision"
|
|
224
|
-
echo ""
|
|
225
|
-
echo "record options:"
|
|
226
|
-
echo " --skill <name> Skill name (default: unknown)"
|
|
227
|
-
echo " --type <type> decision | payment | denial (default: decision)"
|
|
228
|
-
echo " --url <url> Resource URL"
|
|
229
|
-
echo " --amount <n> Required amount"
|
|
230
|
-
echo " --asset <sym> Asset symbol (default: USDC)"
|
|
231
|
-
echo " --chain <name> Chain name (default: base)"
|
|
232
|
-
echo " --policy <result> dry_run | approved | denied | disabled"
|
|
233
|
-
echo " --reason <text> Reason for policy result"
|
|
234
|
-
echo " --would-pay Set would_have_paid=true"
|
|
235
|
-
echo ""
|
|
236
|
-
echo "Ledger file: ~/.clawpowers/logs/payments.jsonl"
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
## === main dispatch ===
|
|
240
|
-
|
|
241
|
-
main() {
|
|
242
|
-
local cmd="${1:-}"
|
|
243
|
-
shift || true
|
|
244
|
-
|
|
245
|
-
case "$cmd" in
|
|
246
|
-
log) cmd_log "$@" ;;
|
|
247
|
-
summary) cmd_summary "$@" ;;
|
|
248
|
-
record) cmd_record "$@" ;;
|
|
249
|
-
help|-h|--help) print_usage ;;
|
|
250
|
-
"")
|
|
251
|
-
print_usage
|
|
252
|
-
exit 1
|
|
253
|
-
;;
|
|
254
|
-
*)
|
|
255
|
-
echo "Unknown command: $cmd" >&2
|
|
256
|
-
print_usage >&2
|
|
257
|
-
exit 1
|
|
258
|
-
;;
|
|
259
|
-
esac
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
main "$@"
|