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.
Files changed (131) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/COMPATIBILITY.md +13 -0
  3. package/KNOWN_LIMITATIONS.md +19 -0
  4. package/LICENSE +44 -0
  5. package/LICENSING.md +10 -0
  6. package/README.md +378 -210
  7. package/SECURITY.md +52 -0
  8. package/dist/index.d.ts +1477 -0
  9. package/dist/index.js +3464 -0
  10. package/dist/index.js.map +1 -0
  11. package/native/Cargo.lock +4863 -0
  12. package/native/Cargo.toml +73 -0
  13. package/native/crates/canonical/Cargo.toml +24 -0
  14. package/native/crates/canonical/src/lib.rs +673 -0
  15. package/native/crates/compression/Cargo.toml +20 -0
  16. package/native/crates/compression/benches/compression_bench.rs +42 -0
  17. package/native/crates/compression/src/lib.rs +393 -0
  18. package/native/crates/evm-eth/Cargo.toml +13 -0
  19. package/native/crates/evm-eth/src/lib.rs +105 -0
  20. package/native/crates/fee/Cargo.toml +15 -0
  21. package/native/crates/fee/src/lib.rs +281 -0
  22. package/native/crates/index/Cargo.toml +16 -0
  23. package/native/crates/index/src/lib.rs +277 -0
  24. package/native/crates/policy/Cargo.toml +17 -0
  25. package/native/crates/policy/src/lib.rs +614 -0
  26. package/native/crates/security/Cargo.toml +22 -0
  27. package/native/crates/security/src/lib.rs +478 -0
  28. package/native/crates/tokens/Cargo.toml +13 -0
  29. package/native/crates/tokens/src/lib.rs +534 -0
  30. package/native/crates/verification/Cargo.toml +23 -0
  31. package/native/crates/verification/src/lib.rs +333 -0
  32. package/native/crates/wallet/Cargo.toml +20 -0
  33. package/native/crates/wallet/src/lib.rs +261 -0
  34. package/native/crates/x402/Cargo.toml +30 -0
  35. package/native/crates/x402/src/lib.rs +423 -0
  36. package/native/ffi/Cargo.toml +34 -0
  37. package/native/ffi/build.rs +4 -0
  38. package/native/ffi/index.node +0 -0
  39. package/native/ffi/src/lib.rs +352 -0
  40. package/native/ffi/tests/integration.rs +354 -0
  41. package/native/pyo3/Cargo.toml +26 -0
  42. package/native/pyo3/pyproject.toml +16 -0
  43. package/native/pyo3/src/lib.rs +407 -0
  44. package/native/pyo3/tests/test_smoke.py +180 -0
  45. package/native/wasm/Cargo.toml +44 -0
  46. package/native/wasm/pkg/.gitignore +6 -0
  47. package/native/wasm/pkg/clawpowers_wasm.d.ts +208 -0
  48. package/native/wasm/pkg/clawpowers_wasm.js +872 -0
  49. package/native/wasm/pkg/clawpowers_wasm_bg.wasm +0 -0
  50. package/native/wasm/pkg/clawpowers_wasm_bg.wasm.d.ts +40 -0
  51. package/native/wasm/pkg/package.json +17 -0
  52. package/native/wasm/pkg-node/.gitignore +6 -0
  53. package/native/wasm/pkg-node/clawpowers_wasm.d.ts +143 -0
  54. package/native/wasm/pkg-node/clawpowers_wasm.js +798 -0
  55. package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm +0 -0
  56. package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm.d.ts +40 -0
  57. package/native/wasm/pkg-node/package.json +13 -0
  58. package/native/wasm/src/lib.rs +433 -0
  59. package/package.json +71 -44
  60. package/src/skills/catalog.ts +435 -0
  61. package/src/skills/executor.ts +56 -0
  62. package/src/skills/index.ts +3 -0
  63. package/src/skills/itp/SKILL.md +112 -0
  64. package/src/skills/loader.ts +193 -0
  65. package/.claude-plugin/manifest.json +0 -19
  66. package/.codex/INSTALL.md +0 -36
  67. package/.cursor-plugin/manifest.json +0 -21
  68. package/.opencode/INSTALL.md +0 -52
  69. package/ARCHITECTURE.md +0 -69
  70. package/bin/clawpowers.js +0 -625
  71. package/bin/clawpowers.sh +0 -91
  72. package/docs/demo/clawpowers-demo.cast +0 -197
  73. package/docs/demo/clawpowers-demo.gif +0 -0
  74. package/docs/launch-images/25-skills-breakdown.jpg +0 -0
  75. package/docs/launch-images/clawpowers-vs-superpowers.jpg +0 -0
  76. package/docs/launch-images/economic-code-optimization.jpg +0 -0
  77. package/docs/launch-images/native-vs-bridge-2.jpg +0 -0
  78. package/docs/launch-images/native-vs-bridge.jpg +0 -0
  79. package/docs/launch-images/post1-hero-lobster.jpg +0 -0
  80. package/docs/launch-images/post2-dashboard.jpg +0 -0
  81. package/docs/launch-images/post3-superpowers.jpg +0 -0
  82. package/docs/launch-images/post4-before-after.jpg +0 -0
  83. package/docs/launch-images/post5-install-now.jpg +0 -0
  84. package/docs/launch-images/ultimate-stack.jpg +0 -0
  85. package/docs/launch-posts.md +0 -76
  86. package/docs/quickstart-first-transaction.md +0 -204
  87. package/gemini-extension.json +0 -32
  88. package/hooks/session-start +0 -205
  89. package/hooks/session-start.cmd +0 -43
  90. package/hooks/session-start.js +0 -163
  91. package/runtime/demo/README.md +0 -78
  92. package/runtime/demo/x402-mock-server.js +0 -230
  93. package/runtime/feedback/analyze.js +0 -621
  94. package/runtime/feedback/analyze.sh +0 -546
  95. package/runtime/init.js +0 -210
  96. package/runtime/init.sh +0 -178
  97. package/runtime/metrics/collector.js +0 -361
  98. package/runtime/metrics/collector.sh +0 -308
  99. package/runtime/payments/ledger.js +0 -305
  100. package/runtime/payments/ledger.sh +0 -262
  101. package/runtime/payments/pipeline.js +0 -455
  102. package/runtime/persistence/store.js +0 -433
  103. package/runtime/persistence/store.sh +0 -303
  104. package/skill.json +0 -106
  105. package/skills/agent-bounties/SKILL.md +0 -553
  106. package/skills/agent-payments/SKILL.md +0 -479
  107. package/skills/brainstorming/SKILL.md +0 -233
  108. package/skills/content-pipeline/SKILL.md +0 -282
  109. package/skills/cross-project-knowledge/SKILL.md +0 -345
  110. package/skills/dispatching-parallel-agents/SKILL.md +0 -305
  111. package/skills/economic-code-optimization/SKILL.md +0 -265
  112. package/skills/executing-plans/SKILL.md +0 -255
  113. package/skills/finishing-a-development-branch/SKILL.md +0 -260
  114. package/skills/formal-verification-lite/SKILL.md +0 -441
  115. package/skills/learn-how-to-learn/SKILL.md +0 -235
  116. package/skills/market-intelligence/SKILL.md +0 -323
  117. package/skills/meta-skill-evolution/SKILL.md +0 -325
  118. package/skills/prospecting/SKILL.md +0 -454
  119. package/skills/receiving-code-review/SKILL.md +0 -225
  120. package/skills/requesting-code-review/SKILL.md +0 -206
  121. package/skills/security-audit/SKILL.md +0 -353
  122. package/skills/self-healing-code/SKILL.md +0 -369
  123. package/skills/subagent-driven-development/SKILL.md +0 -244
  124. package/skills/systematic-debugging/SKILL.md +0 -355
  125. package/skills/test-driven-development/SKILL.md +0 -416
  126. package/skills/using-clawpowers/SKILL.md +0 -160
  127. package/skills/using-git-worktrees/SKILL.md +0 -261
  128. package/skills/validator/SKILL.md +0 -281
  129. package/skills/verification-before-completion/SKILL.md +0 -254
  130. package/skills/writing-plans/SKILL.md +0 -276
  131. package/skills/writing-skills/SKILL.md +0 -260
