agentstr 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
agentstr/__init__.py CHANGED
@@ -2,8 +2,14 @@
2
2
  AgentStr: Nostr extension for Agno AI agents
3
3
  """
4
4
 
5
- from nostr_sdk import ShippingCost, ShippingMethod # type: ignore
5
+ import importlib.metadata
6
+ import logging
6
7
 
8
+ from nostr_sdk import ShippingCost, ShippingMethod, Timestamp # type: ignore
9
+
10
+ from agentstr.nostr import EventId, Keys, Kind, NostrClient, generate_and_save_keys
11
+
12
+ from .buyer import BuyerTools
7
13
  from .merchant import MerchantTools
8
14
 
9
15
  # Import main classes to make them available at package level
@@ -11,27 +17,33 @@ from .models import AgentProfile, MerchantProduct, MerchantStall, NostrProfile
11
17
 
12
18
  # Import version from pyproject.toml at runtime
13
19
  try:
14
- from importlib.metadata import version
15
-
16
- __version__ = version("agentstr")
17
- except Exception:
20
+ __version__ = importlib.metadata.version("agentstr")
21
+ except importlib.metadata.PackageNotFoundError:
22
+ logging.warning("Package 'agentstr' not found. Falling back to 'unknown'.")
23
+ __version__ = "unknown"
24
+ except ImportError:
25
+ logging.warning("importlib.metadata is not available. Falling back to 'unknown'.")
18
26
  __version__ = "unknown"
19
27
 
28
+ # Define What is Exposed at the Package Level
20
29
  __all__ = [
30
+ # Merchant Tools
21
31
  "MerchantTools",
22
32
  "MerchantProduct",
23
33
  "MerchantStall",
34
+ # Buyer Tools
35
+ "BuyerTools",
36
+ # Shipping
24
37
  "ShippingCost",
25
38
  "ShippingMethod",
26
- ]
27
-
28
- from agentstr.nostr import EventId, Keys, NostrClient, ProductData, StallData
29
-
30
- __all__ = [
39
+ # Nostr-related utils
31
40
  "EventId",
32
41
  "Keys",
42
+ "Kind",
33
43
  "NostrClient",
34
- "ProductData",
35
- "StallData",
44
+ "generate_and_save_keys",
45
+ "Timestamp",
46
+ # Models
36
47
  "AgentProfile",
48
+ "NostrProfile",
37
49
  ]
agentstr/buyer.py CHANGED
@@ -1,17 +1,24 @@
1
+ """
2
+ Module implementing the BuyerTools Toolkit for Agno agents.
3
+ """
4
+
1
5
  import json
2
6
  import logging
3
7
  from uuid import uuid4
4
8
 
5
- from agno.agent import AgentKnowledge # type: ignore
6
- from agno.document.base import Document
9
+ from pydantic import ConfigDict
7
10
 
8
11
  from agentstr.models import AgentProfile, NostrProfile
9
12
  from agentstr.nostr import NostrClient, PublicKey
10
13
 
11
14
  try:
15
+ from agno.agent import AgentKnowledge # type: ignore
16
+ from agno.document.base import Document
12
17
  from agno.tools import Toolkit
13
- except ImportError:
14
- raise ImportError("`agno` not installed. Please install using `pip install agno`")
18
+ except ImportError as exc:
19
+ raise ImportError(
20
+ "`agno` not installed. Please install using `pip install agno`"
21
+ ) from exc
15
22
 
16
23
 
17
24
  def _map_location_to_geohash(location: str) -> str:
@@ -21,20 +28,22 @@ def _map_location_to_geohash(location: str) -> str:
21
28
  TBD: Implement this function. Returning a fixed geohash for now.
22
29
 
23
30
  Args:
24
- location: location to map to a geohash. Can be a zip code, city, state, country, or latitude and longitude.
31
+ location: location to map to a geohash. Can be a zip code, city,
32
+ state, country, or latitude and longitude.
25
33
 
26
34
  Returns:
27
35
  str: geohash of the location or empty string if location is not found
28
36
  """
29
37
  if "snoqualmie" in location.lower():
30
38
  return "C23Q7U36W"
31
- else:
32
- return ""
39
+
40
+ return ""
33
41
 
34
42
 
35
43
  class BuyerTools(Toolkit):
36
44
  """
37
- BuyerTools is a toolkit that allows an agent to find sellers and transact with them over Nostr.
45
+ BuyerTools is a toolkit that allows an agent to find sellers and
46
+ transact with them over Nostr.
38
47
 
39
48
  Sellers are downloaded from the Nostr relay and cached.
40
49
  Sellers can be found by name or public key.
@@ -44,8 +53,6 @@ class BuyerTools(Toolkit):
44
53
  TBD: populate the sellers locations with info from stalls.
45
54
  """
46
55
 
47
- from pydantic import ConfigDict
48
-
49
56
  model_config = ConfigDict(
50
57
  arbitrary_types_allowed=True, extra="allow", validate_assignment=True
51
58
  )
@@ -92,8 +99,13 @@ class BuyerTools(Toolkit):
92
99
 
93
100
  Args:
94
101
  product: JSON string with product to purchase
102
+
103
+ Returns:
104
+ str: JSON string with status and message
95
105
  """
96
- return json.dumps({"status": "success", "message": "Product purchased"})
106
+ return json.dumps(
107
+ {"status": "success", "message": f"Product {product} purchased"}
108
+ )
97
109
 
98
110
  def find_seller_by_name(self, name: str) -> str:
99
111
  """Find a seller by name.
@@ -151,9 +163,6 @@ class BuyerTools(Toolkit):
151
163
  # Find sellers in the same geohash
152
164
  for seller in self.sellers:
153
165
  if geohash in seller.get_locations():
154
- # print(
155
- # f"geohash {geohash} found in seller {seller.get_name()} with locations {seller.get_locations()}"
156
- # )
157
166
  sellers.add(seller)
158
167
 
159
168
  if not sellers:
@@ -165,9 +174,17 @@ class BuyerTools(Toolkit):
165
174
  response = json.dumps([seller.to_dict() for seller in sellers])
166
175
  # print("find_sellers_by_location: storing response in knowledge base")
167
176
  self._store_response_in_knowledge_base(response)
168
- # print(f"Found {len(sellers)} sellers near {location}")
177
+ self.logger.info("Found %d sellers", len(sellers))
169
178
  return response
170
179
 
180
+ def get_nostr_client(self) -> NostrClient:
181
+ """Get the Nostr client.
182
+
183
+ Returns:
184
+ NostrClient: Nostr client
185
+ """
186
+ return self._nostr_client
187
+
171
188
  def get_profile(self) -> str:
