@wlfi-agent/cli 1.4.16 → 1.4.18

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 (97) hide show
  1. package/Cargo.lock +26 -20
  2. package/Cargo.toml +1 -1
  3. package/README.md +61 -28
  4. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  5. package/crates/vault-cli-admin/src/main.rs +639 -16
  6. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  7. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  8. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  9. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  10. package/crates/vault-cli-agent/Cargo.toml +1 -0
  11. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  12. package/crates/vault-cli-agent/src/main.rs +648 -32
  13. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  14. package/crates/vault-cli-daemon/src/main.rs +617 -67
  15. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  16. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  17. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  18. package/crates/vault-daemon/src/persistence.rs +637 -100
  19. package/crates/vault-daemon/src/tests.rs +1013 -3
  20. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  21. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  22. package/crates/vault-domain/src/nonce.rs +4 -0
  23. package/crates/vault-domain/src/tests.rs +616 -0
  24. package/crates/vault-policy/src/engine.rs +55 -32
  25. package/crates/vault-policy/src/tests.rs +195 -0
  26. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  27. package/crates/vault-signer/Cargo.toml +3 -0
  28. package/crates/vault-signer/src/lib.rs +266 -40
  29. package/crates/vault-transport-unix/src/lib.rs +653 -5
  30. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  31. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  32. package/dist/cli.cjs +663 -190
  33. package/dist/cli.cjs.map +1 -1
  34. package/package.json +5 -2
  35. package/packages/cache/.turbo/turbo-build.log +53 -52
  36. package/packages/cache/coverage/clover.xml +529 -394
  37. package/packages/cache/coverage/coverage-final.json +2 -2
  38. package/packages/cache/coverage/index.html +21 -21
  39. package/packages/cache/coverage/src/client/index.html +1 -1
  40. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.html +1 -1
  42. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  43. package/packages/cache/coverage/src/index.html +1 -1
  44. package/packages/cache/coverage/src/index.ts.html +1 -1
  45. package/packages/cache/coverage/src/service/index.html +21 -21
  46. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  47. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  48. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  49. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  50. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  51. package/packages/cache/dist/index.cjs +2 -2
  52. package/packages/cache/dist/index.js +1 -1
  53. package/packages/cache/dist/service/index.cjs +2 -2
  54. package/packages/cache/dist/service/index.js +1 -1
  55. package/packages/cache/node_modules/.bin/tsc +2 -2
  56. package/packages/cache/node_modules/.bin/tsserver +2 -2
  57. package/packages/cache/node_modules/.bin/tsup +2 -2
  58. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  59. package/packages/cache/node_modules/.bin/vitest +4 -4
  60. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  61. package/packages/cache/src/service/index.test.ts +165 -19
  62. package/packages/cache/src/service/index.ts +38 -1
  63. package/packages/config/.turbo/turbo-build.log +18 -17
  64. package/packages/config/dist/index.cjs +0 -17
  65. package/packages/config/dist/index.cjs.map +1 -1
  66. package/packages/config/src/index.ts +0 -17
  67. package/packages/rpc/.turbo/turbo-build.log +32 -31
  68. package/packages/rpc/dist/index.cjs +0 -17
  69. package/packages/rpc/dist/index.cjs.map +1 -1
  70. package/packages/rpc/src/index.js +1 -0
  71. package/packages/ui/.turbo/turbo-build.log +44 -43
  72. package/packages/ui/dist/components/badge.d.ts +1 -1
  73. package/packages/ui/dist/components/button.d.ts +1 -1
  74. package/packages/ui/node_modules/.bin/tsc +2 -2
  75. package/packages/ui/node_modules/.bin/tsserver +2 -2
  76. package/packages/ui/node_modules/.bin/tsup +2 -2
  77. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  78. package/scripts/install-cli-launcher.mjs +37 -0
  79. package/scripts/install-rust-binaries.mjs +112 -0
  80. package/scripts/run-tests-isolated.mjs +210 -0
  81. package/src/cli.ts +310 -50
  82. package/src/lib/admin-reset.ts +15 -30
  83. package/src/lib/admin-setup.ts +246 -55
  84. package/src/lib/agent-auth-migrate.ts +5 -1
  85. package/src/lib/asset-broadcast.ts +15 -4
  86. package/src/lib/config-amounts.ts +6 -4
  87. package/src/lib/hidden-tty-prompt.js +1 -0
  88. package/src/lib/hidden-tty-prompt.ts +105 -0
  89. package/src/lib/keychain.ts +1 -0
  90. package/src/lib/local-admin-access.ts +4 -29
  91. package/src/lib/rust.ts +129 -33
  92. package/src/lib/signed-tx.ts +1 -0
  93. package/src/lib/sudo.ts +15 -5
  94. package/src/lib/wallet-profile.ts +3 -0
  95. package/src/lib/wallet-setup.ts +52 -0
  96. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  97. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -97,6 +97,7 @@ async fn reserve_nonce_is_monotonic_for_same_agent_and_chain() {
97
97
  agent_auth_token: agent_credentials.auth_token.clone(),
98
98
  chain_id: 1,
99
99
  min_nonce: 7,
100
+ exact_nonce: false,
100
101
  requested_at: now,
101
102
  expires_at: now + time::Duration::minutes(2),
102
103
  })
