agentstr 0.1.12__py3-none-any.whl → 0.1.13__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- agentstr/__init__.py +24 -12
- agentstr/buyer.py +37 -23
- agentstr/buyer.pyi +1 -0
- agentstr/merchant.py +68 -59
- agentstr/merchant.pyi +1 -1
- agentstr/models.py +8 -9
- agentstr/models.pyi +1 -1
- agentstr/nostr.py +75 -78
- agentstr/nostr.pyi +15 -1
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/METADATA +10 -7
- agentstr-0.1.13.dist-info/RECORD +15 -0
- agentstr-0.1.12.dist-info/RECORD +0 -15
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/LICENSE +0 -0
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/WHEEL +0 -0
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/top_level.txt +0 -0
agentstr/__init__.py
CHANGED
@@ -2,8 +2,14 @@
|
|
2
2
|
AgentStr: Nostr extension for Agno AI agents
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
"
|
35
|
-
"
|
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
|
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(
|
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,
|
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
|
-
|
32
|
-
|
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
|
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(
|
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
|
-
|
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
|
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
|
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
|
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(
|
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(
|
13
|
-
|
14
|
-
from
|
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
|
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
|
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
|
151
|
-
logging.error(
|
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
|
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
|
190
|
-
logging.error(
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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.
|
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
|
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
|
-
|
529
|
-
|
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
|
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":
|
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
|
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
|
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":
|
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
|
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
|
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"}
|
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
|
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":
|
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
|
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":
|
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
|
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
|
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
|
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(
|
291
|
-
logging.error(
|
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
|
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
|
351
|
-
We can only access cost and id from the
|
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
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
|
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
|
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,
|
54
|
-
|
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
|
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.
|
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
|
269
|
+
# Add profile to the dictionary
|
270
|
+
# associated with the author's PublicKey
|
274
271
|
authors[event.author()] = profile
|
275
|
-
except
|
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
|
-
|
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
|
326
|
-
self.logger.warning(
|
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(
|
334
|
+
cls.logger.info("Logging level set to %s", logging.getLevelName(logging_level))
|
343
335
|
|
344
|
-
#
|
345
|
-
#
|
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
|
-
"""
|
350
|
-
|
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(
|
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(
|
378
|
+
NostrClient.logger.debug("Attempting to publish event: %s", event_builder)
|
384
379
|
NostrClient.logger.debug(
|
385
|
-
|
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
|
-
|
396
|
-
|
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
|
-
|
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(
|
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
|
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 -
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
524
|
+
events_filter = Filter().kind(Kind(30017))
|
528
525
|
events = await self.client.fetch_events_from(
|
529
|
-
urls=[self.relay], filter=
|
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
|
-
|
552
|
+
events_filter = Filter().kind(Kind(30018)).authors([seller])
|
556
553
|
events = await self.client.fetch_events_from(
|
557
|
-
urls=[self.relay], filter=
|
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
|
-
|
605
|
+
events_filter = Filter().kind(Kind(30017)).authors([seller])
|
609
606
|
events = await self.client.fetch_events_from(
|
610
|
-
urls=[self.relay], filter=
|
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.
|
4
|
-
Summary:
|
5
|
-
Author-email: Synvya <
|
3
|
+
Version: 0.1.13
|
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:
|
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.13.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
|
12
|
+
agentstr-0.1.13.dist-info/METADATA,sha256=JBXZQ5tw7lPrir_3TKkbYYXpLMqHlMofkfdwXKTP6Gk,4667
|
13
|
+
agentstr-0.1.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
14
|
+
agentstr-0.1.13.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
|
15
|
+
agentstr-0.1.13.dist-info/RECORD,,
|
agentstr-0.1.12.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|
File without changes
|