172
189
  """Get the Nostr profile of the buyer agent.
173
190
 
@@ -204,7 +221,7 @@ class BuyerTools(Toolkit):
204
221
  response = json.dumps([stall.as_json() for stall in stalls])
205
222
  self._store_response_in_knowledge_base(response)
206
223
  return response
207
- except Exception as e:
224
+ except RuntimeError as e:
208
225
  response = json.dumps({"status": "error", "message": str(e)})
209
226
  return response
210
227
 
@@ -234,7 +251,7 @@ class BuyerTools(Toolkit):
234
251
  response = json.dumps([product.to_dict() for product in products])
235
252
  self._store_response_in_knowledge_base(response)
236
253
  return response
237
- except Exception as e:
254
+ except RuntimeError as e:
238
255
  response = json.dumps({"status": "error", "message": str(e)})
239
256
  return response
240
257
 
@@ -265,7 +282,8 @@ class BuyerTools(Toolkit):
265
282
  def _refresh_sellers(self) -> None:
266
283
  """
267
284
  Internal fucntion to retrieve a new list of sellers from the Nostr relay.
268
- The old list is discarded and the new list only contains unique sellers currently stored at the relay.
285
+ The old list is discarded and the new list only contains unique sellers
286
+ currently stored at the relay.
269
287
 
270
288
  Returns:
271
289
  List[NostrProfile]: List of Nostr profiles of all sellers.
@@ -274,11 +292,7 @@ class BuyerTools(Toolkit):
274
292
  if len(sellers) == 0:
275
293
  self.logger.info("No sellers found")
276
294
  else:
277
- self.logger.info(f"Found {len(sellers)} sellers")
278
-
279
- # Print the locations of the sellers
280
- # for seller in sellers:
281
- # print(f"Seller {seller.get_name()} has locations {seller.get_locations()}")
295
+ self.logger.info("Found %d sellers", len(sellers))
282
296
 
283
297
  self.sellers = sellers
284
298
 
agentstr/buyer.pyi CHANGED
@@ -19,6 +19,7 @@ class BuyerTools(Toolkit):
19
19
  def find_seller_by_name(self, name: str) -> str: ...
20
20
  def find_seller_by_public_key(self, public_key: str) -> str: ...
21
21
  def find_sellers_by_location(self, location: str) -> str: ...
22
+ def get_nostr_client(self) -> NostrClient: ...
22
23
  def get_profile(self) -> str: ...
23
24
  def get_relay(self) -> str: ...
24
25
  def get_seller_stalls(self, public_key: str) -> str: ...
agentstr/merchant.py CHANGED
@@ -1,30 +1,36 @@
1
+ """
2
+ Module implementing the MerchantTools Toolkit for Agno agents.
3
+ """
4
+
1
5
  import json
2
6
  import logging
3
7
  import time
4
8
  from typing import Any, List, Optional, Tuple, Union
5
9
 
10
+ from pydantic import ConfigDict
11
+
6
12
  from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
7
13
  from agentstr.nostr import EventId, NostrClient
8
14
 
9
15
  try:
10
16
  from agno.tools import Toolkit
11
- except ImportError:
12
- raise ImportError("`agno` not installed. Please install using `pip install agno`")
13
-
14
- from pydantic import BaseModel, ConfigDict
17
+ except ImportError as exc:
18
+ raise ImportError(
19
+ "`agno` not installed. Please install using `pip install agno`"
20
+ ) from exc
15
21
 
16
22
 
17
23
  class MerchantTools(Toolkit):
18
24
  """
19
- MerchantTools is a toolkit that allows a merchant to publish products and stalls to Nostr.
25
+ MerchantTools is a toolkit that allows a merchant to publish
26
+ products and stalls to Nostr.
20
27
 
21
28
  TBD:
22
- - Better differentiation between products and stalls in the database and products and stalls published.
29
+ - Better differentiation between products and stalls in the database
30
+ and products and stalls published.
23
31
 
24
32
  """
25
33
 
26
- from pydantic import ConfigDict
27
-
28
34
  model_config = ConfigDict(
29
35
  arbitrary_types_allowed=True, extra="allow", validate_assignment=True
30
36
  )
@@ -77,6 +83,12 @@ class MerchantTools(Toolkit):
77
83
  self.register(self.remove_product_by_name)
78
84
  self.register(self.remove_stall_by_name)
79
85
 
86
+ def get_nostr_client(self) -> NostrClient:
87
+ """
88
+ Get the NostrClient instance
89
+ """
90
+ return self._nostr_client
91
+
80
92
  def get_profile(self) -> str:
81
93
  """
82
94
  Get the merchant profile in JSON format
@@ -147,8 +159,8 @@ class MerchantTools(Toolkit):
147
159
  )
148
160
  # Pause for 0.5 seconds to avoid rate limiting
149
161
  time.sleep(0.5)
150
- except Exception as e:
151
- logging.error(f"Unable to publish product {product}. Error {e}")
162
+ except RuntimeError as e:
163
+ logging.error("Unable to publish product %s. Error %s", product, e)
152
164
  results.append(
153
165
  {"status": "error", "message": str(e), "product_name": product.name}
154
166
  )
@@ -159,7 +171,8 @@ class MerchantTools(Toolkit):
159
171
  self,
160
172
  ) -> str:
161
173
  """
162
- Publishes or updates to Nostr all stalls managed by the merchant and adds the corresponding EventId to the Stall DB
174
+ Publishes or updates to Nostr all stalls managed by the merchant and adds
175
+ the corresponding EventId to the Stall DB
163
176
 
164
177
  Returns:
165
178
  str: JSON array with status of all stall publishing operations
@@ -186,8 +199,8 @@ class MerchantTools(Toolkit):
186
199
  )
187
200
  # Pause for 0.5 seconds to avoid rate limiting
188
201
  time.sleep(0.5)
189
- except Exception as e:
190
- logging.error(f"Unable to publish stall {stall}. Error {e}")
202
+ except RuntimeError as e:
203
+ logging.error("Unable to publish stall %s. Error %s", stall, e)
191
204
  results.append(
192
205
  {"status": "error", "message": str(e), "stall_name": stall.name}
193
206
  )
@@ -196,7 +209,8 @@ class MerchantTools(Toolkit):
196
209
 
197
210
  def publish_new_product(self, product: MerchantProduct) -> str:
198
211
  """
199
- Publishes to Nostra new product that is not currently in the Merchant's Product DB and adds it to the Product DB
212
+ Publishes to Nostra new product that is not currently in the Merchant's
213
+ Product DB and adds it to the Product DB
200
214
 
201
215
  Args:
202
216
  product: MerchantProduct to be published
@@ -224,7 +238,7 @@ class MerchantTools(Toolkit):
224
238
  "product_name": product.name,
225
239
  }
226
240
  )
227
- except Exception as e:
241
+ except RuntimeError as e:
228
242
  return json.dumps(
229
243
  {"status": "error", "message": str(e), "product_name": product.name}
230
244
  )
@@ -233,7 +247,8 @@ class MerchantTools(Toolkit):
233
247
  """
234
248
  Publishes or updates to Nostra given product from the Merchant's Product DB
235
249
  Args:
236
- arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
250
+ arguments: JSON string that may contain
251
+ {"name": "product_name"} or just "product_name"
237
252
 
238
253
  Returns:
239
254
  str: JSON string with status of the operation
@@ -267,6 +282,8 @@ class MerchantTools(Toolkit):
267
282
  event_id = self._nostr_client.publish_product(product)
268
283
  # Update the product_db with the new event_id
269
284
  self.product_db[i] = (product, event_id)
285
+ # Pause for 0.5 seconds to avoid rate limiting
286
+ time.sleep(0.5)
270
287
  return json.dumps(
271
288
  {
272
289
  "status": "success",
@@ -274,9 +291,7 @@ class MerchantTools(Toolkit):
274
291
  "product_name": product.name,
275
292
  }
276
293
  )
277
- # Pause for 0.5 seconds to avoid rate limiting
278
- time.sleep(0.5)
279
- except Exception as e:
294
+ except RuntimeError as e:
280
295
  return json.dumps(
281
296
  {
282
297
  "status": "error",
@@ -376,7 +391,7 @@ class MerchantTools(Toolkit):
376
391
  )
377
392
  # Pause for 0.5 seconds to avoid rate limiting
378
393
  time.sleep(0.5)
379
- except Exception as e:
394
+ except RuntimeError as e:
380
395
  results.append(
381
396
  {
382
397
  "status": "error",
@@ -399,7 +414,7 @@ class MerchantTools(Toolkit):
399
414
 
400
415
  return json.dumps(results)
401
416
 
402
- except Exception as e:
417
+ except RuntimeError as e:
403
418
  return json.dumps(
404
419
  [{"status": "error", "message": str(e), "arguments": str(arguments)}]
405
420
  )
@@ -424,12 +439,13 @@ class MerchantTools(Toolkit):
424
439
  self.merchant_profile.get_picture(),
425
440
  )
426
441
  return json.dumps(event_id.__dict__)
427
- except Exception as e:
428
- raise RuntimeError(f"Unable to publish the profile: {e}")
442
+ except RuntimeError as e:
443
+ raise RuntimeError(f"Unable to publish the profile: {e}") from e
429
444
 
430
445
  def publish_new_stall(self, stall: MerchantStall) -> str:
431
446
  """
432
- Publishes to Nostr a new stall that is not currently in the Merchant's Stall DB and adds it to the Stall DB
447
+ Publishes to Nostr a new stall that is not currently in the Merchant's
448
+ Stall DB and adds it to the Stall DB
433
449
 
434
450
  Args:
435
451
  stall: MerchantStall to be published
@@ -444,7 +460,8 @@ class MerchantTools(Toolkit):
444
460
  raise ValueError("NostrClient not initialized")
445
461
 
446
462
  try:
447
- # We don't ned to convert to StallData. nostr_client.publish_stall() accepts a MerchantStall
463
+ # We don't ned to convert to StallData.
464
+ # nostr_client.publish_stall() accepts a MerchantStall
448
465
  # stall_data = stall.to_stall_data()
449
466
  # Publish using the synchronous method
450
467
  event_id = self._nostr_client.publish_stall(stall)
@@ -457,7 +474,7 @@ class MerchantTools(Toolkit):
457
474
  "stall_name": stall.name,
458
475
  }
459
476
  )
460
- except Exception as e:
477
+ except RuntimeError as e:
461
478
  return json.dumps(
462
479
  {"status": "error", "message": str(e), "stall_name": stall.name}
463
480
  )
@@ -514,10 +531,10 @@ class MerchantTools(Toolkit):
514
531
  for i, (stall, _) in enumerate(self.stall_db):
515
532
  if stall.name == stall_name:
516
533
  try:
517
- # We are not passing StallData to nostr_client.publish_stall() anymore
518
- # stall_data = stall.to_stall_data()
519
534
  event_id = self._nostr_client.publish_stall(stall)
520
535
  self.stall_db[i] = (stall, event_id)
536
+ # Pause for 0.5 seconds to avoid rate limiting
537
+ time.sleep(0.5)
521
538
  return json.dumps(
522
539
  {
523
540
  "status": "success",
@@ -525,9 +542,8 @@ class MerchantTools(Toolkit):
525
542
  "stall_name": stall.name,
526
543
  }
527
544
  )
528
- # Pause for 0.5 seconds to avoid rate limiting
529
- time.sleep(0.5)
530
- except Exception as e:
545
+
546
+ except RuntimeError as e:
531
547
  return json.dumps(
532
548
  [
533
549
  {
@@ -549,7 +565,7 @@ class MerchantTools(Toolkit):
549
565
  ]
550
566
  )
551
567
 
552
- except Exception as e:
568
+ except RuntimeError as e:
553
569
  return json.dumps(
554
570
  [{"status": "error", "message": str(e), "stall_name": "unknown"}]
555
571
  )
@@ -574,7 +590,9 @@ class MerchantTools(Toolkit):
574
590
  results.append(
575
591
  {
576
592
  "status": "skipped",
577
- "message": f"Product '{product.name}' has not been published yet",
593
+ "message": (
594
+ f"Product '{product.name}' has not been published yet"
595
+ ),
578
596
  "product_name": product.name,
579
597
  }
580
598
  )
@@ -597,7 +615,7 @@ class MerchantTools(Toolkit):
597
615
  )
598
616
  # Pause for 0.5 seconds to avoid rate limiting
599
617
  time.sleep(0.5)
600
- except Exception as e:
618
+ except RuntimeError as e:
601
619
  results.append(
602
620
  {"status": "error", "message": str(e), "product_name": product.name}
603
621
  )
@@ -606,7 +624,8 @@ class MerchantTools(Toolkit):
606
624
 
607
625
  def remove_all_stalls(self) -> str:
608
626
  """
609
- Removes from Nostr all stalls from the merchant and their corresponding products
627
+ Removes from Nostr all stalls from the merchant and their
628
+ corresponding products
610
629
 
611
630
  Returns:
612
631
  str: JSON array with status of all removal operations
@@ -631,7 +650,7 @@ class MerchantTools(Toolkit):
631
650
  results.append(
632
651
  {
633
652
  "status": "skipped",
634
- "message": f"Product '{product.name}' has not been published yet",
653
+ "message": "Unpublished product",
635
654
  "product_name": product.name,
636
655
  "stall_name": stall_name,
637
656
  }
@@ -653,7 +672,7 @@ class MerchantTools(Toolkit):
653
672
  "event_id": str(event_id),
654
673
  }
655
674
  )
656
- except Exception as e:
675
+ except RuntimeError as e:
657
676
  results.append(
658
677
  {
659
678
  "status": "error",
@@ -669,7 +688,7 @@ class MerchantTools(Toolkit):
669
688
  results.append(
670
689
  {
671
690
  "status": "skipped",
672
- "message": f"Stall '{stall_name}' has not been published yet",
691
+ "message": (f"Stall '{stall_name}' has not been published yet"),
673
692
  "stall_name": stall_name,
674
693
  }
675
694
  )
@@ -689,7 +708,7 @@ class MerchantTools(Toolkit):
689
708
  )
690
709
  # Pause for 0.5 seconds to avoid rate limiting
691
710
  time.sleep(0.5)
692
- except Exception as e:
711
+ except RuntimeError as e:
693
712
  results.append(
694
713
  {"status": "error", "message": str(e), "stall_name": stall_name}
695
714
  )
@@ -701,7 +720,8 @@ class MerchantTools(Toolkit):
701
720
  Removes from Nostr a product with the given name
702
721
 
703
722
  Args:
704
- arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
723
+ arguments: JSON string that may contain {"name": "product_name"}
724
+ or just "product_name"
705
725
 
706
726
  Returns:
707
727
  str: JSON string with status of the operation
@@ -754,7 +774,7 @@ class MerchantTools(Toolkit):
754
774
  "event_id": str(event_id),
755
775
  }
756
776
  )