@@ -109,6 +110,7 @@ async fn reserve_nonce_is_monotonic_for_same_agent_and_chain() {
109
110
  agent_auth_token: agent_credentials.auth_token,
110
111
  chain_id: 1,
111
112
  min_nonce: 7,
113
+ exact_nonce: false,
112
114
  requested_at: now,
113
115
  expires_at: now + time::Duration::minutes(2),
114
116
  })
@@ -152,6 +154,7 @@ async fn reserve_nonce_replayed_request_id_is_rejected() {
152
154
  agent_auth_token: agent_credentials.auth_token,
153
155
  chain_id: 1,
154
156
  min_nonce: 0,
157
+ exact_nonce: false,
155
158
  requested_at: now,
156
159
  expires_at: now + time::Duration::minutes(2),
157
160
  };
@@ -203,6 +206,7 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
203
206
  agent_auth_token: agent_credentials.auth_token.clone(),
204
207
  chain_id: 56,
205
208
  min_nonce: 0,
209
+ exact_nonce: false,
206
210
  requested_at: now,
207
211
  expires_at: now + time::Duration::minutes(2),
208
212
  })
@@ -229,6 +233,7 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
229
233
  agent_auth_token: agent_credentials.auth_token,
230
234
  chain_id: 56,
231
235
  min_nonce: 0,
236
+ exact_nonce: false,
232
237
  requested_at: now,
233
238
  expires_at: now + time::Duration::minutes(2),
234
239
  })
@@ -237,6 +242,94 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
237
242
  assert_eq!(next.nonce, 0);
238
243
  }
239
244
 
245
+ #[tokio::test]
246
+ async fn exact_nonce_can_reuse_chain_nonce_after_signed_request_advances_head() {
247
+ let daemon = InMemoryDaemon::new(
248
+ "vault-password",
249
+ SoftwareSignerBackend::default(),
250
+ DaemonConfig::default(),
251
+ )
252
+ .expect("daemon");
253
+
254
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
255
+ let session = AdminSession {
256
+ vault_password: "vault-password".to_string(),
257
+ lease,
258
+ };
259
+ daemon
260
+ .add_policy(&session, policy_all_per_tx(1_000_000_000_000_000_000))
261
+ .await
262
+ .expect("add per-tx policy");
263
+ daemon
264
+ .add_policy(&session, policy_per_chain_gas(1, 1_000_000_000_000_000))
265
+ .await
266
+ .expect("add gas policy");
267
+
268
+ let key = daemon
269
+ .create_vault_key(&session, KeyCreateRequest::Generate)
270
+ .await
271
+ .expect("key");
272
+ let agent_credentials = daemon
273
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
274
+ .await
275
+ .expect("agent");
276
+
277
+ reserve_nonce_for_agent(&daemon, &agent_credentials, 1, 0).await;
278
+ daemon
279
+ .sign_for_agent(sign_request(
280
+ &agent_credentials,
281
+ AgentAction::BroadcastTx {
282
+ tx: BroadcastTx {
283
+ chain_id: 1,
284
+ nonce: 0,
285
+ to: "0x9300000000000000000000000000000000000000"
286
+ .parse()
287
+ .expect("to"),
288
+ value_wei: 0,
289
+ data_hex: "0x".to_string(),
290
+ gas_limit: 21_000,
291
+ max_fee_per_gas_wei: 1_000_000_000,
292
+ max_priority_fee_per_gas_wei: 1_000_000_000,
293
+ tx_type: 0x02,
294
+ delegation_enabled: false,
295
+ },
296
+ },
297
+ ))
298
+ .await
299
+ .expect("consume first reservation");
300
+
301
+ let now = time::OffsetDateTime::now_utc();
302
+ let exact = daemon
303
+ .reserve_nonce(NonceReservationRequest {
304
+ request_id: Uuid::new_v4(),
305
+ agent_key_id: agent_credentials.agent_key.id,
306
+ agent_auth_token: agent_credentials.auth_token.clone(),
307
+ chain_id: 1,
308
+ min_nonce: 0,
309
+ exact_nonce: true,
310
+ requested_at: now,
311
+ expires_at: now + time::Duration::minutes(2),
312
+ })
313
+ .await
314
+ .expect("exact reserve");
315
+ assert_eq!(exact.nonce, 0);
316
+
317
+ let next = daemon
318
+ .reserve_nonce(NonceReservationRequest {
319
+ request_id: Uuid::new_v4(),
320
+ agent_key_id: agent_credentials.agent_key.id,
321
+ agent_auth_token: agent_credentials.auth_token,
322
+ chain_id: 1,
323
+ min_nonce: 0,
324
+ exact_nonce: false,
325
+ requested_at: now,
326
+ expires_at: now + time::Duration::minutes(2),
327
+ })
328
+ .await
329
+ .expect("next reserve");
330
+ assert_eq!(next.nonce, 1);
331
+ }
332
+
240
333
  #[tokio::test]
