intentkit 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of intentkit might be problematic. Click here for more details.

Files changed (366) hide show
  1. intentkit/__init__.py +17 -0
  2. intentkit/abstracts/__init__.py +0 -0
  3. intentkit/abstracts/agent.py +60 -0
  4. intentkit/abstracts/api.py +4 -0
  5. intentkit/abstracts/engine.py +38 -0
  6. intentkit/abstracts/exception.py +9 -0
  7. intentkit/abstracts/graph.py +25 -0
  8. intentkit/abstracts/skill.py +129 -0
  9. intentkit/abstracts/twitter.py +54 -0
  10. intentkit/clients/__init__.py +14 -0
  11. intentkit/clients/cdp.py +53 -0
  12. intentkit/clients/twitter.py +445 -0
  13. intentkit/config/__init__.py +0 -0
  14. intentkit/config/config.py +164 -0
  15. intentkit/core/__init__.py +0 -0
  16. intentkit/core/agent.py +191 -0
  17. intentkit/core/api.py +40 -0
  18. intentkit/core/client.py +45 -0
  19. intentkit/core/credit.py +1767 -0
  20. intentkit/core/engine.py +1018 -0
  21. intentkit/core/node.py +223 -0
  22. intentkit/core/prompt.py +58 -0
  23. intentkit/core/skill.py +124 -0
  24. intentkit/models/agent.py +1689 -0
  25. intentkit/models/agent_data.py +810 -0
  26. intentkit/models/agent_schema.json +733 -0
  27. intentkit/models/app_setting.py +156 -0
  28. intentkit/models/base.py +9 -0
  29. intentkit/models/chat.py +581 -0
  30. intentkit/models/conversation.py +286 -0
  31. intentkit/models/credit.py +1406 -0
  32. intentkit/models/db.py +120 -0
  33. intentkit/models/db_mig.py +102 -0
  34. intentkit/models/generator.py +347 -0
  35. intentkit/models/llm.py +746 -0
  36. intentkit/models/redis.py +132 -0
  37. intentkit/models/skill.py +466 -0
  38. intentkit/models/user.py +243 -0
  39. intentkit/skills/__init__.py +12 -0
  40. intentkit/skills/acolyt/__init__.py +83 -0
  41. intentkit/skills/acolyt/acolyt.jpg +0 -0
  42. intentkit/skills/acolyt/ask.py +128 -0
  43. intentkit/skills/acolyt/base.py +28 -0
  44. intentkit/skills/acolyt/schema.json +89 -0
  45. intentkit/skills/aixbt/README.md +71 -0
  46. intentkit/skills/aixbt/__init__.py +73 -0
  47. intentkit/skills/aixbt/aixbt.jpg +0 -0
  48. intentkit/skills/aixbt/base.py +21 -0
  49. intentkit/skills/aixbt/projects.py +153 -0
  50. intentkit/skills/aixbt/schema.json +99 -0
  51. intentkit/skills/allora/__init__.py +83 -0
  52. intentkit/skills/allora/allora.jpeg +0 -0
  53. intentkit/skills/allora/base.py +28 -0
  54. intentkit/skills/allora/price.py +130 -0
  55. intentkit/skills/allora/schema.json +89 -0
  56. intentkit/skills/base.py +174 -0
  57. intentkit/skills/carv/README.md +95 -0
  58. intentkit/skills/carv/__init__.py +121 -0
  59. intentkit/skills/carv/base.py +183 -0
  60. intentkit/skills/carv/carv.webp +0 -0
  61. intentkit/skills/carv/fetch_news.py +92 -0
  62. intentkit/skills/carv/onchain_query.py +164 -0
  63. intentkit/skills/carv/schema.json +137 -0
  64. intentkit/skills/carv/token_info_and_price.py +110 -0
  65. intentkit/skills/cdp/__init__.py +137 -0
  66. intentkit/skills/cdp/base.py +21 -0
  67. intentkit/skills/cdp/cdp.png +0 -0
  68. intentkit/skills/cdp/get_balance.py +81 -0
  69. intentkit/skills/cdp/schema.json +473 -0
  70. intentkit/skills/chainlist/README.md +38 -0
  71. intentkit/skills/chainlist/__init__.py +54 -0
  72. intentkit/skills/chainlist/base.py +21 -0
  73. intentkit/skills/chainlist/chain_lookup.py +208 -0
  74. intentkit/skills/chainlist/chainlist.png +0 -0
  75. intentkit/skills/chainlist/schema.json +47 -0
  76. intentkit/skills/common/__init__.py +82 -0
  77. intentkit/skills/common/base.py +21 -0
  78. intentkit/skills/common/common.jpg +0 -0
  79. intentkit/skills/common/current_time.py +84 -0
  80. intentkit/skills/common/schema.json +57 -0
  81. intentkit/skills/cookiefun/README.md +121 -0
  82. intentkit/skills/cookiefun/__init__.py +78 -0
  83. intentkit/skills/cookiefun/base.py +41 -0
  84. intentkit/skills/cookiefun/constants.py +18 -0
  85. intentkit/skills/cookiefun/cookiefun.png +0 -0
  86. intentkit/skills/cookiefun/get_account_details.py +171 -0
  87. intentkit/skills/cookiefun/get_account_feed.py +282 -0
  88. intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
  89. intentkit/skills/cookiefun/get_sectors.py +128 -0
  90. intentkit/skills/cookiefun/schema.json +155 -0
  91. intentkit/skills/cookiefun/search_accounts.py +225 -0
  92. intentkit/skills/cryptocompare/__init__.py +130 -0
  93. intentkit/skills/cryptocompare/api.py +159 -0
  94. intentkit/skills/cryptocompare/base.py +303 -0
  95. intentkit/skills/cryptocompare/cryptocompare.png +0 -0
  96. intentkit/skills/cryptocompare/fetch_news.py +96 -0
  97. intentkit/skills/cryptocompare/fetch_price.py +99 -0
  98. intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
  99. intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
  100. intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
  101. intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
  102. intentkit/skills/cryptocompare/schema.json +168 -0
  103. intentkit/skills/cryptopanic/__init__.py +108 -0
  104. intentkit/skills/cryptopanic/base.py +51 -0
  105. intentkit/skills/cryptopanic/cryptopanic.png +0 -0
  106. intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
  107. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
  108. intentkit/skills/cryptopanic/schema.json +103 -0
  109. intentkit/skills/dapplooker/README.md +92 -0
  110. intentkit/skills/dapplooker/__init__.py +83 -0
  111. intentkit/skills/dapplooker/base.py +26 -0
  112. intentkit/skills/dapplooker/dapplooker.jpg +0 -0
  113. intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
  114. intentkit/skills/dapplooker/schema.json +91 -0
  115. intentkit/skills/defillama/__init__.py +323 -0
  116. intentkit/skills/defillama/api.py +315 -0
  117. intentkit/skills/defillama/base.py +135 -0
  118. intentkit/skills/defillama/coins/__init__.py +0 -0
  119. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
  120. intentkit/skills/defillama/coins/fetch_block.py +98 -0
  121. intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
  122. intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
  123. intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
  124. intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
  125. intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
  126. intentkit/skills/defillama/config/__init__.py +0 -0
  127. intentkit/skills/defillama/config/chains.py +433 -0
  128. intentkit/skills/defillama/defillama.jpeg +0 -0
  129. intentkit/skills/defillama/fees/__init__.py +0 -0
  130. intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
  131. intentkit/skills/defillama/schema.json +383 -0
  132. intentkit/skills/defillama/stablecoins/__init__.py +0 -0
  133. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
  134. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
  135. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
  136. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
  137. intentkit/skills/defillama/tests/__init__.py +0 -0
  138. intentkit/skills/defillama/tests/api_integration.test.py +192 -0
  139. intentkit/skills/defillama/tests/api_unit.test.py +583 -0
  140. intentkit/skills/defillama/tvl/__init__.py +0 -0
  141. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
  142. intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
  143. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
  144. intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
  145. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
  146. intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
  147. intentkit/skills/defillama/volumes/__init__.py +0 -0
  148. intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
  149. intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
  150. intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
  151. intentkit/skills/defillama/yields/__init__.py +0 -0
  152. intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
  153. intentkit/skills/defillama/yields/fetch_pools.py +126 -0
  154. intentkit/skills/dexscreener/__init__.py +93 -0
  155. intentkit/skills/dexscreener/base.py +133 -0
  156. intentkit/skills/dexscreener/dexscreener.png +0 -0
  157. intentkit/skills/dexscreener/model/__init__.py +0 -0
  158. intentkit/skills/dexscreener/model/search_token_response.py +82 -0
  159. intentkit/skills/dexscreener/schema.json +48 -0
  160. intentkit/skills/dexscreener/search_token.py +321 -0
  161. intentkit/skills/dune_analytics/__init__.py +103 -0
  162. intentkit/skills/dune_analytics/base.py +46 -0
  163. intentkit/skills/dune_analytics/dune.png +0 -0
  164. intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
  165. intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
  166. intentkit/skills/dune_analytics/schema.json +99 -0
  167. intentkit/skills/elfa/README.md +100 -0
  168. intentkit/skills/elfa/__init__.py +123 -0
  169. intentkit/skills/elfa/base.py +28 -0
  170. intentkit/skills/elfa/elfa.jpg +0 -0
  171. intentkit/skills/elfa/mention.py +504 -0
  172. intentkit/skills/elfa/schema.json +153 -0
  173. intentkit/skills/elfa/stats.py +118 -0
  174. intentkit/skills/elfa/tokens.py +126 -0
  175. intentkit/skills/enso/README.md +75 -0
  176. intentkit/skills/enso/__init__.py +114 -0
  177. intentkit/skills/enso/abi/__init__.py +0 -0
  178. intentkit/skills/enso/abi/approval.py +279 -0
  179. intentkit/skills/enso/abi/erc20.py +14 -0
  180. intentkit/skills/enso/abi/route.py +129 -0
  181. intentkit/skills/enso/base.py +44 -0
  182. intentkit/skills/enso/best_yield.py +286 -0
  183. intentkit/skills/enso/enso.jpg +0 -0
  184. intentkit/skills/enso/networks.py +105 -0
  185. intentkit/skills/enso/prices.py +93 -0
  186. intentkit/skills/enso/route.py +300 -0
  187. intentkit/skills/enso/schema.json +212 -0
  188. intentkit/skills/enso/tokens.py +223 -0
  189. intentkit/skills/enso/wallet.py +381 -0
  190. intentkit/skills/github/README.md +63 -0
  191. intentkit/skills/github/__init__.py +54 -0
  192. intentkit/skills/github/base.py +21 -0
  193. intentkit/skills/github/github.jpg +0 -0
  194. intentkit/skills/github/github_search.py +183 -0
  195. intentkit/skills/github/schema.json +59 -0
  196. intentkit/skills/heurist/__init__.py +143 -0
  197. intentkit/skills/heurist/base.py +26 -0
  198. intentkit/skills/heurist/heurist.png +0 -0
  199. intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
  200. intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
  201. intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
  202. intentkit/skills/heurist/image_generation_braindance.py +162 -0
  203. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
  204. intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
  205. intentkit/skills/heurist/image_generation_sdxl.py +161 -0
  206. intentkit/skills/heurist/schema.json +196 -0
  207. intentkit/skills/lifi/README.md +294 -0
  208. intentkit/skills/lifi/__init__.py +141 -0
  209. intentkit/skills/lifi/base.py +21 -0
  210. intentkit/skills/lifi/lifi.png +0 -0
  211. intentkit/skills/lifi/schema.json +89 -0
  212. intentkit/skills/lifi/token_execute.py +472 -0
  213. intentkit/skills/lifi/token_quote.py +190 -0
  214. intentkit/skills/lifi/utils.py +656 -0
  215. intentkit/skills/moralis/README.md +490 -0
  216. intentkit/skills/moralis/__init__.py +110 -0
  217. intentkit/skills/moralis/api.py +281 -0
  218. intentkit/skills/moralis/base.py +55 -0
  219. intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
  220. intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
  221. intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
  222. intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
  223. intentkit/skills/moralis/moralis.png +0 -0
  224. intentkit/skills/moralis/schema.json +156 -0
  225. intentkit/skills/moralis/tests/__init__.py +0 -0
  226. intentkit/skills/moralis/tests/test_wallet.py +511 -0
  227. intentkit/skills/nation/__init__.py +62 -0
  228. intentkit/skills/nation/base.py +31 -0
  229. intentkit/skills/nation/nation.png +0 -0
  230. intentkit/skills/nation/nft_check.py +106 -0
  231. intentkit/skills/nation/schema.json +58 -0
  232. intentkit/skills/openai/__init__.py +107 -0
  233. intentkit/skills/openai/base.py +32 -0
  234. intentkit/skills/openai/dalle_image_generation.py +128 -0
  235. intentkit/skills/openai/gpt_image_generation.py +152 -0
  236. intentkit/skills/openai/gpt_image_to_image.py +186 -0
  237. intentkit/skills/openai/image_to_text.py +126 -0
  238. intentkit/skills/openai/openai.png +0 -0
  239. intentkit/skills/openai/schema.json +139 -0
  240. intentkit/skills/portfolio/README.md +55 -0
  241. intentkit/skills/portfolio/__init__.py +151 -0
  242. intentkit/skills/portfolio/base.py +107 -0
  243. intentkit/skills/portfolio/constants.py +9 -0
  244. intentkit/skills/portfolio/moralis.png +0 -0
  245. intentkit/skills/portfolio/schema.json +237 -0
  246. intentkit/skills/portfolio/token_balances.py +155 -0
  247. intentkit/skills/portfolio/wallet_approvals.py +102 -0
  248. intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
  249. intentkit/skills/portfolio/wallet_history.py +155 -0
  250. intentkit/skills/portfolio/wallet_net_worth.py +112 -0
  251. intentkit/skills/portfolio/wallet_nfts.py +139 -0
  252. intentkit/skills/portfolio/wallet_profitability.py +101 -0
  253. intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
  254. intentkit/skills/portfolio/wallet_stats.py +79 -0
  255. intentkit/skills/portfolio/wallet_swaps.py +147 -0
  256. intentkit/skills/skills.toml +103 -0
  257. intentkit/skills/slack/__init__.py +98 -0
  258. intentkit/skills/slack/base.py +55 -0
  259. intentkit/skills/slack/get_channel.py +109 -0
  260. intentkit/skills/slack/get_message.py +136 -0
  261. intentkit/skills/slack/schedule_message.py +92 -0
  262. intentkit/skills/slack/schema.json +135 -0
  263. intentkit/skills/slack/send_message.py +81 -0
  264. intentkit/skills/slack/slack.jpg +0 -0
  265. intentkit/skills/system/__init__.py +90 -0
  266. intentkit/skills/system/base.py +22 -0
  267. intentkit/skills/system/read_agent_api_key.py +87 -0
  268. intentkit/skills/system/regenerate_agent_api_key.py +77 -0
  269. intentkit/skills/system/schema.json +53 -0
  270. intentkit/skills/system/system.svg +76 -0
  271. intentkit/skills/tavily/README.md +86 -0
  272. intentkit/skills/tavily/__init__.py +91 -0
  273. intentkit/skills/tavily/base.py +27 -0
  274. intentkit/skills/tavily/schema.json +119 -0
  275. intentkit/skills/tavily/tavily.jpg +0 -0
  276. intentkit/skills/tavily/tavily_extract.py +147 -0
  277. intentkit/skills/tavily/tavily_search.py +139 -0
  278. intentkit/skills/token/README.md +89 -0
  279. intentkit/skills/token/__init__.py +107 -0
  280. intentkit/skills/token/base.py +154 -0
  281. intentkit/skills/token/constants.py +9 -0
  282. intentkit/skills/token/erc20_transfers.py +145 -0
  283. intentkit/skills/token/moralis.png +0 -0
  284. intentkit/skills/token/schema.json +141 -0
  285. intentkit/skills/token/token_analytics.py +81 -0
  286. intentkit/skills/token/token_price.py +132 -0
  287. intentkit/skills/token/token_search.py +121 -0
  288. intentkit/skills/twitter/__init__.py +146 -0
  289. intentkit/skills/twitter/base.py +68 -0
  290. intentkit/skills/twitter/follow_user.py +69 -0
  291. intentkit/skills/twitter/get_mentions.py +124 -0
  292. intentkit/skills/twitter/get_timeline.py +111 -0
  293. intentkit/skills/twitter/get_user_by_username.py +84 -0
  294. intentkit/skills/twitter/get_user_tweets.py +123 -0
  295. intentkit/skills/twitter/like_tweet.py +65 -0
  296. intentkit/skills/twitter/post_tweet.py +90 -0
  297. intentkit/skills/twitter/reply_tweet.py +98 -0
  298. intentkit/skills/twitter/retweet.py +76 -0
  299. intentkit/skills/twitter/schema.json +258 -0
  300. intentkit/skills/twitter/search_tweets.py +115 -0
  301. intentkit/skills/twitter/twitter.png +0 -0
  302. intentkit/skills/unrealspeech/__init__.py +55 -0
  303. intentkit/skills/unrealspeech/base.py +21 -0
  304. intentkit/skills/unrealspeech/schema.json +100 -0
  305. intentkit/skills/unrealspeech/text_to_speech.py +177 -0
  306. intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
  307. intentkit/skills/venice_audio/__init__.py +106 -0
  308. intentkit/skills/venice_audio/base.py +119 -0
  309. intentkit/skills/venice_audio/input.py +41 -0
  310. intentkit/skills/venice_audio/schema.json +152 -0
  311. intentkit/skills/venice_audio/venice_audio.py +240 -0
  312. intentkit/skills/venice_audio/venice_logo.jpg +0 -0
  313. intentkit/skills/venice_image/README.md +119 -0
  314. intentkit/skills/venice_image/__init__.py +154 -0
  315. intentkit/skills/venice_image/api.py +138 -0
  316. intentkit/skills/venice_image/base.py +188 -0
  317. intentkit/skills/venice_image/config.py +35 -0
  318. intentkit/skills/venice_image/image_enhance/README.md +119 -0
  319. intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
  320. intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
  321. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
  322. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
  323. intentkit/skills/venice_image/image_generation/README.md +144 -0
  324. intentkit/skills/venice_image/image_generation/__init__.py +0 -0
  325. intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
  326. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
  327. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
  328. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
  329. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
  330. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
  331. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
  332. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
  333. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
  334. intentkit/skills/venice_image/image_upscale/README.md +111 -0
  335. intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
  336. intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
  337. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
  338. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
  339. intentkit/skills/venice_image/image_vision/README.md +112 -0
  340. intentkit/skills/venice_image/image_vision/__init__.py +0 -0
  341. intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
  342. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
  343. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
  344. intentkit/skills/venice_image/schema.json +267 -0
  345. intentkit/skills/venice_image/utils.py +78 -0
  346. intentkit/skills/venice_image/venice_image.jpg +0 -0
  347. intentkit/skills/web_scraper/README.md +82 -0
  348. intentkit/skills/web_scraper/__init__.py +92 -0
  349. intentkit/skills/web_scraper/base.py +21 -0
  350. intentkit/skills/web_scraper/langchain.png +0 -0
  351. intentkit/skills/web_scraper/schema.json +115 -0
  352. intentkit/skills/web_scraper/scrape_and_index.py +327 -0
  353. intentkit/utils/__init__.py +1 -0
  354. intentkit/utils/chain.py +436 -0
  355. intentkit/utils/error.py +134 -0
  356. intentkit/utils/logging.py +70 -0
  357. intentkit/utils/middleware.py +61 -0
  358. intentkit/utils/random.py +16 -0
  359. intentkit/utils/s3.py +267 -0
  360. intentkit/utils/slack_alert.py +79 -0
  361. intentkit/utils/tx.py +37 -0
  362. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
  363. intentkit-0.5.2.dist-info/RECORD +365 -0
  364. intentkit-0.5.0.dist-info/RECORD +0 -4
  365. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
  366. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,445 @@