757
- except Exception as e:
777
+ except RuntimeError as e:
758
778
  return json.dumps(
759
779
  {"status": "error", "message": str(e), "product_name": name}
760
780
  )
@@ -843,7 +863,7 @@ class MerchantTools(Toolkit):
843
863
  results.append(
844
864
  {
845
865
  "status": "skipped",
846
- "message": f"Product '{product.name}' has not been published yet",
866
+ "message": "Unpublished product",
847
867
  "product_name": product.name,
848
868
  "stall_name": stall_name,
849
869
  }
@@ -866,7 +886,7 @@ class MerchantTools(Toolkit):
866
886
  )
867
887
  # Pause for 0.5 seconds to avoid rate limiting
868
888
  time.sleep(0.5)
869
- except Exception as e:
889
+ except RuntimeError as e:
870
890
  results.append(
871
891
  {
872
892
  "status": "error",
@@ -883,7 +903,9 @@ class MerchantTools(Toolkit):
883
903
  results.append(
884
904
  {
885
905
  "status": "skipped",
886
- "message": f"Stall '{stall_name}' has not been published yet",
906
+ "message": (
907
+ f"Stall '{stall_name}' has not been published yet"
908
+ ),
887
909
  "stall_name": stall_name,
888
910
  }
889
911
  )
@@ -904,7 +926,7 @@ class MerchantTools(Toolkit):
904
926
  "event_id": str(stall_event_id),
905
927
  }
906
928
  )