241
334
  async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
242
335
  let daemon = InMemoryDaemon::new(
@@ -275,6 +368,7 @@ async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
275
368
  agent_auth_token: agent_credentials.auth_token.clone(),
276
369
  chain_id: 56,
277
370
  min_nonce: 0,
371
+ exact_nonce: false,
278
372
  requested_at: now,
279
373
  expires_at: now + time::Duration::minutes(2),
280
374
  })
@@ -291,6 +385,7 @@ async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
291
385
  agent_auth_token: agent_credentials.auth_token,
292
386
  chain_id: 56,
293
387
  min_nonce: 0,
388
+ exact_nonce: false,
294
389
  requested_at: time::OffsetDateTime::now_utc(),
295
390
  expires_at: time::OffsetDateTime::now_utc() + time::Duration::minutes(2),
296
391
  })
@@ -334,6 +429,7 @@ async fn reserve_nonce_rejects_zero_chain_id() {
334
429
  agent_auth_token: agent_credentials.auth_token,
335
430
  chain_id: 0,
336
431
  min_nonce: 0,
432
+ exact_nonce: false,
337
433
  requested_at: now,
338
434
  expires_at: now + time::Duration::minutes(2),
339
435
  })
@@ -382,6 +478,7 @@ async fn release_nonce_replayed_request_id_is_rejected() {
382
478
  agent_auth_token: agent_credentials.auth_token.clone(),
383
479
  chain_id: 1,
384
480
  min_nonce: 0,
481
+ exact_nonce: false,
385
482
  requested_at: now,
386
483
  expires_at: now + time::Duration::minutes(2),
387
484
  })
@@ -449,6 +546,7 @@ async fn release_nonce_rejects_non_owner_agent() {
449
546
  agent_auth_token: owner_credentials.auth_token.clone(),
450
547
  chain_id: 1,
451
548
  min_nonce: 0,
549
+ exact_nonce: false,
452
550
  requested_at: now,
453
551
  expires_at: now + time::Duration::minutes(2),
454
552
  })
@@ -614,6 +712,7 @@ async fn release_nonce_removes_reservation_from_snapshot() {
614
712
  agent_auth_token: agent_credentials.auth_token.clone(),
615
713
  chain_id: 1,
616
714
  min_nonce: 0,
715
+ exact_nonce: false,
617
716
  requested_at: now,
618
717
  expires_at: now + time::Duration::minutes(2),
619
718
  })
@@ -365,6 +365,7 @@ async fn daily_spend_limit_counts_in_scope_history_across_assets_and_chains() {
365
365
  ));
366
366
  }
367
367
 
368
+ #[cfg(not(coverage))]
368
369
  #[tokio::test]
369
370
  async fn persistent_store_restores_policies_and_agent_auth_state() {
370
371
  let state_path = unique_state_path("restore");
@@ -374,7 +375,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
374
375
  "vault-password",
375
376
  SoftwareSignerBackend::default(),
376
377
  config.clone(),
377
- PersistentStoreConfig::new(state_path.clone()),
378
+ PersistentStoreConfig::new_test(state_path.clone()),
378
379
  )
379
380
  .expect("daemon");
380
381
 
@@ -420,7 +421,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
420
421
  "vault-password",
421
422
  SoftwareSignerBackend::default(),
422
423
  config,
423
- PersistentStoreConfig::new(state_path.clone()),
424
+ PersistentStoreConfig::new_test(state_path.clone()),
424
425
  )
425
426
  .expect("restarted daemon");
426
427
  let lease = restarted