@@ -0,0 +1,354 @@
1
+ //! Integration tests for the clawpowers-core cross-crate pipeline.
2
+ //!
3
+ //! These tests exercise the full workflows that the napi-rs FFI bindings expose
4
+ //! to JavaScript, but at the Rust level using the public crate APIs directly.
5
+ //!
6
+ //! Pipelines tested:
7
+ //! 1. Wallet → Policy → Fee → x402 payment format
8
+ //! 2. Canonical store → Compression → Index → Verification
9
+ //! 3. Security firewall → Evaluate write request → Audit log
10
+
11
+ // ─── Pipeline 1: wallet → policy → fee → x402 ───────────────────────────────
12
+
13
+ #[cfg(test)]
14
+ mod wallet_payment_pipeline {
15
+ use clawpowers_fee::{FeeSchedule, FeeType};
16
+ use clawpowers_policy::{PolicyDecision, ProposedTx, SpendingPolicy};
17
+ use clawpowers_tokens::TokenAmount;
18
+ use clawpowers_wallet::AgentWallet;
19
+ use clawpowers_x402::{X402Client, X402PaymentRequired};
20
+
21
+ /// Hard-coded Hardhat account #0 private key used for deterministic tests.
22
+ const TEST_KEY: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
23
+
24
+ fn usdc(amount: f64) -> TokenAmount {
25
+ TokenAmount::from_human(amount, 6)
26
+ }
27
+
28
+ /// Build a complete x402 header from wallet signing.
29
+ #[test]
30
+ fn test_wallet_sign_then_x402_header() {
31
+ // 1. Import wallet from known key.
32
+ let wallet = AgentWallet::from_private_key(TEST_KEY).expect("valid test key");
33
+ assert!(wallet.address().to_string().to_lowercase().contains("f39f"));
34
+
35
+ // 2. Sign a payment descriptor.
36
+ let descriptor = b"pay:USDC:1.00:8453:0xrecipient";
37
+ let sig = wallet.sign_message(descriptor).expect("sign");
38
+ assert_eq!(sig.as_bytes().len(), 65, "signature must be 65 bytes");
39
+ let sig_hex = format!("0x{}", hex_encode(&sig.as_bytes()));
40
+
41
+ // 3. Build x402 payment required struct.
42
+ let requirement = X402PaymentRequired {
43
+ payment_url: "https://pay.example.com/pay".to_string(),
44
+ amount: "1.00".to_string(),
45
+ token: "USDC".to_string(),
46
+ chain_id: 8453,
47
+ recipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
48
+ memo: None,
49
+ };
50
+
51
+ // 4. Format the X-Payment header.
52
+ let header = X402Client::create_payment_header(&requirement, &sig_hex);
53
+ assert!(header.contains("8453"), "chain_id must be in header");
54
+ assert!(header.contains(&sig_hex), "signature must be in header");
55
+ }
56
+
57
+ /// Policy → fee → verify the fee calculation is consistent.
58
+ #[test]
59
+ fn test_policy_permits_then_fee_calculated() {
60
+ // 1. Build a spending policy: max 500 USDC per tx.
61
+ let policy = SpendingPolicy::builder()
62
+ .max_per_tx(usdc(500.0))
63
+ .fail_closed(true)
64
+ .build();
65
+
66
+ // 2. Define a proposed 100 USDC transaction.
67
+ let tx = ProposedTx {
68
+ recipient: "0x0000000000000000000000000000000000000001"
69
+ .parse()
70
+ .expect("valid address"),
71
+ amount: usdc(100.0),
72
+ merchant_allowlist_check: false,
73
+ };
74
+
75
+ // 3. Policy evaluation must approve.
76
+ assert_eq!(policy.evaluate(&tx), PolicyDecision::Approve);
77
+
78
+ // 4. Calculate 30 bps fee on the approved amount.
79
+ let schedule = FeeSchedule::new(
80
+ 30, // tx_bps
81
+ 50, // swap_bps
82
+ "0x0000000000000000000000000000000000000000"
83
+ .parse()
84
+ .expect("zero address"),
85
+ );
86
+ let calc = schedule
87
+ .calculate(tx.amount, FeeType::Transaction)
88
+ .expect("fee calculation");
89
+
90
+ // 5. Invariant: gross = fee + net.
91
+ let gross: f64 = calc.gross_amount.to_human();
92
+ let fee: f64 = calc.fee_amount.to_human();
93
+ let net: f64 = calc.net_amount.to_human();
94
+ assert!((gross - fee - net).abs() < 1e-9, "gross != fee + net");
95
+
96
+ // 6. 30 bps of 100 USDC = 0.3 USDC.
97
+ assert!(
98
+ (fee - 0.3).abs() < 0.000_001,
99
+ "fee should be 0.3 USDC, got {fee}"
100
+ );
101
+ }
102
+
103
+ /// Policy denies oversized transactions.
104
+ #[test]
105
+ fn test_policy_denies_over_limit() {
106
+ let policy = SpendingPolicy::builder()
107
+ .max_per_tx(usdc(100.0))
108
+ .fail_closed(true)
109
+ .build();
110
+ let big_tx = ProposedTx {
111
+ recipient: "0x0000000000000000000000000000000000000001"
112
+ .parse()
113
+ .expect("valid"),
114
+ amount: usdc(200.0),
115
+ merchant_allowlist_check: false,
116
+ };
117
+ assert!(matches!(policy.evaluate(&big_tx), PolicyDecision::Deny(_)));
118
+ }
119
+
120
+ fn hex_encode(bytes: &[u8]) -> String {
121
+ bytes.iter().map(|b| format!("{b:02x}")).collect()
122
+ }
123
+ }
124
+
125
+ // ─── Pipeline 2: canonical store → compression → index → verification ────────
126
+
127
+ #[cfg(test)]
128
+ mod memory_pipeline {
129
+ use clawpowers_canonical::{CanonicalRecord, CanonicalStore};
130
+ use clawpowers_compression::{CompressionConfig, TurboCompressor};
131
+ use clawpowers_index::{InMemoryIndex, VectorIndex};
132
+ use clawpowers_verification::VerificationPipeline;
133
+
134
+ const DIM: usize = 32;
135
+
136
+ fn make_compressor() -> TurboCompressor {
137
+ TurboCompressor::new(CompressionConfig {
138
+ dimensions: DIM,
139
+ quantization_bits: 8,
140
+ rotation_seed: 42,
141
+ })
142
+ }
143
+
144
+ fn simple_vector(seed: f32) -> Vec<f32> {
145
+ (0..DIM).map(|i| seed + i as f32 * 0.01).collect()
146
+ }
147
+
148
+ /// Full memory pipeline: insert → compress → index → search → verify.
149
+ #[test]
150
+ fn test_full_memory_pipeline() {
151
+ // 1. Create in-memory canonical store.
152
+ let store = CanonicalStore::in_memory().expect("in-memory store");
153
+
154
+ // 2. Insert three records.
155
+ let rec1 = CanonicalRecord::new(
156
+ "embeddings",
157
+ "first document text",
158
+ Some(simple_vector(0.1)),
159
+ serde_json::json!({"doc_id": 1}),
160
+ "test-pipeline",
161
+ );
162
+ let rec2 = CanonicalRecord::new(
163
+ "embeddings",
164
+ "second document text",
165
+ Some(simple_vector(0.5)),
166
+ serde_json::json!({"doc_id": 2}),
167
+ "test-pipeline",
168
+ );
169
+ let rec3 = CanonicalRecord::new(
170
+ "embeddings",
171
+ "third document text",
172
+ Some(simple_vector(0.9)),
173
+ serde_json::json!({"doc_id": 3}),
174
+ "test-pipeline",
175
+ );
176
+
177
+ let id1 = store.insert(&rec1).expect("insert rec1");
178
+ let id2 = store.insert(&rec2).expect("insert rec2");
179
+ let id3 = store.insert(&rec3).expect("insert rec3");
180
+
181
+ // 3. Compress the embeddings and index them.
182
+ let compressor = make_compressor();
183
+ let mut index = InMemoryIndex::with_dimensions(DIM);
184
+
185
+ for &id in &[id1, id2, id3] {
186
+ let record = store.get(&id).expect("get").expect("present");
187
+ let embedding = record.embedding.expect("has embedding");
188
+ // Compress to verify the pipeline works (store compressed stats).
189
+ let _ = compressor.compress(&embedding).expect("compress");
190
+ // Index the original vector for ANN search.
191
+ index.insert(id, embedding).expect("index");
192
+ }
193
+
194
+ assert_eq!(index.len(), 3);
195
+
196
+ // 4. Search: query close to rec1's vector (seed 0.1).
197
+ let query = simple_vector(0.11); // very close to rec1
198
+ let results = index.search(&query, 1).expect("search");
199
+ assert_eq!(results.len(), 1);
200
+ assert_eq!(results[0].id, id1, "nearest should be rec1");
201
+
202
+ // 5. Verify integrity of all records.
203
+ let pipeline = VerificationPipeline::new(store);
204
+ for &id in &[id1, id2, id3] {
205
+ let result = pipeline.verify(&id).expect("verify");
206
+ assert!(
207
+ matches!(
208
+ result,
209
+ clawpowers_verification::VerificationResult::Verified(_)
210
+ ),
211
+ "all records should be verified"
212
+ );
213
+ }
214
+ }
215
+
216
+ /// Compression produces smaller byte footprint than original f32 slice.
217
+ #[test]
218
+ fn test_compression_reduces_size() {
219
+ let compressor = make_compressor();
220
+ let original = simple_vector(0.5);
221
+ let original_bytes = original.len() * 4; // f32 = 4 bytes each
222
+ let compressed = compressor.compress(&original).expect("compress");
223
+ assert!(
224
+ compressed.byte_size() < original_bytes,
225
+ "compressed ({}) should be smaller than original ({original_bytes})",
226
+ compressed.byte_size()
227
+ );
228
+ }
229
+
230
+ /// Decompressed vector is within 5% L2 error of the original.
231
+ #[test]
232
+ fn test_compression_roundtrip_accuracy() {
233
+ let compressor = make_compressor();
234
+ let original = simple_vector(0.3);
235
+ let compressed = compressor.compress(&original).expect("compress");
236
+ let restored = compressor.decompress(&compressed).expect("decompress");
237
+
238
+ let l2: f32 = original
239
+ .iter()
240
+ .zip(restored.iter())
241
+ .map(|(a, b)| (a - b) * (a - b))
242
+ .sum::<f32>()
243
+ .sqrt();
244
+ let norm: f32 = original.iter().map(|x| x * x).sum::<f32>().sqrt();
245
+ let relative_err = if norm > f32::EPSILON { l2 / norm } else { l2 };
246
+ assert!(
247
+ relative_err < 0.05,
248
+ "relative L2 error {relative_err:.4} should be < 5%"
249
+ );
250
+ }
251
+ }
252
+
253
+ // ─── Pipeline 3: security firewall → evaluate → audit log ────────────────────
254
+
255
+ #[cfg(test)]
256
+ mod security_pipeline {
257
+ use clawpowers_security::{
258
+ AuditLog, FirewallDecision, TrustLevel, WriteFirewall, WriteRequest,
259
+ };
260
+
261
+ fn make_firewall() -> WriteFirewall {
262
+ WriteFirewall::new(vec!["agents".to_string(), "memory".to_string()])
263
+ }
264
+
265
+ fn req(namespace: &str, content: &str, trust: TrustLevel, source: &str) -> WriteRequest {
266
+ WriteRequest {
267
+ namespace: namespace.to_string(),
268
+ content: content.to_string(),
269
+ trust_level: trust,
270
+ source: source.to_string(),
271
+ }
272
+ }
273
+
274
+ /// Full security pipeline: evaluate request, record in audit log.
275
+ #[test]
276
+ fn test_full_security_pipeline() {
277
+ let firewall = make_firewall();
278
+ let mut audit = AuditLog::new();
279
+
280
+ // 1. Approved agent write.
281
+ let r1 = req("agents", "store memory fact", TrustLevel::Agent, "agent-1");
282
+ let d1 = firewall.evaluate(&r1);
283
+ assert_eq!(d1, FirewallDecision::Allow);
284
+ audit.record(&r1, d1, "write");
285
+
286
+ // 2. Denied: unauthorized namespace.
287
+ let r2 = req("secrets", "leak private key", TrustLevel::Agent, "agent-1");
288
+ let d2 = firewall.evaluate(&r2);
289
+ assert!(matches!(d2, FirewallDecision::Deny(_)));
290
+ audit.record(&r2, d2, "write");
291
+
292
+ // 3. External write with injection attempt.
293
+ let r3 = req(
294
+ "memory",
295
+ "hello; DROP users;--",
296
+ TrustLevel::External,
297
+ "api-gateway",
298
+ );
299
+ let d3 = firewall.evaluate(&r3);
300
+ // Should be sanitized or allowed; not panicking.
301
+ audit.record(&r3, d3, "write");
302
+
303
+ // 4. Verify audit log has all 3 entries.
304
+ assert_eq!(audit.len(), 3);
305
+
306
+ // 5. Query by namespace.
307
+ let agent_entries = audit.query("agents", 10);
308
+ assert_eq!(agent_entries.len(), 1);
309
+ assert_eq!(agent_entries[0].source, "agent-1");
310
+
311
+ let secret_entries = audit.query("secrets", 10);
312
+ assert_eq!(secret_entries.len(), 1);
313
+ assert!(matches!(
314
+ secret_entries[0].decision,
315
+ FirewallDecision::Deny(_)
316
+ ));
317
+ }
318
+
319
+ /// System trust level always allowed in permitted namespaces.
320
+ #[test]
321
+ fn test_system_trust_always_allowed() {
322
+ let firewall = make_firewall();
323
+ let r = req("agents", "system operation", TrustLevel::System, "sys");
324
+ assert_eq!(firewall.evaluate(&r), FirewallDecision::Allow);
325
+ }
326
+
327
+ /// Content length enforcement.
328
+ #[test]
329
+ fn test_content_length_limit() {
330
+ let mut firewall = make_firewall();
331
+ firewall.max_content_length = 10;
332
+ let r = req(
333
+ "agents",
334
+ "this content is way too long",
335
+ TrustLevel::Agent,
336
+ "a",
337
+ );
338
+ assert!(matches!(firewall.evaluate(&r), FirewallDecision::Deny(_)));
339
+ }
340
+
341
+ /// Audit log query respects limit.
342
+ #[test]
343
+ fn test_audit_log_limit() {
344
+ let firewall = make_firewall();
345
+ let mut audit = AuditLog::new();
346
+ for i in 0..5 {
347
+ let r = req("agents", &format!("content {i}"), TrustLevel::Agent, "a");
348
+ let d = firewall.evaluate(&r);
349
+ audit.record(&r, d, "write");
350
+ }
351
+ let limited = audit.query("agents", 3);
352
+ assert_eq!(limited.len(), 3);
353
+ }
354
+ }
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "clawpowers-pyo3"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+
7
+ [lib]
8
+ name = "clawpowers_core"
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ pyo3 = { workspace = true }
13
+ serde = { workspace = true }
14
+ serde_json = { workspace = true }
15
+ alloy-primitives = { workspace = true }
16
+ uuid = { workspace = true }
17
+ clawpowers-wallet = { path = "../crates/wallet" }
18
+ clawpowers-tokens = { path = "../crates/tokens" }
19
+ clawpowers-fee = { path = "../crates/fee" }
20
+ clawpowers-x402 = { path = "../crates/x402" }
21
+ clawpowers-canonical = { path = "../crates/canonical" }
22
+ clawpowers-compression = { path = "../crates/compression" }
23
+ clawpowers-index = { path = "../crates/index" }
24
+ clawpowers-verification = { path = "../crates/verification" }
25
+ clawpowers-security = { path = "../crates/security" }
26
+ clawpowers-policy = { path = "../crates/policy" }
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "clawpowers-core"
7
+ requires-python = ">=3.9"
8
+ classifiers = [
9
+ "Programming Language :: Rust",
10
+ "Programming Language :: Python :: Implementation :: CPython",
11
+ ]
12
+ dynamic = ["version"]
13
+
14
+ [tool.maturin]
15
+ features = ["pyo3/extension-module"]
16
+ manifest-path = "Cargo.toml"