907
- except Exception as e:
929
+ except RuntimeError as e:
908
930
  results.append(
909
931
  {
910
932
  "status": "error",
@@ -915,20 +937,7 @@ class MerchantTools(Toolkit):
915
937
 
916
938
  return json.dumps(results)
917
939
 
918
- except Exception as e:
940
+ except RuntimeError as e:
919
941
  return json.dumps(
920
942
  [{"status": "error", "message": str(e), "stall_name": "unknown"}]
921
943
  )
922
-
923
- # def get_event_id(self, response: Any) -> str:
924
- # """Convert any response to a string event ID.
925
-
926
- # Args:
927
- # response: Response that might contain an event ID
928
-
929
- # Returns:
930
- # str: String representation of event ID or empty string if None
931
- # """
932
- # if response is None:
933
- # return ""
934
- # return str(response)
agentstr/merchant.pyi CHANGED
@@ -18,6 +18,7 @@ class MerchantTools(Toolkit):
18
18
  stalls: List[MerchantStall],
19
19
  products: List[MerchantProduct],
20
20
  ) -> None: ...
21
+ def get_nostr_client(self) -> NostrClient: ...
21
22
  def get_profile(self) -> str: ...
22
23
  def get_relay(self) -> str: ...
23
24
  def get_products(self) -> str: ...
@@ -34,4 +35,3 @@ class MerchantTools(Toolkit):
34
35
  def remove_all_stalls(self) -> str: ...
35
36
  def remove_product_by_name(self, product_name: str) -> str: ...
36
37
  def remove_stall_by_name(self, stall_name: str) -> str: ...
37
- def get_event_id(self, response: Any) -> str: ...
agentstr/models.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from typing import List, Optional
3
+ from typing import List
4
4
 
5
5
  from nostr_sdk import (
6
6
  Keys,
@@ -131,9 +131,6 @@ class NostrProfile(Profile):
131
131
  def get_profile_url(self) -> str:
132
132
  return self.profile_url
133
133
 
134
- def get_zip_codes(self) -> List[str]:
135
- return self.zip_codes
136
-
137
134
  def to_json(self) -> str:
138
135
  # Ensure super().to_json() returns a dictionary
139
136
  parent_json = super().to_json()
@@ -287,14 +284,15 @@ class MerchantProduct(BaseModel):
287
284
  specs=self.specs,
288
285
  )
289
286
  except Exception as e:
290
- logging.error(f"Failed to convert to ProductData: {e}")
291
- logging.error(f"Shipping data: {self.shipping}")
287
+ logging.error("Failed to convert to ProductData: %s", e)
288
+ logging.error("Shipping data: %s", self.shipping)
292
289
  raise
293
290
 
294
291
  def to_dict(self) -> dict:
295
292
  """
296
293
  Returns a dictionary representation of the MerchantProduct.
297
- ShippingCost class is not serializable, so we need to convert it to a dictionary.
294
+ ShippingCost class is not serializable, so we need to convert it
295
+ to a dictionary.
298
296
 
299
297
  Returns:
300
298
  dict: dictionary representation of the MerchantProduct
@@ -347,8 +345,9 @@ class MerchantStall(BaseModel):
347
345
  def to_dict(self) -> dict:
348
346
  """
349
347
  Returns a dictionary representation of the MerchantStall.
350
- ShippingMethod class is not serializable, so we need to convert it to a dictionary.
351
- We can only access cost and id from the ShippingMethod class. We can't access name or regions.
348
+ ShippingMethod class is not serializable, so we need to convert
349
+ it to a dictionary. We can only access cost and id from the
350
+ ShippingMethod class. We can't access name or regions.
352
351
 
353
352
  Returns:
354
353
  dict: dictionary representation of the MerchantStall
agentstr/models.pyi CHANGED
@@ -1,5 +1,5 @@
1
1
  from logging import Logger
2
- from typing import ClassVar, List, Optional, Set
2
+ from typing import ClassVar, List, Set
3
3
 
4
4
  from nostr_sdk import (
5
5
  Keys,
agentstr/nostr.py CHANGED
@@ -1,19 +1,16 @@
1
+ """
2
+ Core Nostr utilities for agentstr.
3
+ """
4
+
5
+ import asyncio
1
6
  import json
2
7
  import logging
3
- import traceback
4
8
  from datetime import timedelta
5
9
  from pathlib import Path
6
- from typing import Dict, List, Optional, Tuple
10
+ from typing import Dict, List, Optional
7
11
 
8
12
  from agentstr.models import MerchantProduct, MerchantStall, NostrProfile
9
13
 
10
- try:
11
- import asyncio
12
- except ImportError:
13
- raise ImportError(
14
- "`asyncio` not installed. Please install using `pip install asyncio`"
15
- )
16
-
17
14
  try:
18
15
  from nostr_sdk import (
19
16
  Alphabet,
@@ -29,29 +26,26 @@ try:
29
26
  NostrSigner,
30
27
  ProductData,
31
28
  PublicKey,
32
- ShippingCost,
33
- ShippingMethod,
34
29
  SingleLetterTag,
35
30
  StallData,
36
31
  Tag,
37
32
  TagKind,
38
33
  TagStandard,
39
- Timestamp,
40
34
  )
41
-
42
- except ImportError:
35
+ except ImportError as exc:
43
36
  raise ImportError(
44
37
  "`nostr_sdk` not installed. Please install using `pip install nostr_sdk`"
45
- )
38
+ ) from exc
46
39
 
47
40
 
48
41
  class NostrClient:
49
42
  """
50
- NostrClient implements the set of Nostr utilities required for higher level functions implementing
51
- like the Marketplace.
43
+ NostrClient implements the set of Nostr utilities required for
44
+ higher level functions implementations like the Marketplace.
52
45
 
53
- Nostr is an asynchronous communication protocol. To hide this, NostrClient exposes synchronous functions.
54
- Users of the NostrClient should ignore `_async_` functions which are for internal purposes only.
46
+ Nostr is an asynchronous communication protocol. To hide this,
47
+ NostrClient exposes synchronous functions. Users of the NostrClient
48
+ should ignore `_async_` functions which are for internal purposes only.
55
49
  """
56
50
 
57
51
  logger = logging.getLogger("NostrClient")
@@ -148,7 +142,7 @@ class NostrClient:
148
142
  try:
149
143
  return asyncio.run(self._async_publish_product(product))
150
144
  except Exception as e:
151
- raise RuntimeError(f"Failed to publish product: {e}")
145
+ raise RuntimeError(f"Failed to publish product: {e}") from e
152
146
 
153
147
  def publish_profile(self, name: str, about: str, picture: str) -> EventId:
154
148
  """
@@ -183,7 +177,7 @@ class NostrClient:
183
177
  try:
184
178
  return asyncio.run(self._async_publish_stall(stall))
185
179
  except Exception as e:
186
- raise RuntimeError(f"Failed to publish stall: {e}")
180
+ raise RuntimeError(f"Failed to publish stall: {e}") from e
187
181
 
188
182
  def retrieve_products_from_seller(self, seller: PublicKey) -> List[MerchantProduct]:
189
183
  """
@@ -217,7 +211,7 @@ class NostrClient:
217
211
  products.append(MerchantProduct.from_product_data(product_data))
218
212
  return products
219
213
  except Exception as e:
220
- raise RuntimeError(f"Failed to retrieve products: {e}")
214
+ raise RuntimeError(f"Failed to retrieve products: {e}") from e
221
215
 
222
216
  def retrieve_profile(self, public_key: PublicKey) -> NostrProfile:
223
217
  """
@@ -235,7 +229,7 @@ class NostrClient:
235
229
  try:
236
230
  return asyncio.run(self._async_retrieve_profile(public_key))
237
231
  except Exception as e:
238
- raise RuntimeError(f"Failed to retrieve profile: {e}")
232
+ raise RuntimeError(f"Failed to retrieve profile: {e}") from e
239
233
 
240
234
  def retrieve_sellers(self) -> set[NostrProfile]:
241
235
  """
@@ -244,17 +238,18 @@ class NostrClient:
244
238
  Return set may be empty if metadata can't be retrieved for any author.
245
239
 
246
240
  Returns:
247
- set[NostrProfile]: set of seller profiles (skips authors with missing metadata)
241
+ set[NostrProfile]: set of seller profiles
242
+ (skips authors with missing metadata)
248
243
  """
249
244
 
250
- sellers: set[NostrProfile] = set()
245
+ # sellers: set[NostrProfile] = set()
251
246
 
252
247
  # First we retrieve all stalls from the relay
253
248
 
254
249
  try:
255
250
  events = asyncio.run(self._async_retrieve_all_stalls())
256
251
  except Exception as e:
257
- raise RuntimeError(f"Failed to retrieve stalls: {e}")
252
+ raise RuntimeError(f"Failed to retrieve stalls: {e}") from e
258
253
 
259
254
  # Now we search for unique npubs from the list of stalls
260
255
 
@@ -265,17 +260,16 @@ class NostrClient:
265
260
  if event.kind() == Kind(30017):
266
261
  # Is this event the first time we see this author?
267
262
  if event.author() not in authors:
268
- # First time we see this author. Let's add the profile to the dictionary
263
+ # First time we see this author.
264
+ # Let's add the profile to the dictionary
269
265
  try:
270
266
  profile = asyncio.run(
271
267
  self._async_retrieve_profile(event.author())
272
268
  )
273
- # Add the profile to the dictionary, associating it with the author's PublicKey
269
+ # Add profile to the dictionary
270
+ # associated with the author's PublicKey
274
271
  authors[event.author()] = profile
275
- except Exception as e:
276
- # print(
277
- # f"Failed to retrieve profile for {event.author().to_bech32()}: {e}"
278
- # )
272
+ except RuntimeError:
279
273
  continue
280
274
 
281
275
  # Now we add locations from the event locations to the profile
@@ -287,15 +281,10 @@ class NostrClient:
287
281
  extracted_geohash = string_repr.split("=")[1].rstrip(
288
282
  ")"
289
283
  ) # Splitting and removing the closing parenthesis
290
- # print(
291
- # f"Adding location {extracted_geohash} to profile {authors[event.author()].get_name()}"
292
- # )
284
+
293
285
  profile = authors[event.author()]
294
286
  profile.add_location(extracted_geohash)
295
287
  authors[event.author()] = profile
296
- # print(
297
- # f"New locations for {authors[event.author()].get_name()}: {authors[event.author()].get_locations()}"
298
- # )
299
288
  # else:
300
289
  # print(f"Unknown tag: {standardized_tag}")
301
290
 
@@ -311,6 +300,9 @@ class NostrClient:
311
300
 
312
301
  Returns:
313
302
  List[StallData]: list of stalls from the seller
303
+
304
+ Raises:
305
+ RuntimeError: if the stalls can't be retrieved
314
306
  """
315
307
  stalls = []
316
308
  try:
@@ -322,12 +314,12 @@ class NostrClient:
322
314
  content = event.content()
323
315
  stall = StallData.from_json(content)
324
316
  stalls.append(stall)
325
- except Exception as e:
326
- self.logger.warning(f"Failed to parse stall data: {e}")
317
+ except RuntimeError as e:
318
+ self.logger.warning("Failed to parse stall data: %s", e)
327
319
  continue
328
320
  return stalls
329
321
  except Exception as e:
330
- raise RuntimeError(f"Failed to retrieve stalls: {e}")
322
+ raise RuntimeError(f"Failed to retrieve stalls: {e}") from e
331
323
 
332
324
  @classmethod
333
325
  def set_logging_level(cls, logging_level: int) -> None:
@@ -339,15 +331,18 @@ class NostrClient:
339
331
  cls.logger.setLevel(logging_level)
340
332
  for handler in cls.logger.handlers:
341
333
  handler.setLevel(logging_level)
342
- cls.logger.info(f"Logging level set to {logging.getLevelName(logging_level)}")
334
+ cls.logger.info("Logging level set to %s", logging.getLevelName(logging_level))
343
335
 
344
- # ----------------------------------------------------------------------------------------------
345
- # --*-- async functions for internal use only. Developers should use synchronous functions above
346
- # ----------------------------------------------------------------------------------------------
336
+ # ----------------------------------------------------------------
337
+ # internal async functions.
338
+ # Developers should use synchronous functions above
339
+ # ----------------------------------------------------------------
347
340
 
348
341
  async def _async_connect(self) -> None:
349
- """Asynchronous function to add relay to the NostrClient instance and connect to it.
350
- TBD: refactor to not return anything if successful and raise an exception if not
342
+ """
343
+ Asynchronous function to add relay to the NostrClient
344
+ instance and connect to it.
345
+
351
346
 
352
347
  Raises:
353
348
  RuntimeError: if the relay can't be connected to
@@ -356,7 +351,7 @@ class NostrClient:
356
351
  if not self.connected:
357
352
  try:
358
353
  await self.client.add_relay(self.relay)
359
- NostrClient.logger.info(f"Relay {self.relay} succesfully added.")
354
+ NostrClient.logger.info("Relay %s successfully added.", self.relay)
360
355
  await self.client.connect()
361
356
  await asyncio.sleep(2) # give time for slower connections
362
357
  NostrClient.logger.info("Connected to relay.")
@@ -364,7 +359,7 @@ class NostrClient:
364
359
  except Exception as e:
365
360
  raise RuntimeError(
366
361
  f"Unable to connect to relay {self.relay}. Exception: {e}."
367
- )
362
+ ) from e
368
363
 