1
+ import logging
2
+ import os
3
+ import tempfile
4
+ from datetime import datetime, timedelta, timezone
5
+ from typing import Any, Dict, List, NotRequired, Optional, TypedDict
6
+
7
+ import httpx
8
+ from pydantic import BaseModel, Field
9
+ from tweepy.asynchronous import AsyncClient
10
+
11
+ from intentkit.abstracts.skill import SkillStoreABC
12
+ from intentkit.abstracts.twitter import TwitterABC
13
+ from intentkit.models.agent_data import AgentData
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _clients_linked: Dict[str, "TwitterClient"] = {}
18
+ _clients_self_key: Dict[str, "TwitterClient"] = {}
19
+
20
+
21
+ class TwitterMedia(BaseModel):
22
+ """Model representing Twitter media from the API response."""
23
+
24
+ media_key: str
25
+ type: str
26
+ url: Optional[str] = None
27
+
28
+
29
+ class TwitterUser(BaseModel):
30
+ """Model representing a Twitter user from the API response."""
31
+
32
+ id: str
33
+ name: str
34
+ username: str
35
+ description: str
36
+ public_metrics: dict = Field(
37
+ description="User metrics including followers_count, following_count, tweet_count, listed_count, like_count, and media_count"
38
+ )
39
+ is_following: bool = Field(
40
+ description="Whether the authenticated user is following this user",
41
+ default=False,
42
+ )
43
+ is_follower: bool = Field(
44
+ description="Whether this user is following the authenticated user",
45
+ default=False,
46
+ )
47
+
48
+
49
+ class Tweet(BaseModel):
50
+ """Model representing a Twitter tweet."""
51
+
52
+ id: str
53
+ text: str
54
+ author_id: str
55
+ author: Optional[TwitterUser] = None
56
+ created_at: datetime
57
+ referenced_tweets: Optional[List["Tweet"]] = None
58
+ attachments: Optional[List[TwitterMedia]] = None
59
+
60
+
61
+ class TwitterClientConfig(TypedDict):
62
+ consumer_key: NotRequired[str]
63
+ consumer_secret: NotRequired[str]
64
+ access_token: NotRequired[str]
65
+ access_token_secret: NotRequired[str]
66
+
67
+
68
+ class TwitterClient(TwitterABC):
69
+ """Implementation of Twitter operations using Tweepy client.
70
+
71
+ This class provides concrete implementations for interacting with Twitter's API
72
+ through a Tweepy client, supporting both API key and OAuth2 authentication.
73
+
74
+ Args:
75
+ agent_id: The ID of the agent
76
+ skill_store: The skill store for retrieving data
77
+ config: Configuration dictionary that may contain API keys
78
+ """
79
+
80
+ def __init__(self, agent_id: str, skill_store: SkillStoreABC, config: Dict) -> None:
81
+ """Initialize the Twitter client.
82
+
83
+ Args:
84
+ agent_id: The ID of the agent
85
+ skill_store: The skill store for retrieving data
86
+ config: Configuration dictionary that may contain API keys
87
+ """
88
+ self.agent_id = agent_id
89
+ self._client: Optional[AsyncClient] = None
90
+ self._skill_store = skill_store
91
+ self._agent_data: Optional[AgentData] = None
92
+ self.use_key = _is_self_key(config)
93
+ self._config = config
94
+
95
+ async def get_client(self) -> AsyncClient:
96
+ """Get the initialized Twitter client.
97
+
98
+ Returns:
99
+ AsyncClient: The Twitter client if initialized
100
+ """
101
+ if not self._agent_data:
102
+ self._agent_data = await self._skill_store.get_agent_data(self.agent_id)
103
+ if not self._agent_data:
104
+ raise Exception(f"[{self.agent_id}] Agent data not found")
105
+ if not self._client:
106
+ # Check if we have API keys in config
107
+ if self.use_key:
108
+ self._client = AsyncClient(
109
+ consumer_key=self._config["consumer_key"],
110
+ consumer_secret=self._config["consumer_secret"],
111
+ access_token=self._config["access_token"],
112
+ access_token_secret=self._config["access_token_secret"],
113
+ return_type=dict,
114
+ )
115
+ # refresh userinfo if needed
116
+ if not self._agent_data.twitter_self_key_refreshed_at or (
117
+ self._agent_data.twitter_self_key_refreshed_at
118
+ < datetime.now(tz=timezone.utc) - timedelta(days=1)
119
+ ):
120
+ me = await self._client.get_me(
121
+ user_auth=self.use_key,
122
+ user_fields="id,username,name,verified",
123
+ )
124
+ if me and "data" in me and "id" in me["data"]:
125
+ await self._skill_store.set_agent_data(
126
+ self.agent_id,
127
+ {
128
+ "twitter_id": me["data"]["id"],
129
+ "twitter_username": me["data"]["username"],
130
+ "twitter_name": me["data"]["name"],
131
+ "twitter_is_verified": me["data"]["verified"],
132
+ "twitter_self_key_refreshed_at": datetime.now(
133
+ tz=timezone.utc
134
+ ),
135
+ },
136
+ )
137
+ self._agent_data = await self._skill_store.get_agent_data(
138
+ self.agent_id
139
+ )
140
+ logger.info(
141
+ f"Twitter self key client initialized. "
142
+ f"Use API key: {self.use_key}, "
143
+ f"User ID: {self.self_id}, "
144
+ f"Username: {self.self_username}, "
145
+ f"Name: {self.self_name}, "
146
+ f"Verified: {self.self_is_verified}"
147
+ )
148
+ return self._client
149
+ # Otherwise try to get OAuth2 tokens from agent data
150
+ if not self._agent_data.twitter_access_token:
151
+ raise Exception(f"[{self.agent_id}] Twitter access token not found")
152
+ if not self._agent_data.twitter_access_token_expires_at:
153
+ raise Exception(
154
+ f"[{self.agent_id}] Twitter access token expiration not found"
155
+ )
156
+ if self._agent_data.twitter_access_token_expires_at <= datetime.now(
157
+ tz=timezone.utc
158
+ ):
159
+ raise Exception(f"[{self.agent_id}] Twitter access token has expired")
160
+ self._client = AsyncClient(
161
+ bearer_token=self._agent_data.twitter_access_token,
162
+ return_type=dict,
163
+ )
164
+ return self._client
165
+ if not self.use_key:
166
+ # check if access token has expired
167
+ if self._agent_data.twitter_access_token_expires_at <= datetime.now(
168
+ tz=timezone.utc
169
+ ):
170
+ self._agent_data = await self._skill_store.get_agent_data(self.agent_id)
171
+ # check again
172
+ if self._agent_data.twitter_access_token_expires_at <= datetime.now(
173
+ tz=timezone.utc
174
+ ):
175
+ raise Exception(
176
+ f"[{self.agent_id}] Twitter access token has expired"
177
+ )
178
+ self._client = AsyncClient(
179
+ bearer_token=self._agent_data.twitter_access_token,
180
+ return_type=dict,
181
+ )
182
+ return self._client
183
+ return self._client
184
+
185
+ @property
186
+ def self_id(self) -> Optional[str]:
187
+ """Get the Twitter user ID.
188
+
189
+ Returns:
190
+ The Twitter user ID if available, None otherwise
191
+ """
192
+ if not self._client:
193
+ return None
194
+ if not self._agent_data:
195
+ return None
196
+ return self._agent_data.twitter_id
197
+
198
+ @property
199
+ def self_username(self) -> Optional[str]:
200
+ """Get the Twitter username.
201
+
202
+ Returns:
203
+ The Twitter username (without @ symbol) if available, None otherwise
204
+ """
205
+ if not self._client:
206
+ return None
207
+ if not self._agent_data:
208
+ return None
209
+ return self._agent_data.twitter_username
210
+
211
+ @property
212
+ def self_name(self) -> Optional[str]:
213
+ """Get the Twitter display name.
214
+
215
+ Returns:
216
+ The Twitter display name if available, None otherwise
217
+ """
218
+ if not self._client:
219
+ return None
220
+ if not self._agent_data:
221
+ return None
222
+ return self._agent_data.twitter_name
223
+
224
+ @property
225
+ def self_is_verified(self) -> Optional[bool]:
226
+ """Get the Twitter account verification status.
227
+
228
+ Returns:
229
+ The Twitter account verification status if available, None otherwise
230
+ """
231
+ if not self._client:
232
+ return None
233
+ if not self._agent_data:
234
+ return None
235
+ return self._agent_data.twitter_is_verified
236
+
237
+ def process_tweets_response(self, response: Dict[str, Any]) -> List[Tweet]:
238
+ """Process Twitter API response and convert it to a list of Tweet objects.
239
+
240
+ Args:
241
+ response: Raw Twitter API response containing tweets data and includes.
242
+
243
+ Returns:
244
+ List[Tweet]: List of processed Tweet objects.
245
+ """
246
+ result = []
247
+ if not response.get("data"):
248
+ return result
249
+
250
+ # Create lookup dictionaries from includes
251
+ users_dict = {}
252
+ if response.get("includes") and "users" in response.get("includes"):
253
+ users_dict = {
254
+ user["id"]: TwitterUser(
255
+ id=str(user["id"]),
256
+ name=user["name"],
257
+ username=user["username"],
258
+ description=user["description"],
259
+ public_metrics=user["public_metrics"],
260
+ is_following="following" in user.get("connection_status", []),
261
+ is_follower="followed_by" in user.get("connection_status", []),
262
+ )
263
+ for user in response.get("includes", {}).get("users", [])
264
+ }
265
+
266
+ media_dict = {}
267
+ if response.get("includes") and "media" in response.get("includes"):
268
+ media_dict = {
269
+ media["media_key"]: TwitterMedia(
270
+ media_key=media["media_key"],
271
+ type=media["type"],
272
+ url=media.get("url"),
273
+ )
274
+ for media in response.get("includes", {}).get("media", [])
275
+ }
276
+
277
+ tweets_dict = {}
278
+ if response.get("includes") and "tweets" in response.get("includes"):
279
+ tweets_dict = {
280
+ tweet["id"]: Tweet(
281
+ id=str(tweet["id"]),
282
+ text=tweet["text"],
283
+ author_id=str(tweet["author_id"]),
284
+ created_at=datetime.fromisoformat(
285
+ tweet["created_at"].replace("Z", "+00:00")
286
+ ),
287
+ author=users_dict.get(tweet["author_id"]),
288
+ referenced_tweets=None, # Will be populated in second pass
289
+ attachments=None, # Will be populated in second pass
290
+ )
291
+ for tweet in response.get("includes", {}).get("tweets", [])
292
+ }
293
+
294
+ # Process main tweets
295
+ for tweet_data in response["data"]:
296
+ tweet_id = tweet_data["id"]
297
+ author_id = tweet_data["author_id"]
298
+
299
+ # Process attachments if present
300
+ attachments = None
301
+ if (
302
+ "attachments" in tweet_data
303
+ and "media_keys" in tweet_data["attachments"]
304
+ ):
305
+ attachments = [
306
+ media_dict[media_key]
307
+ for media_key in tweet_data["attachments"]["media_keys"]
308
+ if media_key in media_dict
309
+ ]
310
+
311
+ # Process referenced tweets if present
312
+ referenced_tweets = None
313
+ if "referenced_tweets" in tweet_data:
314
+ referenced_tweets = [
315
+ tweets_dict[ref["id"]]
316
+ for ref in tweet_data["referenced_tweets"]
317
+ if ref["id"] in tweets_dict
318
+ ]
319
+
320
+ # Create the Tweet object
321
+ tweet = Tweet(
322
+ id=str(tweet_id),
323
+ text=tweet_data["text"],
324
+ author_id=str(author_id),
325
+ created_at=datetime.fromisoformat(
326
+ tweet_data["created_at"].replace("Z", "+00:00")
327
+ ),
328
+ author=users_dict.get(author_id),
329
+ referenced_tweets=referenced_tweets,
330
+ attachments=attachments,
331
+ )
332
+ result.append(tweet)
333
+
334
+ return result
335
+
336
+ async def upload_media(self, agent_id: str, image_url: str) -> List[str]:
337
+ """Upload media to Twitter and return the media IDs.
338
+
339
+ Args:
340
+ agent_id: The ID of the agent.
341
+ image_url: The URL of the image to upload.
342
+
343
+ Returns:
344
+ List[str]: A list of media IDs for the uploaded media.
345
+
346
+ Raises:
347
+ ValueError: If there's an error uploading the media.
348
+ """
349
+ # Get agent data to access the token
350
+ agent_data = await self._skill_store.get_agent_data(agent_id)
351
+ if not agent_data or not agent_data.twitter_access_token:
352
+ raise ValueError("Only linked X account can post media")
353
+
354
+ media_ids = []
355
+ # Download the image
356
+ async with httpx.AsyncClient() as session:
357
+ response = await session.get(image_url)
358
+ if response.status_code == 200:
359
+ # Create a temporary file to store the image
360
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
361
+ tmp_file.write(response.content)
362
+ tmp_file_path = tmp_file.name
363
+
364
+ # tweepy is outdated, we need to use httpx call new API
365
+ try:
366
+ # Upload the image directly to Twitter using the Media Upload API
367
+ headers = {
368
+ "Authorization": f"Bearer {agent_data.twitter_access_token}"
369
+ }
370
+
371
+ # Upload to Twitter's media/upload endpoint using multipart/form-data
372
+ upload_url = "https://api.twitter.com/2/media/upload"
373
+
374
+ # Get the content type from the response headers or default to image/jpeg
375
+ content_type = response.headers.get("content-type", "image/jpeg")
376
+
377
+ # Create a multipart form with the image file and required parameters
378
+ files = {
379
+ "media": (
380
+ "image",
381
+ open(tmp_file_path, "rb"),
382
+ content_type,
383
+ )
384
+ }
385
+
386
+ # Add required parameters according to new API
387
+ data = {"media_category": "tweet_image", "media_type": content_type}
388
+
389
+ upload_response = await session.post(
390
+ upload_url, headers=headers, files=files, data=data
391
+ )
392
+
393
+ if upload_response.status_code == 200:
394
+ media_data = upload_response.json()
395
+ if "data" in media_data and "id" in media_data["data"]:
396
+ media_ids.append(media_data["data"]["id"])
397
+ else:
398
+ raise ValueError(
399
+ f"Unexpected response format from Twitter media upload: {media_data}"
400
+ )
401
+ else:
402
+ raise ValueError(
403
+ f"Failed to upload image to Twitter. Status code: {upload_response.status_code}, Response: {upload_response.text}"
404
+ )
405
+ finally:
406
+ # Clean up the temporary file
407
+ if os.path.exists(tmp_file_path):
408
+ os.unlink(tmp_file_path)
409
+ else:
410
+ raise ValueError(
411
+ f"Failed to download image from URL: {image_url}. Status code: {response.status_code}"
412
+ )
413
+
414
+ return media_ids
415
+
416
+
417
+ def _is_self_key(config: Dict) -> bool:
418
+ return config.get("api_key_provider") == "agent_owner"
419
+
420
+
421
+ def get_twitter_client(
422
+ agent_id: str, skill_store: SkillStoreABC, config: Dict
423
+ ) -> "TwitterClient":
424
+ if _is_self_key(config):
425
+ if agent_id not in _clients_self_key:
426
+ _clients_self_key[agent_id] = TwitterClient(agent_id, skill_store, config)
427
+ return _clients_self_key[agent_id]
428
+ if agent_id not in _clients_linked:
429
+ _clients_linked[agent_id] = TwitterClient(agent_id, skill_store, config)
430
+ return _clients_linked[agent_id]
431
+
432
+
433
+ async def unlink_twitter(agent_id: str) -> AgentData:
434
+ logger.info(f"Unlinking Twitter for agent {agent_id}")
435
+ return await AgentData.patch(
436
+ agent_id,
437
+ {
438
+ "twitter_id": None,
439
+ "twitter_username": None,
440
+ "twitter_name": None,
441
+ "twitter_access_token": None,
442
+ "twitter_access_token_expires_at": None,
443
+ "twitter_refresh_token": None,
444
+ },
445
+ )
File without changes
@@ -0,0 +1,164 @@
1
+ # app/config.py
2
+ import json
3
+ import logging
4
+ import os
5
+
6
+ from dotenv import load_dotenv
7
+
8
+ from intentkit.utils.chain import ChainProvider, QuicknodeChainProvider
9
+ from intentkit.utils.logging import setup_logging
10
+ from intentkit.utils.s3 import init_s3
11
+ from intentkit.utils.slack_alert import init_slack
12
+
13
+ # Load environment variables from .env file
14
+ load_dotenv()
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def load_from_aws(name):
20
+ import botocore.session
21
+ from aws_secretsmanager_caching import SecretCache, SecretCacheConfig
22
+
23
+ client = botocore.session.get_session().create_client("secretsmanager")
24
+ cache_config = SecretCacheConfig()
25
+ cache = SecretCache(config=cache_config, client=client)
26
+ secret = cache.get_secret_string(name)
27
+ return json.loads(secret)
28
+
29
+
30
+ class Config:
31
+ def __init__(self):
32
+ # ==== this part can only be load from env
33
+ self.env = os.getenv("ENV", "local")
34
+ self.release = os.getenv("RELEASE", "local")
35
+ secret_name = os.getenv("AWS_SECRET_NAME")
36
+ db_secret_name = os.getenv("AWS_DB_SECRET_NAME")
37
+ # ==== load from aws secrets manager
38
+ if secret_name:
39
+ self.secrets = load_from_aws(secret_name)
40
+ else:
41
+ self.secrets = {}
42
+ if db_secret_name:
43
+ self.db = load_from_aws(db_secret_name)
44
+ # format the db config
45
+ self.db["port"] = str(self.db["port"])
46
+ # only keep the necessary fields
47
+ self.db = {
48
+ k: v
49
+ for k, v in self.db.items()
50
+ if k in ["username", "password", "host", "dbname", "port"]
51
+ }
52
+ else:
53
+ self.db = {
54
+ "username": os.getenv("DB_USERNAME"),
55
+ "password": os.getenv("DB_PASSWORD"),
56
+ "host": os.getenv("DB_HOST"),
57
+ "port": os.getenv("DB_PORT"),
58
+ "dbname": os.getenv("DB_NAME"),
59
+ }
60
+ # ==== this part can be load from env or aws secrets manager
61
+ self.db["auto_migrate"] = self.load("DB_AUTO_MIGRATE", "true") == "true"
62
+ self.debug = self.load("DEBUG") == "true"
63
+ self.debug_checkpoint = (
64
+ self.load("DEBUG_CHECKPOINT", "false") == "true"
65
+ ) # log with checkpoint
66
+ # Redis
67
+ self.redis_host = self.load("REDIS_HOST")
68
+ self.redis_port = int(self.load("REDIS_PORT", "6379"))
69
+ # AWS
70
+ self.aws_s3_bucket = self.load("AWS_S3_BUCKET")
71
+ self.aws_s3_cdn_url = self.load("AWS_S3_CDN_URL")
72
+ # Internal API
73
+ self.internal_base_url = self.load("INTERNAL_BASE_URL", "http://intent-api")
74
+ self.admin_auth_enabled = self.load("ADMIN_AUTH_ENABLED", "false") == "true"
75
+ self.admin_jwt_secret = self.load("ADMIN_JWT_SECRET")
76
+ self.debug_auth_enabled = self.load("DEBUG_AUTH_ENABLED", "false") == "true"
77
+ self.debug_username = self.load("DEBUG_USERNAME")
78
+ self.debug_password = self.load("DEBUG_PASSWORD")
79
+ self.admin_llm_skill_control = (
80
+ self.load("ADMIN_LLM_SKILL_CONTROL", "false") == "true"
81
+ )
82
+ # Payment
83
+ self.payment_enabled = self.load("PAYMENT_ENABLED", "false") == "true"
84
+ # Open API for agent
85
+ self.open_api_base_url = self.load("OPEN_API_BASE_URL", "http://localhost:8000")
86
+ # CDP
87
+ self.cdp_api_key_name = self.load("CDP_API_KEY_NAME")
88
+ self.cdp_api_key_private_key = self.load("CDP_API_KEY_PRIVATE_KEY")
89
+ # LLM providers
90
+ self.openai_api_key = self.load("OPENAI_API_KEY")
91
+ self.deepseek_api_key = self.load("DEEPSEEK_API_KEY")
92
+ self.xai_api_key = self.load("XAI_API_KEY")
93
+ self.eternal_api_key = self.load("ETERNAL_API_KEY")
94
+ self.reigent_api_key = self.load("REIGENT_API_KEY")
95
+ self.venice_api_key = self.load("VENICE_API_KEY")
96
+ self.system_prompt = self.load("SYSTEM_PROMPT")
97
+ self.input_token_limit = int(self.load("INPUT_TOKEN_LIMIT", "60000"))
98
+ # Telegram server settings
99
+ self.tg_base_url = self.load("TG_BASE_URL")
100
+ self.tg_server_host = self.load("TG_SERVER_HOST", "127.0.0.1")
101
+ self.tg_server_port = self.load("TG_SERVER_PORT", "8081")
102
+ self.tg_new_agent_poll_interval = self.load("TG_NEW_AGENT_POLL_INTERVAL", "60")
103
+ # Twitter
104
+ self.twitter_oauth2_client_id = self.load("TWITTER_OAUTH2_CLIENT_ID")
105
+ self.twitter_oauth2_client_secret = self.load("TWITTER_OAUTH2_CLIENT_SECRET")
106
+ self.twitter_oauth2_redirect_uri = self.load("TWITTER_OAUTH2_REDIRECT_URI")
107
+ self.twitter_entrypoint_interval = int(
108
+ self.load("TWITTER_ENTRYPOINT_INTERVAL", "5")
109
+ ) # in minutes
110
+ # Slack Alert
111
+ self.slack_alert_token = self.load("SLACK_ALERT_TOKEN")
112
+ self.slack_alert_channel = self.load("SLACK_ALERT_CHANNEL")
113
+ # Skills - Platform Hosted Keys
114
+ self.acolyt_api_key = self.load("ACOLYT_API_KEY")
115
+ self.allora_api_key = self.load("ALLORA_API_KEY")
116
+ self.elfa_api_key = self.load("ELFA_API_KEY")
117
+ self.heurist_api_key = self.load("HEURIST_API_KEY")
118
+ self.enso_api_token = self.load("ENSO_API_TOKEN")
119
+ self.dapplooker_api_key = self.load("DAPPLOOKER_API_KEY")
120
+ self.moralis_api_key = self.load("MORALIS_API_KEY")
121
+ self.tavily_api_key = self.load("TAVILY_API_KEY")
122
+ self.cookiefun_api_key = self.load("COOKIEFUN_API_KEY")
123
+ # Sentry
124
+ self.sentry_dsn = self.load("SENTRY_DSN")
125
+ self.sentry_sample_rate = float(self.load("SENTRY_SAMPLE_RATE", "0.1"))
126
+ self.sentry_traces_sample_rate = float(
127
+ self.load("SENTRY_TRACES_SAMPLE_RATE", "0.01")
128
+ )
129
+ self.sentry_profiles_sample_rate = float(
130
+ self.load("SENTRY_PROFILES_SAMPLE_RATE", "0.01")
131
+ )
132
+ # RPC Providers
133
+ self.quicknode_api_key = self.load("QUICKNODE_API_KEY")
134
+ if self.quicknode_api_key:
135
+ self.chain_provider: ChainProvider = QuicknodeChainProvider(
136
+ self.quicknode_api_key
137
+ )
138
+ if hasattr(self, "chain_provider"):
139
+ self.chain_provider.init_chain_configs()
140
+ self.rpc_networks = self.load(
141
+ "RPC_NETWORKS", "base-mainnet,base-sepolia,ethereum-sepolia,solana-mainnet"
142
+ )
143
+
144
+ # Nation
145
+ self.nation_api_key = self.load("NATION_API_KEY")
146
+ self.nation_api_url = self.load("NATION_API_URL", "")
147
+
148
+ # ===== config loaded
149
+ # Now we know the env, set up logging
150
+ setup_logging(self.env, self.debug)
151
+ logger.info("config loaded")
152
+ # If the slack alert token exists, init it
153
+ if self.slack_alert_token and self.slack_alert_channel:
154
+ init_slack(self.slack_alert_token, self.slack_alert_channel)
155
+ # If the AWS S3 bucket and CDN URL exist, init it
156
+ if self.aws_s3_bucket and self.aws_s3_cdn_url:
157
+ init_s3(self.aws_s3_bucket, self.aws_s3_cdn_url, self.env)
158
+
159
+ def load(self, key, default=None):
160
+ """Load a secret from the secrets map or env"""
161
+ return self.secrets.get(key, os.getenv(key, default))
162
+
163
+
164
+ config: Config = Config()
File without changes