clawpowers 2.0.0 → 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.
Files changed (63) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/COMPATIBILITY.md +13 -0
  3. package/KNOWN_LIMITATIONS.md +19 -0
  4. package/LICENSING.md +10 -0
  5. package/README.md +201 -9
  6. package/SECURITY.md +33 -53
  7. package/dist/index.d.ts +638 -5
  8. package/dist/index.js +986 -58
  9. package/dist/index.js.map +1 -1
  10. package/native/Cargo.lock +4863 -0
  11. package/native/Cargo.toml +73 -0
  12. package/native/crates/canonical/Cargo.toml +24 -0
  13. package/native/crates/canonical/src/lib.rs +673 -0
  14. package/native/crates/compression/Cargo.toml +20 -0
  15. package/native/crates/compression/benches/compression_bench.rs +42 -0
  16. package/native/crates/compression/src/lib.rs +393 -0
  17. package/native/crates/evm-eth/Cargo.toml +13 -0
  18. package/native/crates/evm-eth/src/lib.rs +105 -0
  19. package/native/crates/fee/Cargo.toml +15 -0
  20. package/native/crates/fee/src/lib.rs +281 -0
  21. package/native/crates/index/Cargo.toml +16 -0
  22. package/native/crates/index/src/lib.rs +277 -0
  23. package/native/crates/policy/Cargo.toml +17 -0
  24. package/native/crates/policy/src/lib.rs +614 -0
  25. package/native/crates/security/Cargo.toml +22 -0
  26. package/native/crates/security/src/lib.rs +478 -0
  27. package/native/crates/tokens/Cargo.toml +13 -0
  28. package/native/crates/tokens/src/lib.rs +534 -0
  29. package/native/crates/verification/Cargo.toml +23 -0
  30. package/native/crates/verification/src/lib.rs +333 -0
  31. package/native/crates/wallet/Cargo.toml +20 -0
  32. package/native/crates/wallet/src/lib.rs +261 -0
  33. package/native/crates/x402/Cargo.toml +30 -0
  34. package/native/crates/x402/src/lib.rs +423 -0
  35. package/native/ffi/Cargo.toml +34 -0
  36. package/native/ffi/build.rs +4 -0
  37. package/native/ffi/index.node +0 -0
  38. package/native/ffi/src/lib.rs +352 -0
  39. package/native/ffi/tests/integration.rs +354 -0
  40. package/native/pyo3/Cargo.toml +26 -0
  41. package/native/pyo3/pyproject.toml +16 -0
  42. package/native/pyo3/src/lib.rs +407 -0
  43. package/native/pyo3/tests/test_smoke.py +180 -0
  44. package/native/wasm/Cargo.toml +44 -0
  45. package/native/wasm/pkg/.gitignore +6 -0
  46. package/native/wasm/pkg/clawpowers_wasm.d.ts +208 -0
  47. package/native/wasm/pkg/clawpowers_wasm.js +872 -0
  48. package/native/wasm/pkg/clawpowers_wasm_bg.wasm +0 -0
  49. package/native/wasm/pkg/clawpowers_wasm_bg.wasm.d.ts +40 -0
  50. package/native/wasm/pkg/package.json +17 -0
  51. package/native/wasm/pkg-node/.gitignore +6 -0
  52. package/native/wasm/pkg-node/clawpowers_wasm.d.ts +143 -0
  53. package/native/wasm/pkg-node/clawpowers_wasm.js +798 -0
  54. package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm +0 -0
  55. package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm.d.ts +40 -0
  56. package/native/wasm/pkg-node/package.json +13 -0
  57. package/native/wasm/src/lib.rs +433 -0
  58. package/package.json +24 -3
  59. package/src/skills/catalog.ts +435 -0
  60. package/src/skills/executor.ts +56 -0
  61. package/src/skills/index.ts +3 -0
  62. package/src/skills/itp/SKILL.md +112 -0
  63. package/src/skills/loader.ts +193 -0