369
364
  async def _async_publish_event(self, event_builder: EventBuilder) -> EventId:
370
365
  """
@@ -380,9 +375,9 @@ class NostrClient:
380
375
  await self._async_connect()
381
376
 
382
377
  # Add debug logging
383
- NostrClient.logger.debug(f"Attempting to publish event: {event_builder}")
378
+ NostrClient.logger.debug("Attempting to publish event: %s", event_builder)
384
379
  NostrClient.logger.debug(
385
- f"Using keys: {self.keys.public_key().to_bech32()}"
380
+ "Using keys: %s", self.keys.public_key().to_bech32()
386
381
  )
387
382
 
388
383
  # Wait for connection and try to publish
@@ -392,19 +387,18 @@ class NostrClient:
392
387
  if not output:
393
388
  raise RuntimeError("No output received from send_event_builder")
394
389
  if len(output.success) == 0:
395
- raise RuntimeError(
396
- f"Event rejected by relay. Reason: {output.message if hasattr(output, 'message') else 'unknown'}"
397
- )
390
+ reason = getattr(output, "message", "unknown")
391
+ raise RuntimeError(f"Event rejected by relay. Reason: {reason}")
398
392
 
399
393
  NostrClient.logger.info(
400
- f"Event published with event id: {output.id.to_bech32()}"
394
+ "Event published with event id: %s", output.id.to_bech32()
401
395
  )
402
396
  return output.id
403
397
 
404
398
  except Exception as e:
405
- NostrClient.logger.error(f"Failed to publish event: {str(e)}")
399
+ NostrClient.logger.error("Failed to publish event: %s", str(e))
406
400
  NostrClient.logger.debug("Event details:", exc_info=True)
407
- raise RuntimeError(f"Unable to publish event: {str(e)}")
401
+ raise RuntimeError(f"Unable to publish event: {str(e)}") from e
408
402
 
409
403
  async def _async_publish_note(self, text: str) -> EventId:
410
404
  """
@@ -424,7 +418,8 @@ class NostrClient:
424
418
 
425
419
  async def _async_publish_product(self, product: MerchantProduct) -> EventId:
426
420
  """
427
- Asynchronous function to create or update a NIP-15 Marketplace product with event kind 30018
421
+ Asynchronous function to create or update a NIP-15
422
+ Marketplace product with event kind 30018
428
423
 
429
424
  Args:
430
425
  product: product to publish
@@ -443,7 +438,8 @@ class NostrClient:
443
438
  # We use the function to create the content field and discard the eventbuilder
444
439
  bad_event_builder = EventBuilder.product_data(product.to_product_data())
445
440
 
446
- # create an event from bad_event_builder to extract the content - not broadcasted
441
+ # create an event from bad_event_builder to extract the content -
442
+ # not broadcasted
447
443
  bad_event = await self.client.sign_event_builder(bad_event_builder)
448
444
  content = bad_event.content()
449
445
 
@@ -451,7 +447,7 @@ class NostrClient:
451
447
  good_event_builder = EventBuilder(Kind(30018), content).tags(
452
448
  [Tag.identifier(product.id), Tag.coordinate(coordinate_tag)]
453
449
  )
454
- self.logger.info("Product event: " + str(good_event_builder))
450
+ NostrClient.logger.info("Product event: %s", good_event_builder)
455
451
  return await self._async_publish_event(good_event_builder)
456
452
 
457
453
  async def _async_publish_profile(
@@ -480,7 +476,8 @@ class NostrClient:
480
476
 
481
477
  async def _async_publish_stall(self, stall: MerchantStall) -> EventId:
482
478
  """
483
- Asynchronous function to create or update a NIP-15 Marketplace stall with event kind 30017
479
+ Asynchronous function to create or update a NIP-15
480
+ Marketplace stall with event kind 30017
484
481
 
485
482
  Args:
486
483
  stall: stall to be published
@@ -496,7 +493,7 @@ class NostrClient:
496
493
  # [Tag.identifier(product.id), Tag.coordinate(coordinate_tag)]
497
494
  # )
498
495
 