@@ -463,6 +464,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
463
464
  std::fs::remove_file(&state_path).expect("cleanup");
464
465
  }
465
466
 
467
+ #[cfg(not(coverage))]
466
468
  #[tokio::test]
467
469
  async fn persistent_store_rejects_wrong_password() {
468
470
  let state_path = unique_state_path("wrong-password");
@@ -472,7 +474,7 @@ async fn persistent_store_rejects_wrong_password() {
472
474
  "vault-password",
473
475
  SoftwareSignerBackend::default(),
474
476
  config.clone(),
475
- PersistentStoreConfig::new(state_path.clone()),
477
+ PersistentStoreConfig::new_test(state_path.clone()),
476
478
  )
477
479
  .expect("daemon");
478
480
  let lease = daemon.issue_lease("vault-password").await.expect("lease");
@@ -490,7 +492,7 @@ async fn persistent_store_rejects_wrong_password() {
490
492
  "wrong-password",
491
493
  SoftwareSignerBackend::default(),
492
494
  config,
493
- PersistentStoreConfig::new(state_path.clone()),
495
+ PersistentStoreConfig::new_test(state_path.clone()),
494
496
  ) {
495
497
  Ok(_) => panic!("wrong password must fail state load"),
496
498
  Err(err) => err,
@@ -519,6 +521,7 @@ fn validate_loaded_state_rejects_insecure_remote_http_relay_url() {
519
521
  }
520
522
 
521
523
  #[cfg(unix)]
524
+ #[cfg(not(coverage))]
522
525
  #[tokio::test]
523
526
  async fn persistent_store_rejects_group_writable_ancestor_directory() {
524
527
  use std::os::unix::fs::PermissionsExt;
@@ -542,7 +545,7 @@ async fn persistent_store_rejects_group_writable_ancestor_directory() {
542
545
  "vault-password",
543
546
  SoftwareSignerBackend::default(),
544
547
  DaemonConfig::default(),
545
- PersistentStoreConfig::new(leaf.join("daemon-state.enc")),
548
+ PersistentStoreConfig::new_test(leaf.join("daemon-state.enc")),
546
549
  ) {
547
550
  Ok(_) => panic!("group-writable ancestor must be rejected"),
548
551
  Err(err) => err,
@@ -557,6 +560,7 @@ async fn persistent_store_rejects_group_writable_ancestor_directory() {
557
560
  }
558
561
 
559
562
  #[cfg(unix)]
563
+ #[cfg(not(coverage))]
560
564
  #[tokio::test]
561
565
  async fn persistent_store_rejects_group_readable_state_file() {
562
566
  use std::os::unix::fs::PermissionsExt;
@@ -568,7 +572,7 @@ async fn persistent_store_rejects_group_readable_state_file() {
568
572
  "vault-password",
569
573
  SoftwareSignerBackend::default(),
570
574
  config.clone(),
571
- PersistentStoreConfig::new(state_path.clone()),
575
+ PersistentStoreConfig::new_test(state_path.clone()),
572
576
  )
573
577
  .expect("daemon");
574
578
  let lease = daemon.issue_lease("vault-password").await.expect("lease");
@@ -589,7 +593,7 @@ async fn persistent_store_rejects_group_readable_state_file() {
589
593
  "vault-password",
590
594
  SoftwareSignerBackend::default(),
591
595
  config,
592
- PersistentStoreConfig::new(state_path.clone()),
596
+ PersistentStoreConfig::new_test(state_path.clone()),
593
597
  ) {
594
598
  Ok(_) => panic!("group-readable state file must be rejected"),
595
599
  Err(err) => err,
@@ -18,6 +18,9 @@ pub struct NonceReservationRequest {
18
18
  pub chain_id: u64,
19
19
  /// Minimum nonce the caller is willing to reserve.
20
20
  pub min_nonce: u64,
21
+ /// When true, reserve exactly `min_nonce` instead of allowing a higher nonce head.
22
+ #[serde(default)]
23
+ pub exact_nonce: bool,
21
24
  /// Request timestamp.
22
25
  pub requested_at: OffsetDateTime,
23
26
  /// Request expiry timestamp.
@@ -33,6 +36,7 @@ impl fmt::Debug for NonceReservationRequest {
33
36
  .field("agent_auth_token", &"<redacted>")
34
37
  .field("chain_id", &self.chain_id)
35
38
  .field("min_nonce", &self.min_nonce)
39
+ .field("exact_nonce", &self.exact_nonce)
36
40
  .field("requested_at", &self.requested_at)
37
41
  .field("expires_at", &self.expires_at)
38
42
  .finish()