@@ -0,0 +1,40 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const __wbg_wasmcanonicalstore_free: (a: number, b: number) => void;
5
+ export const approximateDistance: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
6
+ export const calculateFee: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number, h: bigint) => [number, number, number, number];
7
+ export const compressVector: (a: number, b: number, c: number) => [number, number, number, number];
8
+ export const computeKeccak256: (a: number, b: number) => [number, number];
9
+ export const computeSha256: (a: number, b: number) => [number, number];
10
+ export const decompressVector: (a: number, b: number, c: number) => [number, number, number, number];
11
+ export const deriveEthereumAddress: (a: number, b: number) => [number, number, number, number];
12
+ export const derivePublicKey: (a: number, b: number) => [number, number, number, number];
13
+ export const evaluateWriteFirewall: (a: number, b: number) => [number, number, number, number];
14
+ export const getAvailableModules: () => [number, number];
15
+ export const getDefaultTokenRegistry: () => [number, number, number, number];
16
+ export const getVersion: () => [number, number];
17
+ export const signEcdsa: (a: number, b: number, c: number, d: number) => [number, number, number, number];
18
+ export const tokenAmountAdd: (a: number, b: number, c: number, d: number) => [number, number, number, number];
19
+ export const tokenAmountFromHuman: (a: number, b: number) => [number, number, number, number];
20
+ export const tokenAmountMulBps: (a: number, b: number, c: bigint) => [number, number, number, number];
21
+ export const tokenAmountSub: (a: number, b: number, c: number, d: number) => [number, number, number, number];
22
+ export const tokenAmountToHuman: (a: number, b: number) => [number, number, number];
23
+ export const verifyEcdsa: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
24
+ export const wasmcanonicalstore_exportJson: (a: number) => [number, number, number, number];
25
+ export const wasmcanonicalstore_get: (a: number, b: number, c: number) => [number, number, number, number];
26
+ export const wasmcanonicalstore_getByHash: (a: number, b: number, c: number) => [number, number, number, number];
27
+ export const wasmcanonicalstore_importJson: (a: number, b: number, c: number) => [number, number, number];
28
+ export const wasmcanonicalstore_insert: (a: number, b: number, c: number) => [number, number, number, number];
29
+ export const wasmcanonicalstore_new: () => [number, number, number];
30
+ export const wasmcanonicalstore_queryNamespace: (a: number, b: number, c: number, d: number) => [number, number, number, number];
31
+ export const wasmcanonicalstore_softDelete: (a: number, b: number, c: number) => [number, number, number];
32
+ export const wasmcanonicalstore_verifyIntegrity: (a: number, b: number, c: number) => [number, number, number];
33
+ export const __wbindgen_exn_store: (a: number) => void;
34
+ export const __externref_table_alloc: () => number;
35
+ export const __wbindgen_externrefs: WebAssembly.Table;
36
+ export const __wbindgen_malloc: (a: number, b: number) => number;
37
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
38
+ export const __externref_table_dealloc: (a: number) => void;
39
+ export const __wbindgen_free: (a: number, b: number, c: number) => void;
40
+ export const __wbindgen_start: () => void;
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "clawpowers-wasm",
3
+ "description": "WASM bindings for ClawPowers core — tokens, fees, policy, compression, index, canonical, verification, security",
4
+ "version": "0.1.0",
5
+ "license": "BUSL-1.1",
6
+ "files": [
7
+ "clawpowers_wasm_bg.wasm",
8
+ "clawpowers_wasm.js",
9
+ "clawpowers_wasm.d.ts"
10
+ ],
11
+ "main": "clawpowers_wasm.js",
12
+ "types": "clawpowers_wasm.d.ts"
13
+ }
@@ -0,0 +1,433 @@
1
+ //! WASM bindings for ClawPowers core.
2
+ //!
3
+ //! Exposes the following modules via `wasm-bindgen`:
4
+ //!
5
+ //! - **tokens** — Token registry, token amounts, decimal math
6
+ //! - **fee** — Fee schedule calculation
7
+ //! - **policy** — Spending policy engine
8
+ //! - **compression** — TurboQuant vector compression
9
+ //! - **index** — In-memory vector index
10
+ //! - **canonical** — Canonical record store (in-memory backend for WASM)
11
+ //! - **verification** — Record verification pipeline
12
+ //! - **security** — Write firewall and audit log
13
+ //!
14
+ //! **NOT included (falls back to TypeScript):**
15
+ //! - **wallet** — Uses `alloy-signer-local` which depends on native crypto
16
+ //! primitives that are not fully WASM-compatible without significant patching.
17
+ //! The TypeScript fallback uses `ethers.js` / `viem` for equivalent functionality.
18
+ //! - **x402** — Uses `reqwest` with `tokio` runtime; in WASM, HTTP is handled
19
+ //! natively by `fetch()` in the TypeScript layer.
20
+
21
+ use wasm_bindgen::prelude::*;
22
+
23
+ // ═══════════════════════════════════════════════════════════════════════════════
24
+ // Tokens
25
+ // ═══════════════════════════════════════════════════════════════════════════════
26
+
27
+ /// Create a TokenAmount from a human-readable f64 value and decimal count.
28
+ /// Returns JSON: `{"raw": "...", "decimals": N}`
29
+ #[wasm_bindgen(js_name = "tokenAmountFromHuman")]
30
+ pub fn token_amount_from_human(human: f64, decimals: u8) -> Result<String, JsError> {
31
+ let amount = clawpowers_tokens::TokenAmount::from_human(human, decimals);
32
+ Ok(serde_json::to_string(&amount)?)
33
+ }
34
+
35
+ /// Convert a TokenAmount JSON back to a human-readable f64.
36
+ #[wasm_bindgen(js_name = "tokenAmountToHuman")]
37
+ pub fn token_amount_to_human(json: &str) -> Result<f64, JsError> {
38
+ let amount: clawpowers_tokens::TokenAmount = serde_json::from_str(json)?;
39
+ Ok(amount.to_human())
40
+ }
41
+
42
+ /// Add two TokenAmount JSONs. Returns the sum as JSON.
43
+ #[wasm_bindgen(js_name = "tokenAmountAdd")]
44
+ pub fn token_amount_add(a_json: &str, b_json: &str) -> Result<String, JsError> {
45
+ let a: clawpowers_tokens::TokenAmount = serde_json::from_str(a_json)?;
46
+ let b: clawpowers_tokens::TokenAmount = serde_json::from_str(b_json)?;
47
+ let result = a.add(&b).map_err(|e| JsError::new(&e.to_string()))?;
48
+ Ok(serde_json::to_string(&result)?)
49
+ }
50
+
51
+ /// Subtract two TokenAmount JSONs. Returns the difference as JSON.
52
+ #[wasm_bindgen(js_name = "tokenAmountSub")]
53
+ pub fn token_amount_sub(a_json: &str, b_json: &str) -> Result<String, JsError> {
54
+ let a: clawpowers_tokens::TokenAmount = serde_json::from_str(a_json)?;
55
+ let b: clawpowers_tokens::TokenAmount = serde_json::from_str(b_json)?;
56
+ let result = a.sub(&b).map_err(|e| JsError::new(&e.to_string()))?;
57
+ Ok(serde_json::to_string(&result)?)
58
+ }
59
+
60
+ /// Multiply a TokenAmount by basis points. Returns result as JSON.
61
+ #[wasm_bindgen(js_name = "tokenAmountMulBps")]
62
+ pub fn token_amount_mul_bps(json: &str, bps: u64) -> Result<String, JsError> {
63
+ let amount: clawpowers_tokens::TokenAmount = serde_json::from_str(json)?;
64
+ let result = amount
65
+ .checked_mul_bps(bps)
66
+ .ok_or_else(|| JsError::new("overflow in checked_mul_bps"))?;
67
+ Ok(serde_json::to_string(&result)?)
68
+ }
69
+
70
+ /// Get the default token registry as JSON.
71
+ #[wasm_bindgen(js_name = "getDefaultTokenRegistry")]
72
+ pub fn get_default_token_registry() -> Result<String, JsError> {
73
+ let registry = clawpowers_tokens::TokenRegistry::default();
74
+ let tokens: Vec<_> = registry.iter().collect();
75
+ Ok(serde_json::to_string(&tokens)?)
76
+ }
77
+
78
+ // ═══════════════════════════════════════════════════════════════════════════════
79
+ // Fee
80
+ // ═══════════════════════════════════════════════════════════════════════════════
81
+
82
+ /// Calculate a transaction fee.
83
+ /// Returns JSON: `{"gross_amount": ..., "fee_amount": ..., "net_amount": ...}`
84
+ #[wasm_bindgen(js_name = "calculateFee")]
85
+ pub fn calculate_fee(
86
+ amount_json: &str,
87
+ fee_type: &str,
88
+ tx_fee_bps: Option<u64>,
89
+ swap_fee_bps: Option<u64>,
90
+ ) -> Result<String, JsError> {
91
+ let amount: clawpowers_tokens::TokenAmount = serde_json::from_str(amount_json)?;
92
+
93
+ let fee_type = match fee_type {
94
+ "transaction" => clawpowers_fee::FeeType::Transaction,
95
+ "swap" => clawpowers_fee::FeeType::Swap,
96
+ s if s.starts_with("custom:") => {
97
+ let bps: u64 = s[7..]
98
+ .parse()
99
+ .map_err(|_| JsError::new("invalid custom bps"))?;
100
+ clawpowers_fee::FeeType::Custom(bps)
101
+ }
102
+ _ => return Err(JsError::new("fee_type must be 'transaction', 'swap', or 'custom:N'")),
103
+ };
104
+
105
+ let schedule = clawpowers_fee::FeeSchedule::new(
106
+ tx_fee_bps.unwrap_or(clawpowers_fee::DEFAULT_TX_FEE_BPS),
107
+ swap_fee_bps.unwrap_or(clawpowers_fee::DEFAULT_SWAP_FEE_BPS),
108
+ alloy_primitives::Address::ZERO,
109
+ );
110
+
111
+ let calc = schedule
112
+ .calculate(amount, fee_type)
113
+ .map_err(|e| JsError::new(&e.to_string()))?;
114
+
115
+ let result = serde_json::json!({
116
+ "gross_amount": calc.gross_amount.to_human(),
117
+ "fee_amount": calc.fee_amount.to_human(),
118
+ "net_amount": calc.net_amount.to_human(),
119
+ });
120
+ Ok(serde_json::to_string(&result)?)
121
+ }
122
+
123
+ // ═══════════════════════════════════════════════════════════════════════════════
124
+ // Compression
125
+ // ═══════════════════════════════════════════════════════════════════════════════
126
+
127
+ /// Compress a float32 vector. Input: JSON array of f32. Output: CompressedVector JSON.
128
+ #[wasm_bindgen(js_name = "compressVector")]
129
+ pub fn compress_vector(vector_json: &str, dimensions: usize) -> Result<String, JsError> {
130
+ let vector: Vec<f32> = serde_json::from_str(vector_json)?;
131
+ let config = clawpowers_compression::CompressionConfig {
132
+ dimensions,
133
+ quantization_bits: 8,
134
+ rotation_seed: 0xDEAD_BEEF_CAFE_1234,
135
+ };
136
+ let compressor = clawpowers_compression::TurboCompressor::new(config);
137
+ let compressed = compressor
138
+ .compress(&vector)
139
+ .map_err(|e| JsError::new(&e.to_string()))?;
140
+ Ok(serde_json::to_string(&compressed)?)
141
+ }
142
+
143
+ /// Decompress a CompressedVector JSON back to a float32 array JSON.
144
+ #[wasm_bindgen(js_name = "decompressVector")]
145
+ pub fn decompress_vector(compressed_json: &str, dimensions: usize) -> Result<String, JsError> {
146
+ let compressed: clawpowers_compression::CompressedVector =
147
+ serde_json::from_str(compressed_json)?;
148
+ let config = clawpowers_compression::CompressionConfig {
149
+ dimensions,
150
+ quantization_bits: 8,
151
+ rotation_seed: 0xDEAD_BEEF_CAFE_1234,
152
+ };
153
+ let compressor = clawpowers_compression::TurboCompressor::new(config);
154
+ let vector = compressor
155
+ .decompress(&compressed)
156
+ .map_err(|e| JsError::new(&e.to_string()))?;
157
+ Ok(serde_json::to_string(&vector)?)
158
+ }
159
+
160
+ /// Compute approximate distance between two CompressedVector JSONs.
161
+ #[wasm_bindgen(js_name = "approximateDistance")]
162
+ pub fn approximate_distance(
163
+ a_json: &str,
164
+ b_json: &str,
165
+ dimensions: usize,
166
+ ) -> Result<f32, JsError> {
167
+ let a: clawpowers_compression::CompressedVector = serde_json::from_str(a_json)?;
168
+ let b: clawpowers_compression::CompressedVector = serde_json::from_str(b_json)?;
169
+ let config = clawpowers_compression::CompressionConfig {
170
+ dimensions,
171
+ quantization_bits: 8,
172
+ rotation_seed: 0xDEAD_BEEF_CAFE_1234,
173
+ };
174
+ let compressor = clawpowers_compression::TurboCompressor::new(config);
175
+ let dist = compressor
176
+ .approximate_distance(&a, &b)
177
+ .map_err(|e| JsError::new(&e.to_string()))?;
178
+ Ok(dist)
179
+ }
180
+
181
+ /// Compute SHA-256 hash of content string.
182
+ #[wasm_bindgen(js_name = "computeSha256")]
183
+ pub fn compute_sha256(content: &str) -> String {
184
+ clawpowers_canonical::compute_sha256(content)
185
+ }
186
+
187
+ /// Compute Keccak-256 hash of raw bytes (EVM-compatible). Returns `0x` + 64 hex chars.
188
+ #[wasm_bindgen(js_name = "computeKeccak256")]
189
+ pub fn compute_keccak256(bytes: &[u8]) -> String {
190
+ let h = alloy_primitives::keccak256(bytes);
191
+ format!("0x{:x}", h)
192
+ }
193
+
194
+ /// Ethereum address from 32-byte secp256k1 private key (`0x` + 20 bytes, EIP-55 checksum).
195
+ #[wasm_bindgen(js_name = "deriveEthereumAddress")]
196
+ pub fn derive_ethereum_address_wasm(private_key: &[u8]) -> Result<String, JsError> {
197
+ clawpowers_evm_eth::derive_ethereum_address(private_key).map_err(|e| JsError::new(&e))
198
+ }
199
+
200
+ /// Uncompressed public key: 64 bytes (x || y), no `0x04` prefix.
201
+ #[wasm_bindgen(js_name = "derivePublicKey")]
202
+ pub fn derive_public_key_wasm(private_key: &[u8]) -> Result<Vec<u8>, JsError> {
203
+ clawpowers_evm_eth::derive_public_key(private_key).map_err(|e| JsError::new(&e))
204
+ }
205
+
206
+ /// ECDSA sign 32-byte message hash → 65 bytes (r || s || recovery_id).
207
+ #[wasm_bindgen(js_name = "signEcdsa")]
208
+ pub fn sign_ecdsa_wasm(private_key: &[u8], message_hash: &[u8]) -> Result<Vec<u8>, JsError> {
209
+ clawpowers_evm_eth::sign_ecdsa(private_key, message_hash).map_err(|e| JsError::new(&e))
210
+ }
211
+
212
+ #[wasm_bindgen(js_name = "verifyEcdsa")]
213
+ pub fn verify_ecdsa_wasm(
214
+ public_key: &[u8],
215
+ message_hash: &[u8],
216
+ signature: &[u8],
217
+ ) -> Result<bool, JsError> {
218
+ clawpowers_evm_eth::verify_ecdsa(public_key, message_hash, signature).map_err(|e| JsError::new(&e))
219
+ }
220
+
221
+ // ═══════════════════════════════════════════════════════════════════════════════
222
+ // Canonical Store
223
+ // ═══════════════════════════════════════════════════════════════════════════════
224
+
225
+ /// Opaque handle to an in-memory CanonicalStore.
226
+ #[wasm_bindgen]
227
+ pub struct WasmCanonicalStore {
228
+ inner: clawpowers_canonical::CanonicalStore,
229
+ }
230
+
231
+ #[wasm_bindgen]
232
+ impl WasmCanonicalStore {
233
+ /// Create a new in-memory canonical store.
234
+ #[wasm_bindgen(constructor)]
235
+ pub fn new() -> Result<WasmCanonicalStore, JsError> {
236
+ let store = clawpowers_canonical::CanonicalStore::in_memory()
237
+ .map_err(|e| JsError::new(&e.to_string()))?;
238
+ Ok(Self { inner: store })
239
+ }
240
+
241
+ /// Insert a record. Input: JSON with {namespace, content, metadata, provenance}.
242
+ /// Returns the record UUID as a string.
243
+ pub fn insert(&self, json: &str) -> Result<String, JsError> {
244
+ #[derive(serde::Deserialize)]
245
+ struct InsertInput {
246
+ namespace: String,
247
+ content: String,
248
+ #[serde(default)]
249
+ metadata: serde_json::Value,
250
+ #[serde(default = "default_provenance")]
251
+ provenance: String,
252
+ }
253
+ fn default_provenance() -> String {
254
+ "wasm".to_string()
255
+ }
256
+
257
+ let input: InsertInput = serde_json::from_str(json)?;
258
+ let record = clawpowers_canonical::CanonicalRecord::new(
259
+ input.namespace,
260
+ input.content,
261
+ None,
262
+ input.metadata,
263
+ input.provenance,
264
+ );
265
+ let id = self
266
+ .inner
267
+ .insert(&record)
268
+ .map_err(|e| JsError::new(&e.to_string()))?;
269
+ Ok(id.to_string())
270
+ }
271
+
272
+ /// Get a record by UUID. Returns JSON or null.
273
+ pub fn get(&self, id: &str) -> Result<Option<String>, JsError> {
274
+ let uuid = uuid::Uuid::parse_str(id).map_err(|e| JsError::new(&e.to_string()))?;
275
+ let record = self
276
+ .inner
277
+ .get(&uuid)
278
+ .map_err(|e| JsError::new(&e.to_string()))?;
279
+ match record {
280
+ Some(r) => Ok(Some(serde_json::to_string(&r)?)),
281
+ None => Ok(None),
282
+ }
283
+ }
284
+
285
+ /// Get a record by content hash. Returns JSON or null.
286
+ #[wasm_bindgen(js_name = "getByHash")]
287
+ pub fn get_by_hash(&self, hash: &str) -> Result<Option<String>, JsError> {
288
+ let record = self
289
+ .inner
290
+ .get_by_hash(hash)
291
+ .map_err(|e| JsError::new(&e.to_string()))?;
292
+ match record {
293
+ Some(r) => Ok(Some(serde_json::to_string(&r)?)),
294
+ None => Ok(None),
295
+ }
296
+ }
297
+
298
+ /// Query records by namespace. Returns JSON array.
299
+ #[wasm_bindgen(js_name = "queryNamespace")]
300
+ pub fn query_namespace(&self, namespace: &str, limit: usize) -> Result<String, JsError> {
301
+ let records = self
302
+ .inner
303
+ .query_namespace(namespace, limit)
304
+ .map_err(|e| JsError::new(&e.to_string()))?;
305
+ Ok(serde_json::to_string(&records)?)
306
+ }
307
+
308
+ /// Soft-delete a record. Returns true if the record existed.
309
+ #[wasm_bindgen(js_name = "softDelete")]
310
+ pub fn soft_delete(&self, id: &str) -> Result<bool, JsError> {
311
+ let uuid = uuid::Uuid::parse_str(id).map_err(|e| JsError::new(&e.to_string()))?;
312
+ self.inner
313
+ .soft_delete(&uuid)
314
+ .map_err(|e| JsError::new(&e.to_string()))
315
+ }
316
+
317
+ /// Verify record integrity. Returns true if hash matches.
318
+ #[wasm_bindgen(js_name = "verifyIntegrity")]
319
+ pub fn verify_integrity(&self, id: &str) -> Result<bool, JsError> {
320
+ let uuid = uuid::Uuid::parse_str(id).map_err(|e| JsError::new(&e.to_string()))?;
321
+ self.inner
322
+ .verify_integrity(&uuid)
323
+ .map_err(|e| JsError::new(&e.to_string()))
324
+ }
325
+
326
+ /// Export all records as JSON for IndexedDB persistence.
327
+ #[wasm_bindgen(js_name = "exportJson")]
328
+ pub fn export_json(&self) -> Result<String, JsError> {
329
+ self.inner
330
+ .export_json()
331
+ .map_err(|e| JsError::new(&e.to_string()))
332
+ }
333
+
334
+ /// Import records from JSON (e.g., loaded from IndexedDB).
335
+ #[wasm_bindgen(js_name = "importJson")]
336
+ pub fn import_json(&self, json: &str) -> Result<usize, JsError> {
337
+ self.inner
338
+ .import_json(json)
339
+ .map_err(|e| JsError::new(&e.to_string()))
340
+ }
341
+ }
342
+
343
+ // ═══════════════════════════════════════════════════════════════════════════════
344
+ // Security — Write Firewall
345
+ // ═══════════════════════════════════════════════════════════════════════════════
346
+
347
+ /// Evaluate a write request against a firewall.
348
+ /// Input JSON: {namespace, content, trust_level, source, allowed_namespaces?, blocked_patterns?, max_content_length?}
349
+ /// Returns JSON: {"decision": "allow"|"deny"|"sanitize", "reason"?: ..., "sanitized"?: ...}
350
+ #[wasm_bindgen(js_name = "evaluateWriteFirewall")]
351
+ pub fn evaluate_write_firewall(json: &str) -> Result<String, JsError> {
352
+ #[derive(serde::Deserialize)]
353
+ struct Input {
354
+ namespace: String,
355
+ content: String,
356
+ trust_level: String,
357
+ source: String,
358
+ #[serde(default)]
359
+ allowed_namespaces: Vec<String>,
360
+ #[serde(default)]
361
+ blocked_patterns: Vec<String>,
362
+ max_content_length: Option<usize>,
363
+ }
364
+
365
+ let input: Input = serde_json::from_str(json)?;
366
+
367
+ let trust = match input.trust_level.as_str() {
368
+ "system" => clawpowers_security::TrustLevel::System,
369
+ "agent" => clawpowers_security::TrustLevel::Agent,
370
+ "external" => clawpowers_security::TrustLevel::External,
371
+ _ => clawpowers_security::TrustLevel::Untrusted,
372
+ };
373
+
374
+ let mut firewall = clawpowers_security::WriteFirewall::new(input.allowed_namespaces);
375
+ firewall.blocked_patterns = input.blocked_patterns;
376
+ if let Some(max) = input.max_content_length {
377
+ firewall.max_content_length = max;
378
+ }
379
+
380
+ let request = clawpowers_security::WriteRequest {
381
+ namespace: input.namespace,
382
+ content: input.content,
383
+ trust_level: trust,
384
+ source: input.source,
385
+ };
386
+
387
+ let decision = firewall.evaluate(&request);
388
+ let result = match decision {
389
+ clawpowers_security::FirewallDecision::Allow => {
390
+ serde_json::json!({"decision": "allow"})
391
+ }
392
+ clawpowers_security::FirewallDecision::Deny(reason) => {
393
+ serde_json::json!({"decision": "deny", "reason": reason})
394
+ }
395
+ clawpowers_security::FirewallDecision::Sanitize(original, sanitized) => {
396
+ serde_json::json!({"decision": "sanitize", "original": original, "sanitized": sanitized})
397
+ }
398
+ };
399
+ Ok(serde_json::to_string(&result)?)
400
+ }
401
+
402
+ // ═══════════════════════════════════════════════════════════════════════════════
403
+ // Version info
404
+ // ═══════════════════════════════════════════════════════════════════════════════
405
+
406
+ /// Returns the version and build info.
407
+ #[wasm_bindgen(js_name = "getVersion")]
408
+ pub fn get_version() -> String {
409
+ format!(
410
+ "clawpowers-wasm v{} ({})",
411
+ env!("CARGO_PKG_VERSION"),
412
+ if cfg!(debug_assertions) {
413
+ "debug"
414
+ } else {
415
+ "release"
416
+ }
417
+ )
418
+ }
419
+
420
+ /// Returns a list of modules available in this WASM build.
421
+ #[wasm_bindgen(js_name = "getAvailableModules")]
422
+ pub fn get_available_modules() -> String {
423
+ serde_json::to_string(&vec![
424
+ "tokens",
425
+ "fee",
426
+ "compression",
427
+ "canonical",
428
+ "verification",
429
+ "security",
430
+ "index",
431
+ ])
432
+ .unwrap_or_default()
433
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawpowers",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Skills library for AI agents — payments, memory, RSI, wallet. Drop-in capability layer for any agent framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,18 +14,35 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
+ "src/skills",
18
+ "native/Cargo.toml",
19
+ "native/Cargo.lock",
20
+ "native/crates",
21
+ "native/ffi",
22
+ "native/wasm",
23
+ "native/wasm/pkg",
24
+ "native/wasm/pkg-node",
25
+ "native/pyo3",
17
26
  "README.md",
18
27
  "LICENSE",
19
28
  "CHANGELOG.md",
20
- "SECURITY.md"
29
+ "SECURITY.md",
30
+ "COMPATIBILITY.md",
31
+ "KNOWN_LIMITATIONS.md",
32
+ "LICENSING.md"
21
33
  ],
22
34
  "scripts": {
35
+ "build:native": "cd native && cargo build --release --workspace || echo 'Rust not installed - using WASM/TS fallback'",
36
+ "build:wasm": "cd native/wasm && wasm-pack build --target nodejs --out-dir pkg-node || true",
23
37
  "build": "tsup",
24
38
  "test": "vitest run",
25
39
  "test:watch": "vitest",
26
40
  "typecheck": "tsc --noEmit",
41
+ "lint": "eslint src tests benchmarks",
42
+ "verify:pack": "npm pack --dry-run",
27
43
  "clean": "rm -rf dist",
28
- "prepublishOnly": "npm run build"
44
+ "prepublishOnly": "npm run build",
45
+ "prepack": "npm run build"
29
46
  },
30
47
  "keywords": [
31
48
  "ai-agent",
@@ -58,6 +75,10 @@
58
75
  },
59
76
  "devDependencies": {
60
77
  "@types/node": "^25.5.0",
78
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
79
+ "@typescript-eslint/parser": "^8.0.0",
80
+ "eslint": "^9.0.0",
81
+ "globals": "^15.0.0",
61
82
  "tsup": "^8.0.0",
62
83
  "typescript": "^5.5.0",
63
84
  "vitest": "^2.0.0"