499
- self.logger.info(f" Merchant Stall: {stall}")
496
+ NostrClient.logger.info("Merchant Stall: %s", stall)
500
497
  event_builder = EventBuilder.stall_data(stall.to_stall_data()).tags(
501
498
  [
502
499
  Tag.custom(
@@ -521,16 +518,16 @@ class NostrClient:
521
518
  try:
522
519
  await self._async_connect()
523
520
  except Exception as e:
524
- raise RuntimeError("Unable to connect to the relay")
521
+ raise RuntimeError("Unable to connect to the relay") from e
525
522
 
526
523
  try:
527
- filter = Filter().kind(Kind(30017))
524
+ events_filter = Filter().kind(Kind(30017))
528
525
  events = await self.client.fetch_events_from(
529
- urls=[self.relay], filter=filter, timeout=timedelta(seconds=2)
526
+ urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
530
527
  )
531
528
  return events
532
529
  except Exception as e:
533
- raise RuntimeError(f"Unable to retrieve stalls: {e}")
530
+ raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
534
531
 
535
532
  async def _async_retrieve_products_from_seller(self, seller: PublicKey) -> Events:
536
533
  """
@@ -548,17 +545,17 @@ class NostrClient:
548
545
  try:
549
546
  await self._async_connect()
550
547
  except Exception as e:
551
- raise RuntimeError("Unable to connect to the relay")
548
+ raise RuntimeError("Unable to connect to the relay") from e
552
549
 
553
550
  try:
554
551
  # print(f"Retrieving products from seller: {seller}")
555
- filter = Filter().kind(Kind(30018)).authors([seller])
552
+ events_filter = Filter().kind(Kind(30018)).authors([seller])
556
553
  events = await self.client.fetch_events_from(
557
- urls=[self.relay], filter=filter, timeout=timedelta(seconds=2)
554
+ urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
558
555
  )
559
556
  return events
560
557
  except Exception as e:
561
- raise RuntimeError(f"Unable to retrieve stalls: {e}")
558
+ raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
562
559
 
563
560
  async def _async_retrieve_profile(self, author: PublicKey) -> NostrProfile:
564
561
  """
@@ -576,7 +573,7 @@ class NostrClient:
576
573
  try:
577
574
  await self._async_connect()
578
575
  except Exception as e:
579
- raise RuntimeError("Unable to connect to the relay")
576
+ raise RuntimeError("Unable to connect to the relay") from e
580
577
 
581
578
  try:
582
579
  metadata = await self.client.fetch_metadata(
@@ -584,7 +581,7 @@ class NostrClient:
584
581
  )
585
582
  return NostrProfile.from_metadata(metadata, author)
586
583
  except Exception as e:
587
- raise RuntimeError(f"Unable to retrieve metadata: {e}")
584
+ raise RuntimeError(f"Unable to retrieve metadata: {e}") from e
588
585
 
589
586
  async def _async_retrieve_stalls_from_seller(self, seller: PublicKey) -> Events:
590
587
  """
@@ -602,16 +599,16 @@ class NostrClient:
602
599
  try:
603
600
  await self._async_connect()
604
601
  except Exception as e:
605
- raise RuntimeError("Unable to connect to the relay")
602
+ raise RuntimeError("Unable to connect to the relay") from e
606
603
 
607
604
  try:
608
- filter = Filter().kind(Kind(30017)).authors([seller])
605
+ events_filter = Filter().kind(Kind(30017)).authors([seller])
609
606
  events = await self.client.fetch_events_from(
610
- urls=[self.relay], filter=filter, timeout=timedelta(seconds=2)
607
+ urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
611
608
  )
612
609
  return events
613
610
  except Exception as e:
614
- raise RuntimeError(f"Unable to retrieve stalls: {e}")
611
+ raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
615
612
 
616
613
 
617
614
  def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
@@ -635,7 +632,7 @@ def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
635
632
  # Read existing .env content
636
633
  env_content = ""
637
634
  if env_path.exists():
638
- with open(env_path, "r") as f:
635
+ with open(env_path, "r", encoding="utf-8") as f:
639
636
  env_content = f.read()
640
637
 
641
638
  # Check if the env var already exists
@@ -655,7 +652,7 @@ def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
655
652
  new_lines.append(f"{env_var}={nsec}")
656
653
 
657
654
  # Write back to .env
658
- with open(env_path, "w") as f:
655
+ with open(env_path, "w", encoding="utf-8") as f:
659
656
  f.write("\n".join(new_lines))
660
657
  if new_lines: # Add final newline if there's content
661
658
  f.write("\n")
agentstr/nostr.pyi CHANGED
@@ -1,3 +1,13 @@
1
+ """
2
+ Type stubs for the Nostr module.
3
+
4
+ This file provides type annotations for the Nostr module, enabling better
5
+ type checking and autocompletion in IDEs. It defines the expected types
6
+ for classes, functions, and variables used within the Nostr module.
7
+
8
+ Note: This is a type stub file and does not contain any executable code.
9
+ """
10
+
1
11
  from logging import Logger
2
12
  from pathlib import Path
3
13
  from typing import ClassVar, List, Optional
@@ -17,7 +27,6 @@ from nostr_sdk import ( # type: ignore
17
27
  ShippingCost,
18
28
  ShippingMethod,
19
29
  StallData,
20
- Tag,
21
30
  Timestamp,
22
31
  )
23
32
 
@@ -41,6 +50,11 @@ __all__ = [
41
50
  ]
42
51
 
43
52
  class NostrClient:
53
+ """
54
+ NostrClient implements the set of Nostr utilities required for higher level functions
55
+ implementations like the Marketplace.
56
+ """
57
+
44
58
  logger: ClassVar[Logger]
45
59
  relay: str
46
60
  keys: Keys
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: agentstr
3
- Version: 0.1.12
4
- Summary: Nostr extension for Agno AI agents
5
- Author-email: Synvya <info@synvya.com>
3
+ Version: 0.1.14
4
+ Summary: Tools for a Nostr agentic ecosystem
5
+ Author-email: Synvya <synvya@synvya.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://www.synvya.com
8
8
  Project-URL: Repository, https://github.com/synvya/agentstr
@@ -13,18 +13,21 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: agno>=1.1.1
15
15
  Requires-Dist: openai>=1.50.0
16
- Requires-Dist: packaging>=24.0
17
16
  Requires-Dist: nostr_sdk>=0.39.0
18
17
  Requires-Dist: pydantic>=2.0.0
19
- Requires-Dist: cassandra-driver>=3.29.2
20
- Requires-Dist: cassio>=0.1.10
21
18
  Provides-Extra: dev
22
19
  Requires-Dist: pytest>=7.0; extra == "dev"
23
20
  Requires-Dist: pytest-asyncio>=0.23.5; extra == "dev"
24
21
  Requires-Dist: black>=23.0; extra == "dev"
25
22
  Requires-Dist: isort>=5.0; extra == "dev"
26
23
  Requires-Dist: mypy>=1.0; extra == "dev"
27
- Requires-Dist: python-dotenv>=1.0; extra == "dev"
24
+ Requires-Dist: pylint>=3.0; extra == "dev"
25
+ Provides-Extra: examples
26
+ Requires-Dist: python-dotenv>=1.0; extra == "examples"
27
+ Requires-Dist: cassandra-driver>=3.29.2; extra == "examples"
28
+ Requires-Dist: cassio>=0.1.10; extra == "examples"
29
+ Requires-Dist: fastapi>=0.110.0; extra == "examples"
30
+ Requires-Dist: uvicorn>=0.30.0; extra == "examples"
28
31
 
29
32
  # AgentStr
30
33
 
@@ -0,0 +1,15 @@
1
+ agentstr/__init__.py,sha256=rbFtFOldBSbeDX_8Xva3YHRC9c3nPFA9mJXS3UZhJ4s,1294
2
+ agentstr/buyer.py,sha256=I1tApMjlq0Bg5Qk7twcAbF2rf_L5I2L9qzQO5sKv2RE,9912
3
+ agentstr/buyer.pyi,sha256=AqdKa7nQWohxHaoKHjCNpOpDB6rsWGWAlASNhwEw45g,1219
4
+ agentstr/merchant.py,sha256=3Fz4hrxyb5m7Kk47RodNC-Vyjm9iV7bI4ncPF8EMkhw,35078
5
+ agentstr/merchant.pyi,sha256=mqak--H7D_b7o8JNQlQRmov2An-defyBGRJNhMNchXQ,1437
6
+ agentstr/models.py,sha256=lEoopEQYIqI0egVxoXhBLlBh35C0WQmR_oBKm5Sexwk,11423
7
+ agentstr/models.pyi,sha256=k1_D-afE17gybHhtQMqA6cx7H5lYh7Fg0gbXclxyP4A,2857
8
+ agentstr/nostr.py,sha256=u2jDwQDmg01UCThDy1WW9lOPvAHyI1WMbtxFFi_2q08,22703
9
+ agentstr/nostr.pyi,sha256=nlBdOOI6vPboACjBvrQZKHy3BtCjboaClG9ZVD2X8XQ,3118
10
+ agentstr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ agentstr-0.1.14.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
12
+ agentstr-0.1.14.dist-info/METADATA,sha256=FV-_lJhW2H7K66AinZpTUB0o_kIjh0mBXS0Y7khKtkE,4667
13
+ agentstr-0.1.14.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
14
+ agentstr-0.1.14.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
15
+ agentstr-0.1.14.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- agentstr/__init__.py,sha256=t2nKJcQ0cq3uYc5sadpJGXSO08UViAEfifCQE9pa068,800
2
- agentstr/buyer.py,sha256=rifdCC4FSmbLimA94AhSp3UaCA5vxtH61j_KgVJwkVo,9848
3
- agentstr/buyer.pyi,sha256=Zs2SbItabztcGg_cJF8_5Mf-p7Me34hxoFHEkC1inDU,1168
4
- agentstr/merchant.py,sha256=Uf3LkV5HlyuOZBA-covkUnDYaxI91yHvmVYOD8kk4cM,35225
5
- agentstr/merchant.pyi,sha256=bAYP8qz9GTGNeyFEkZsc4CqdCsC2HV4nrV0dWyYu1VY,1440
6
- agentstr/models.py,sha256=-anXkTmOANTKw-elxKG-RZCarCGBIWlUHiUoMBrE3FQ,11488
7
- agentstr/models.pyi,sha256=QVnjv01cuMynOjzuEcct72yO-LUWpdxEeiEzcf7QTl4,2867
8
- agentstr/nostr.py,sha256=VSC5gDN8XM54BSlfQ0yYv2N3DdTVVviWg5VJkJOD5R0,23199
9
- agentstr/nostr.pyi,sha256=A-Dq0ZVuZuXEXnMfgVhKRym4ubFnsSjFrgH1Db1JMCI,2647
10
- agentstr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- agentstr-0.1.12.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
12
- agentstr-0.1.12.dist-info/METADATA,sha256=idnYX1pNr1qsbQXDOTdHmmPBEy3pa9RLdinZn6PeIsE,4475
13
- agentstr-0.1.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
14
- agentstr-0.1.12.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
15
- agentstr-0.1.12.dist-info/RECORD,,