opencandle 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/README.md +110 -87
  2. package/assets/logo.svg +187 -0
  3. package/dist/analysts/orchestrator.js +1 -2
  4. package/dist/analysts/orchestrator.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +38 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +34 -5
  9. package/dist/config.js +29 -8
  10. package/dist/config.js.map +1 -1
  11. package/dist/infra/browser.d.ts +10 -0
  12. package/dist/infra/browser.js +1 -0
  13. package/dist/infra/browser.js.map +1 -1
  14. package/dist/infra/cache.d.ts +4 -0
  15. package/dist/infra/cache.js +4 -0
  16. package/dist/infra/cache.js.map +1 -1
  17. package/dist/infra/native-dependencies.d.ts +1 -0
  18. package/dist/infra/native-dependencies.js +10 -0
  19. package/dist/infra/native-dependencies.js.map +1 -0
  20. package/dist/infra/node-version.d.ts +2 -0
  21. package/dist/infra/node-version.js +23 -0
  22. package/dist/infra/node-version.js.map +1 -0
  23. package/dist/infra/rate-limiter.js +6 -0
  24. package/dist/infra/rate-limiter.js.map +1 -1
  25. package/dist/memory/index.d.ts +2 -0
  26. package/dist/memory/index.js +1 -0
  27. package/dist/memory/index.js.map +1 -1
  28. package/dist/memory/sqlite.js +42 -4
  29. package/dist/memory/sqlite.js.map +1 -1
  30. package/dist/memory/storage.d.ts +6 -0
  31. package/dist/memory/storage.js +3 -3
  32. package/dist/memory/storage.js.map +1 -1
  33. package/dist/memory/tool-defaults.d.ts +8 -0
  34. package/dist/memory/tool-defaults.js +59 -0
  35. package/dist/memory/tool-defaults.js.map +1 -0
  36. package/dist/onboarding/connect.d.ts +35 -0
  37. package/dist/onboarding/connect.js +118 -0
  38. package/dist/onboarding/connect.js.map +1 -0
  39. package/dist/onboarding/credential-interceptor.d.ts +44 -0
  40. package/dist/onboarding/credential-interceptor.js +72 -0
  41. package/dist/onboarding/credential-interceptor.js.map +1 -0
  42. package/dist/onboarding/degradation-accumulator.d.ts +21 -0
  43. package/dist/onboarding/degradation-accumulator.js +55 -0
  44. package/dist/onboarding/degradation-accumulator.js.map +1 -0
  45. package/dist/onboarding/prompt-user.d.ts +23 -0
  46. package/dist/onboarding/prompt-user.js +61 -0
  47. package/dist/onboarding/prompt-user.js.map +1 -0
  48. package/dist/onboarding/providers.d.ts +116 -0
  49. package/dist/onboarding/providers.js +239 -0
  50. package/dist/onboarding/providers.js.map +1 -0
  51. package/dist/onboarding/state.d.ts +31 -2
  52. package/dist/onboarding/state.js +141 -13
  53. package/dist/onboarding/state.js.map +1 -1
  54. package/dist/onboarding/tool-helpers.d.ts +34 -0
  55. package/dist/onboarding/tool-helpers.js +80 -0
  56. package/dist/onboarding/tool-helpers.js.map +1 -0
  57. package/dist/onboarding/tool-tags.d.ts +37 -0
  58. package/dist/onboarding/tool-tags.js +149 -0
  59. package/dist/onboarding/tool-tags.js.map +1 -0
  60. package/dist/onboarding/validation.d.ts +19 -0
  61. package/dist/onboarding/validation.js +117 -0
  62. package/dist/onboarding/validation.js.map +1 -0
  63. package/dist/pi/opencandle-extension.d.ts +7 -1
  64. package/dist/pi/opencandle-extension.js +488 -13
  65. package/dist/pi/opencandle-extension.js.map +1 -1
  66. package/dist/pi/session-storage.d.ts +2 -0
  67. package/dist/pi/session-storage.js +5 -0
  68. package/dist/pi/session-storage.js.map +1 -0
  69. package/dist/pi/session.d.ts +4 -1
  70. package/dist/pi/session.js +25 -3
  71. package/dist/pi/session.js.map +1 -1
  72. package/dist/pi/setup.d.ts +1 -2
  73. package/dist/pi/setup.js +67 -120
  74. package/dist/pi/setup.js.map +1 -1
  75. package/dist/pi/tool-adapter.d.ts +2 -2
  76. package/dist/pi/tool-adapter.js +14 -1
  77. package/dist/pi/tool-adapter.js.map +1 -1
  78. package/dist/prompts/context-builder.d.ts +22 -0
  79. package/dist/prompts/context-builder.js +47 -11
  80. package/dist/prompts/context-builder.js.map +1 -1
  81. package/dist/prompts/disclaimer.d.ts +6 -0
  82. package/dist/prompts/disclaimer.js +9 -0
  83. package/dist/prompts/disclaimer.js.map +1 -0
  84. package/dist/prompts/workflow-prompts.d.ts +8 -0
  85. package/dist/prompts/workflow-prompts.js +39 -5
  86. package/dist/prompts/workflow-prompts.js.map +1 -1
  87. package/dist/providers/alpha-vantage.js +20 -1
  88. package/dist/providers/alpha-vantage.js.map +1 -1
  89. package/dist/providers/exa-search.d.ts +39 -0
  90. package/dist/providers/exa-search.js +276 -0
  91. package/dist/providers/exa-search.js.map +1 -0
  92. package/dist/providers/finnhub.d.ts +17 -0
  93. package/dist/providers/finnhub.js +94 -0
  94. package/dist/providers/finnhub.js.map +1 -0
  95. package/dist/providers/fred.js +13 -1
  96. package/dist/providers/fred.js.map +1 -1
  97. package/dist/providers/index.d.ts +2 -0
  98. package/dist/providers/index.js +1 -0
  99. package/dist/providers/index.js.map +1 -1
  100. package/dist/providers/provider-credential-error.d.ts +8 -0
  101. package/dist/providers/provider-credential-error.js +22 -0
  102. package/dist/providers/provider-credential-error.js.map +1 -0
  103. package/dist/providers/reddit.d.ts +8 -0
  104. package/dist/providers/reddit.js +36 -9
  105. package/dist/providers/reddit.js.map +1 -1
  106. package/dist/providers/twitter.js +2 -8
  107. package/dist/providers/twitter.js.map +1 -1
  108. package/dist/providers/web-search.d.ts +17 -0
  109. package/dist/providers/web-search.js +224 -0
  110. package/dist/providers/web-search.js.map +1 -0
  111. package/dist/providers/wrap-provider.d.ts +7 -0
  112. package/dist/providers/wrap-provider.js +15 -0
  113. package/dist/providers/wrap-provider.js.map +1 -1
  114. package/dist/providers/yahoo-finance.js +70 -33
  115. package/dist/providers/yahoo-finance.js.map +1 -1
  116. package/dist/routing/classify-intent.js +22 -0
  117. package/dist/routing/classify-intent.js.map +1 -1
  118. package/dist/routing/defaults.js +1 -1
  119. package/dist/routing/defaults.js.map +1 -1
  120. package/dist/routing/index.d.ts +4 -0
  121. package/dist/routing/index.js +3 -0
  122. package/dist/routing/index.js.map +1 -1
  123. package/dist/routing/router-llm-client.d.ts +11 -0
  124. package/dist/routing/router-llm-client.js +42 -0
  125. package/dist/routing/router-llm-client.js.map +1 -0
  126. package/dist/routing/router-prompt.d.ts +2 -0
  127. package/dist/routing/router-prompt.js +138 -0
  128. package/dist/routing/router-prompt.js.map +1 -0
  129. package/dist/routing/router-types.d.ts +62 -0
  130. package/dist/routing/router-types.js +2 -0
  131. package/dist/routing/router-types.js.map +1 -0
  132. package/dist/routing/router.d.ts +10 -0
  133. package/dist/routing/router.js +194 -0
  134. package/dist/routing/router.js.map +1 -0
  135. package/dist/runtime/session-coordinator.d.ts +63 -4
  136. package/dist/runtime/session-coordinator.js +155 -4
  137. package/dist/runtime/session-coordinator.js.map +1 -1
  138. package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
  139. package/dist/runtime/tool-defaults-wrapper.js +25 -0
  140. package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
  141. package/dist/sentiment/adapters/finnhub.d.ts +7 -0
  142. package/dist/sentiment/adapters/finnhub.js +39 -0
  143. package/dist/sentiment/adapters/finnhub.js.map +1 -0
  144. package/dist/sentiment/adapters/reddit.d.ts +11 -0
  145. package/dist/sentiment/adapters/reddit.js +54 -0
  146. package/dist/sentiment/adapters/reddit.js.map +1 -0
  147. package/dist/sentiment/adapters/twitter.d.ts +9 -0
  148. package/dist/sentiment/adapters/twitter.js +32 -0
  149. package/dist/sentiment/adapters/twitter.js.map +1 -0
  150. package/dist/sentiment/adapters/web.d.ts +9 -0
  151. package/dist/sentiment/adapters/web.js +40 -0
  152. package/dist/sentiment/adapters/web.js.map +1 -0
  153. package/dist/sentiment/index.d.ts +16 -0
  154. package/dist/sentiment/index.js +44 -0
  155. package/dist/sentiment/index.js.map +1 -0
  156. package/dist/sentiment/keywords.d.ts +2 -0
  157. package/dist/sentiment/keywords.js +9 -0
  158. package/dist/sentiment/keywords.js.map +1 -0
  159. package/dist/sentiment/pipeline.d.ts +9 -0
  160. package/dist/sentiment/pipeline.js +57 -0
  161. package/dist/sentiment/pipeline.js.map +1 -0
  162. package/dist/sentiment/scorer.d.ts +9 -0
  163. package/dist/sentiment/scorer.js +64 -0
  164. package/dist/sentiment/scorer.js.map +1 -0
  165. package/dist/sentiment/store.d.ts +24 -0
  166. package/dist/sentiment/store.js +182 -0
  167. package/dist/sentiment/store.js.map +1 -0
  168. package/dist/sentiment/trends.d.ts +13 -0
  169. package/dist/sentiment/trends.js +73 -0
  170. package/dist/sentiment/trends.js.map +1 -0
  171. package/dist/sentiment/types.d.ts +66 -0
  172. package/dist/sentiment/types.js +54 -0
  173. package/dist/sentiment/types.js.map +1 -0
  174. package/dist/system-prompt.js +29 -13
  175. package/dist/system-prompt.js.map +1 -1
  176. package/dist/tool-kit.d.ts +4 -4
  177. package/dist/tools/fundamentals/company-overview.d.ts +4 -2
  178. package/dist/tools/fundamentals/company-overview.js +27 -27
  179. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  180. package/dist/tools/fundamentals/comps.d.ts +1 -1
  181. package/dist/tools/fundamentals/comps.js +45 -45
  182. package/dist/tools/fundamentals/comps.js.map +1 -1
  183. package/dist/tools/fundamentals/dcf.d.ts +1 -1
  184. package/dist/tools/fundamentals/dcf.js +82 -82
  185. package/dist/tools/fundamentals/dcf.js.map +1 -1
  186. package/dist/tools/fundamentals/earnings.d.ts +4 -2
  187. package/dist/tools/fundamentals/earnings.js +25 -25
  188. package/dist/tools/fundamentals/earnings.js.map +1 -1
  189. package/dist/tools/fundamentals/financials.d.ts +4 -2
  190. package/dist/tools/fundamentals/financials.js +23 -23
  191. package/dist/tools/fundamentals/financials.js.map +1 -1
  192. package/dist/tools/fundamentals/sec-filings.d.ts +1 -1
  193. package/dist/tools/index.d.ts +28 -1
  194. package/dist/tools/index.js +35 -2
  195. package/dist/tools/index.js.map +1 -1
  196. package/dist/tools/interaction/ask-user.d.ts +1 -1
  197. package/dist/tools/interaction/ask-user.js +28 -64
  198. package/dist/tools/interaction/ask-user.js.map +1 -1
  199. package/dist/tools/interaction/twitter-login.d.ts +1 -1
  200. package/dist/tools/macro/fear-greed.d.ts +1 -1
  201. package/dist/tools/macro/fred-data.d.ts +4 -2
  202. package/dist/tools/macro/fred-data.js +26 -26
  203. package/dist/tools/macro/fred-data.js.map +1 -1
  204. package/dist/tools/market/crypto-history.d.ts +1 -1
  205. package/dist/tools/market/crypto-price.d.ts +1 -1
  206. package/dist/tools/market/search-ticker.d.ts +1 -1
  207. package/dist/tools/market/stock-history.d.ts +1 -1
  208. package/dist/tools/market/stock-quote.d.ts +1 -1
  209. package/dist/tools/options/option-chain.d.ts +1 -1
  210. package/dist/tools/options/option-chain.js +4 -1
  211. package/dist/tools/options/option-chain.js.map +1 -1
  212. package/dist/tools/portfolio/correlation.d.ts +1 -1
  213. package/dist/tools/portfolio/predictions.d.ts +1 -1
  214. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  215. package/dist/tools/portfolio/tracker.d.ts +1 -1
  216. package/dist/tools/portfolio/watchlist.d.ts +1 -1
  217. package/dist/tools/sentiment/reddit-sentiment.d.ts +4 -2
  218. package/dist/tools/sentiment/reddit-sentiment.js +107 -22
  219. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  220. package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
  221. package/dist/tools/sentiment/sentiment-summary.js +230 -0
  222. package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
  223. package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
  224. package/dist/tools/sentiment/sentiment-trend.js +39 -0
  225. package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
  226. package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
  227. package/dist/tools/sentiment/twitter-sentiment.js +17 -0
  228. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  229. package/dist/tools/sentiment/web-search.d.ts +11 -0
  230. package/dist/tools/sentiment/web-search.js +115 -0
  231. package/dist/tools/sentiment/web-search.js.map +1 -0
  232. package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
  233. package/dist/tools/sentiment/web-sentiment.js +66 -0
  234. package/dist/tools/sentiment/web-sentiment.js.map +1 -0
  235. package/dist/tools/technical/backtest.d.ts +1 -1
  236. package/dist/tools/technical/indicators.d.ts +1 -1
  237. package/dist/tools/technical/indicators.js +7 -1
  238. package/dist/tools/technical/indicators.js.map +1 -1
  239. package/dist/types/index.d.ts +1 -1
  240. package/dist/types/sentiment.d.ts +21 -0
  241. package/dist/workflows/options-screener.js +7 -2
  242. package/dist/workflows/options-screener.js.map +1 -1
  243. package/dist/workflows/portfolio-builder.js +3 -3
  244. package/dist/workflows/portfolio-builder.js.map +1 -1
  245. package/gui/server/background-quotes.ts +31 -0
  246. package/gui/server/chat-event-adapter.ts +142 -0
  247. package/gui/server/invoke-tool.ts +89 -0
  248. package/gui/server/live-chat-event-adapter.ts +181 -0
  249. package/gui/server/model-setup.ts +100 -0
  250. package/gui/server/package.json +5 -0
  251. package/gui/server/projector.ts +212 -0
  252. package/gui/server/server.ts +592 -0
  253. package/gui/server/session-actions.ts +31 -0
  254. package/gui/server/tool-metadata.ts +88 -0
  255. package/gui/server/websocket.ts +128 -0
  256. package/gui/server/writer-lock.ts +118 -0
  257. package/gui/shared/chat-events.ts +118 -0
  258. package/gui/shared/event-reducer.ts +186 -0
  259. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +1 -0
  260. package/gui/web/dist/assets/index-DBrWq43L.css +1 -0
  261. package/gui/web/dist/assets/index-RflHaj0y.js +67 -0
  262. package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
  263. package/gui/web/dist/index.html +17 -0
  264. package/package.json +62 -20
  265. package/src/analysts/contracts.ts +189 -0
  266. package/src/analysts/orchestrator.ts +300 -0
  267. package/src/cli.ts +205 -0
  268. package/src/config.ts +161 -0
  269. package/src/index.ts +5 -0
  270. package/src/infra/browser.ts +111 -0
  271. package/src/infra/cache.ts +103 -0
  272. package/src/infra/http-client.ts +68 -0
  273. package/src/infra/index.ts +18 -0
  274. package/src/infra/native-dependencies.ts +12 -0
  275. package/src/infra/node-version.ts +24 -0
  276. package/src/infra/open-url.ts +28 -0
  277. package/src/infra/opencandle-paths.ts +64 -0
  278. package/src/infra/rate-limiter.ts +64 -0
  279. package/src/memory/index.ts +10 -0
  280. package/src/memory/manager.ts +159 -0
  281. package/src/memory/preference-extractor.ts +106 -0
  282. package/src/memory/retrieval.ts +70 -0
  283. package/src/memory/sqlite.ts +172 -0
  284. package/src/memory/storage.ts +204 -0
  285. package/src/memory/tool-defaults.ts +87 -0
  286. package/src/memory/types.ts +67 -0
  287. package/src/onboarding/connect.ts +184 -0
  288. package/src/onboarding/credential-interceptor.ts +134 -0
  289. package/src/onboarding/degradation-accumulator.ts +79 -0
  290. package/src/onboarding/prompt-user.ts +85 -0
  291. package/src/onboarding/providers.ts +315 -0
  292. package/src/onboarding/state.ts +218 -0
  293. package/src/onboarding/tool-helpers.ts +111 -0
  294. package/src/onboarding/tool-tags.ts +201 -0
  295. package/src/onboarding/validation.ts +158 -0
  296. package/src/pi/opencandle-extension.ts +724 -0
  297. package/src/pi/session-storage.ts +5 -0
  298. package/src/pi/session.ts +81 -0
  299. package/src/pi/setup.ts +371 -0
  300. package/src/pi/tool-adapter.ts +36 -0
  301. package/src/prompts/context-builder.ts +204 -0
  302. package/src/prompts/disclaimer.ts +9 -0
  303. package/src/prompts/sections.ts +46 -0
  304. package/src/prompts/workflow-prompts.ts +279 -0
  305. package/src/providers/alpha-vantage.ts +292 -0
  306. package/src/providers/coingecko.ts +96 -0
  307. package/src/providers/exa-search.ts +373 -0
  308. package/src/providers/fear-greed.ts +45 -0
  309. package/src/providers/finnhub.ts +124 -0
  310. package/src/providers/fred.ts +83 -0
  311. package/src/providers/index.ts +9 -0
  312. package/src/providers/provider-credential-error.ts +23 -0
  313. package/src/providers/reddit.ts +151 -0
  314. package/src/providers/sec-edgar.ts +96 -0
  315. package/src/providers/twitter.ts +173 -0
  316. package/src/providers/web-search.ts +293 -0
  317. package/src/providers/with-fallback.ts +41 -0
  318. package/src/providers/wrap-provider.ts +64 -0
  319. package/src/providers/yahoo-finance.ts +367 -0
  320. package/src/routing/classify-intent.ts +194 -0
  321. package/src/routing/defaults.ts +29 -0
  322. package/src/routing/entity-extractor.ts +140 -0
  323. package/src/routing/index.ts +26 -0
  324. package/src/routing/router-llm-client.ts +51 -0
  325. package/src/routing/router-prompt.ts +159 -0
  326. package/src/routing/router-types.ts +66 -0
  327. package/src/routing/router.ts +213 -0
  328. package/src/routing/slot-resolver.ts +152 -0
  329. package/src/routing/types.ts +63 -0
  330. package/src/runtime/evidence.ts +77 -0
  331. package/src/runtime/index.ts +55 -0
  332. package/src/runtime/prompt-step.ts +75 -0
  333. package/src/runtime/provider-ids.ts +15 -0
  334. package/src/runtime/provider-tracker.ts +40 -0
  335. package/src/runtime/run-context.ts +22 -0
  336. package/src/runtime/session-coordinator.ts +406 -0
  337. package/src/runtime/tool-defaults-wrapper.ts +35 -0
  338. package/src/runtime/validation.ts +214 -0
  339. package/src/runtime/workflow-events.ts +75 -0
  340. package/src/runtime/workflow-runner.ts +188 -0
  341. package/src/runtime/workflow-types.ts +102 -0
  342. package/src/sentiment/adapters/finnhub.ts +44 -0
  343. package/src/sentiment/adapters/reddit.ts +65 -0
  344. package/src/sentiment/adapters/twitter.ts +36 -0
  345. package/src/sentiment/adapters/web.ts +44 -0
  346. package/src/sentiment/index.ts +58 -0
  347. package/src/sentiment/keywords.ts +9 -0
  348. package/src/sentiment/pipeline.ts +68 -0
  349. package/src/sentiment/scorer.ts +78 -0
  350. package/src/sentiment/store.ts +260 -0
  351. package/src/sentiment/trends.ts +90 -0
  352. package/src/sentiment/types.ts +108 -0
  353. package/src/system-prompt.ts +115 -0
  354. package/src/tool-kit.ts +68 -0
  355. package/src/tools/AGENTS.md +36 -0
  356. package/src/tools/fundamentals/company-overview.ts +54 -0
  357. package/src/tools/fundamentals/comps.ts +156 -0
  358. package/src/tools/fundamentals/dcf.ts +267 -0
  359. package/src/tools/fundamentals/earnings.ts +47 -0
  360. package/src/tools/fundamentals/financials.ts +54 -0
  361. package/src/tools/fundamentals/sec-filings.ts +61 -0
  362. package/src/tools/index.ts +88 -0
  363. package/src/tools/interaction/ask-user.ts +81 -0
  364. package/src/tools/interaction/twitter-login.ts +93 -0
  365. package/src/tools/macro/fear-greed.ts +41 -0
  366. package/src/tools/macro/fred-data.ts +54 -0
  367. package/src/tools/market/crypto-history.ts +51 -0
  368. package/src/tools/market/crypto-price.ts +53 -0
  369. package/src/tools/market/search-ticker.ts +53 -0
  370. package/src/tools/market/stock-history.ts +79 -0
  371. package/src/tools/market/stock-quote.ts +64 -0
  372. package/src/tools/options/greeks.ts +82 -0
  373. package/src/tools/options/option-chain.ts +91 -0
  374. package/src/tools/portfolio/correlation.ts +162 -0
  375. package/src/tools/portfolio/predictions.ts +253 -0
  376. package/src/tools/portfolio/risk-analysis.ts +134 -0
  377. package/src/tools/portfolio/tracker.ts +147 -0
  378. package/src/tools/portfolio/watchlist.ts +153 -0
  379. package/src/tools/sentiment/reddit-sentiment.ts +164 -0
  380. package/src/tools/sentiment/sentiment-summary.ts +256 -0
  381. package/src/tools/sentiment/sentiment-trend.ts +58 -0
  382. package/src/tools/sentiment/twitter-sentiment.ts +96 -0
  383. package/src/tools/sentiment/web-search.ts +150 -0
  384. package/src/tools/sentiment/web-sentiment.ts +76 -0
  385. package/src/tools/technical/backtest.ts +246 -0
  386. package/src/tools/technical/indicators.ts +258 -0
  387. package/src/types/fundamentals.ts +46 -0
  388. package/src/types/index.ts +20 -0
  389. package/src/types/macro.ts +27 -0
  390. package/src/types/market.ts +43 -0
  391. package/src/types/options.ts +35 -0
  392. package/src/types/portfolio.ts +41 -0
  393. package/src/types/sentiment.ts +70 -0
  394. package/src/workflows/compare-assets.ts +39 -0
  395. package/src/workflows/index.ts +4 -0
  396. package/src/workflows/options-screener.ts +49 -0
  397. package/src/workflows/portfolio-builder.ts +52 -0
  398. package/src/workflows/types.ts +4 -0
  399. package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
  400. package/dist/tools/sentiment/news-sentiment.js +0 -55
  401. package/dist/tools/sentiment/news-sentiment.js.map +0 -1
@@ -0,0 +1,54 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getFinancials } from "../../providers/alpha-vantage.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import { getConfig } from "../../config.js";
6
+ import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
7
+ import type { FinancialStatement } from "../../types/fundamentals.js";
8
+
9
+ const params = Type.Object({
10
+ symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT)" }),
11
+ });
12
+
13
+ export const financialsTool: AgentTool<typeof params, FinancialStatement[] | { credentialRequired: unknown }> = {
14
+ name: "get_financials",
15
+ label: "Financial Statements",
16
+ description:
17
+ "Get annual income statement data: revenue, gross profit, operating income, net income, and EPS. Requires Alpha Vantage.",
18
+ parameters: params,
19
+ async execute(toolCallId, args) {
20
+ return withCredentialCheck("alpha_vantage", async () => {
21
+ const apiKey = getConfig().alphaVantageApiKey!;
22
+ const result = await wrapProvider("alphavantage", () =>
23
+ getFinancials(args.symbol.toUpperCase(), apiKey),
24
+ );
25
+ if (result.status === "unavailable") {
26
+ return {
27
+ content: [{ type: "text", text: `⚠ Financial statements unavailable for ${args.symbol.toUpperCase()} (${result.reason}). Analysis will proceed without financials.` }],
28
+ details: [],
29
+ };
30
+ }
31
+ const statements = result.data;
32
+ if (statements.length === 0) {
33
+ return {
34
+ content: [{ type: "text", text: `No financial data found for ${args.symbol}` }],
35
+ details: [],
36
+ };
37
+ }
38
+
39
+ const header = `${args.symbol.toUpperCase()} — Annual Income Statement (${statements.length} years)`;
40
+ const rows = statements.map((s) =>
41
+ `${s.fiscalDate} | Rev: $${fmt(s.revenue)} | GP: $${fmt(s.grossProfit)} | OpInc: $${fmt(s.operatingIncome)} | Net: $${fmt(s.netIncome)}`,
42
+ );
43
+
44
+ const text = [header, ...rows].join("\n");
45
+ return { content: [{ type: "text", text }], details: statements };
46
+ });
47
+ },
48
+ };
49
+
50
+ function fmt(n: number): string {
51
+ if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
52
+ if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
53
+ return n.toLocaleString();
54
+ }
@@ -0,0 +1,61 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { searchFilings } from "../../providers/sec-edgar.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+
6
+ const params = Type.Object({
7
+ symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT, TSLA)" }),
8
+ form_types: Type.Optional(
9
+ Type.Array(Type.String(), {
10
+ description: "Filing types to search: 10-K (annual), 10-Q (quarterly), 8-K (material events). Default: all three.",
11
+ }),
12
+ ),
13
+ limit: Type.Optional(
14
+ Type.Number({ description: "Maximum number of filings to return (default: 10)" }),
15
+ ),
16
+ });
17
+
18
+ export const secFilingsTool: AgentTool<typeof params> = {
19
+ name: "get_sec_filings",
20
+ label: "SEC Filings",
21
+ description:
22
+ "Search SEC EDGAR for company filings (10-K annual reports, 10-Q quarterly reports, 8-K material events). Returns filing dates, types, and direct links to the documents. Free API, no key required.",
23
+ parameters: params,
24
+ async execute(toolCallId, args) {
25
+ const symbol = args.symbol.toUpperCase();
26
+ const formTypes = args.form_types ?? ["10-K", "10-Q", "8-K"];
27
+ const limit = args.limit ?? 10;
28
+
29
+ const result = await wrapProvider("sec-edgar", () => searchFilings(symbol, formTypes, limit));
30
+ if (result.status === "unavailable") {
31
+ return {
32
+ content: [{ type: "text", text: `⚠ SEC filings unavailable for ${symbol} (${result.reason}).` }],
33
+ details: null,
34
+ };
35
+ }
36
+ const filings = result.data;
37
+
38
+ if (filings.length === 0) {
39
+ return {
40
+ content: [{ type: "text", text: `No SEC filings found for ${symbol}. Verify the ticker is a US-listed company.` }],
41
+ details: null,
42
+ };
43
+ }
44
+
45
+ const lines = [
46
+ `**${symbol} SEC Filings** (${filings.length} found)`,
47
+ ``,
48
+ ...filings.map((f) =>
49
+ ` ${f.formType.padEnd(6)} | Filed: ${f.filedDate} | Period: ${f.periodOfReport || "N/A"} | ${f.entityName}`
50
+ ),
51
+ ``,
52
+ `Links:`,
53
+ ...filings.map((f) => ` ${f.formType} (${f.filedDate}): ${f.url}`),
54
+ ];
55
+
56
+ return {
57
+ content: [{ type: "text", text: lines.join("\n") }],
58
+ details: { symbol, filings },
59
+ };
60
+ },
61
+ };
@@ -0,0 +1,88 @@
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
+ import { stockQuoteTool } from "./market/stock-quote.js";
3
+ import { stockHistoryTool } from "./market/stock-history.js";
4
+ import { cryptoPriceTool } from "./market/crypto-price.js";
5
+ import { cryptoHistoryTool } from "./market/crypto-history.js";
6
+ import { searchTickerTool } from "./market/search-ticker.js";
7
+ import { companyOverviewTool } from "./fundamentals/company-overview.js";
8
+ import { financialsTool } from "./fundamentals/financials.js";
9
+ import { earningsTool } from "./fundamentals/earnings.js";
10
+ import { fredDataTool } from "./macro/fred-data.js";
11
+ import { fearGreedTool } from "./macro/fear-greed.js";
12
+ import { redditSentimentTool } from "./sentiment/reddit-sentiment.js";
13
+ import { twitterSentimentTool } from "./sentiment/twitter-sentiment.js";
14
+ import { technicalIndicatorsTool } from "./technical/indicators.js";
15
+ import { portfolioTrackerTool } from "./portfolio/tracker.js";
16
+ import { riskAnalysisTool } from "./portfolio/risk-analysis.js";
17
+ import { watchlistTool } from "./portfolio/watchlist.js";
18
+ import { correlationTool } from "./portfolio/correlation.js";
19
+ import { optionChainTool } from "./options/option-chain.js";
20
+ import { dcfTool } from "./fundamentals/dcf.js";
21
+ import { compsTool } from "./fundamentals/comps.js";
22
+ import { secFilingsTool } from "./fundamentals/sec-filings.js";
23
+ import { backtestTool } from "./technical/backtest.js";
24
+ import { predictionsTool } from "./portfolio/predictions.js";
25
+ import { webSearchTool } from "./sentiment/web-search.js";
26
+ import { webSentimentTool } from "./sentiment/web-sentiment.js";
27
+ import { sentimentTrendTool } from "./sentiment/sentiment-trend.js";
28
+ import { sentimentSummaryTool } from "./sentiment/sentiment-summary.js";
29
+
30
+ export { stockQuoteTool } from "./market/stock-quote.js";
31
+ export { stockHistoryTool } from "./market/stock-history.js";
32
+ export { cryptoPriceTool } from "./market/crypto-price.js";
33
+ export { cryptoHistoryTool } from "./market/crypto-history.js";
34
+ export { searchTickerTool } from "./market/search-ticker.js";
35
+ export { companyOverviewTool } from "./fundamentals/company-overview.js";
36
+ export { financialsTool } from "./fundamentals/financials.js";
37
+ export { earningsTool } from "./fundamentals/earnings.js";
38
+ export { dcfTool } from "./fundamentals/dcf.js";
39
+ export { compsTool } from "./fundamentals/comps.js";
40
+ export { secFilingsTool } from "./fundamentals/sec-filings.js";
41
+ export { fredDataTool } from "./macro/fred-data.js";
42
+ export { fearGreedTool } from "./macro/fear-greed.js";
43
+ export { redditSentimentTool } from "./sentiment/reddit-sentiment.js";
44
+ export { twitterSentimentTool } from "./sentiment/twitter-sentiment.js";
45
+ export { webSearchTool } from "./sentiment/web-search.js";
46
+ export { webSentimentTool } from "./sentiment/web-sentiment.js";
47
+ export { sentimentTrendTool } from "./sentiment/sentiment-trend.js";
48
+ export { sentimentSummaryTool } from "./sentiment/sentiment-summary.js";
49
+ export { technicalIndicatorsTool } from "./technical/indicators.js";
50
+ export { backtestTool } from "./technical/backtest.js";
51
+ export { portfolioTrackerTool } from "./portfolio/tracker.js";
52
+ export { riskAnalysisTool } from "./portfolio/risk-analysis.js";
53
+ export { watchlistTool } from "./portfolio/watchlist.js";
54
+ export { correlationTool } from "./portfolio/correlation.js";
55
+ export { predictionsTool } from "./portfolio/predictions.js";
56
+ export { optionChainTool } from "./options/option-chain.js";
57
+
58
+ export function getAllTools(): AgentTool<any>[] {
59
+ return [
60
+ searchTickerTool,
61
+ stockQuoteTool,
62
+ stockHistoryTool,
63
+ cryptoPriceTool,
64
+ cryptoHistoryTool,
65
+ companyOverviewTool,
66
+ financialsTool,
67
+ earningsTool,
68
+ dcfTool,
69
+ compsTool,
70
+ secFilingsTool,
71
+ fredDataTool,
72
+ fearGreedTool,
73
+ redditSentimentTool,
74
+ twitterSentimentTool,
75
+ technicalIndicatorsTool,
76
+ backtestTool,
77
+ portfolioTrackerTool,
78
+ riskAnalysisTool,
79
+ watchlistTool,
80
+ correlationTool,
81
+ predictionsTool,
82
+ optionChainTool,
83
+ webSearchTool,
84
+ webSentimentTool,
85
+ sentimentTrendTool,
86
+ sentimentSummaryTool,
87
+ ];
88
+ }
@@ -0,0 +1,81 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ import { promptUser } from "../../onboarding/prompt-user.js";
4
+ import type { AskUserHandler } from "../../types/index.js";
5
+
6
+ interface AskUserDetails {
7
+ question: string;
8
+ questionType: string;
9
+ answer: string | null;
10
+ cancelled: boolean;
11
+ }
12
+
13
+ const AskUserParams = Type.Object({
14
+ question: Type.String({ description: "The question to ask the user" }),
15
+ question_type: Type.Union(
16
+ [Type.Literal("select"), Type.Literal("text"), Type.Literal("confirm")],
17
+ { description: "Type of answer expected: select (pick from options), text (free input), or confirm (yes/no)" },
18
+ ),
19
+ options: Type.Optional(
20
+ Type.Array(Type.String(), { description: "Choices for select-type questions" }),
21
+ ),
22
+ placeholder: Type.Optional(
23
+ Type.String({ description: "Hint text for text input" }),
24
+ ),
25
+ reason: Type.Optional(
26
+ Type.String({ description: "Brief context for why this clarification is needed" }),
27
+ ),
28
+ });
29
+
30
+ export function registerAskUserTool(pi: ExtensionAPI, askUserHandler?: AskUserHandler): void {
31
+ pi.registerTool({
32
+ name: "ask_user",
33
+ label: "Ask User",
34
+ description:
35
+ "Ask the user a clarification question when their request is ambiguous or missing key details. Use select for multiple-choice, text for free input, or confirm for yes/no.",
36
+ promptSnippet:
37
+ "ask_user: Ask the user a clarification question when their request is ambiguous or missing key details",
38
+ parameters: AskUserParams,
39
+
40
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
41
+ const { question, question_type: questionType } = params;
42
+
43
+ // Preserve the pre-refactor error message for the specific misuse case
44
+ // where the model asks a select question with no options — this is a
45
+ // model/caller bug, not a cancellation.
46
+ if (questionType === "select" && (!params.options || params.options.length === 0)) {
47
+ return {
48
+ content: [{ type: "text", text: "Error: No options provided for select question. Provide options or use text type instead." }],
49
+ details: { question, questionType, answer: null, cancelled: true } as AskUserDetails,
50
+ };
51
+ }
52
+
53
+ const result = await promptUser(
54
+ ctx!,
55
+ {
56
+ question,
57
+ questionType,
58
+ options: params.options,
59
+ placeholder: params.placeholder,
60
+ reason: params.reason,
61
+ },
62
+ askUserHandler,
63
+ );
64
+
65
+ if (result.cancelled) {
66
+ const text = !askUserHandler && !ctx?.hasUI
67
+ ? "UI not available (non-interactive mode). Proceed with your best judgment and clearly disclose your assumption."
68
+ : "User cancelled the selection. Proceed with your best judgment and disclose your assumption.";
69
+ return {
70
+ content: [{ type: "text", text }],
71
+ details: { question, questionType, answer: null, cancelled: true } as AskUserDetails,
72
+ };
73
+ }
74
+
75
+ return {
76
+ content: [{ type: "text", text: `User answered: ${result.answer}` }],
77
+ details: { question, questionType, answer: result.answer, cancelled: false } as AskUserDetails,
78
+ };
79
+ },
80
+ });
81
+ }
@@ -0,0 +1,93 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import type { BrowserContext } from "playwright-core";
3
+ import { Type } from "@sinclair/typebox";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+
6
+ interface TwitterLoginResult {
7
+ success: boolean;
8
+ message: string;
9
+ }
10
+
11
+ export async function runTwitterLogin(notify: (msg: string) => void): Promise<TwitterLoginResult> {
12
+ const { getBrowserProfileDir } = await import("../../infra/opencandle-paths.js");
13
+ const { Camoufox } = await import("camoufox-js");
14
+
15
+ const profileDir = getBrowserProfileDir();
16
+ mkdirSync(profileDir, { recursive: true });
17
+
18
+ notify("Launching browser for Twitter login...");
19
+
20
+ const context = (await Camoufox({
21
+ headless: false,
22
+ user_data_dir: profileDir,
23
+ geoip: true,
24
+ humanize: true,
25
+ os: "macos",
26
+ block_webrtc: true,
27
+ window: [1440, 900],
28
+ })) as unknown as BrowserContext;
29
+
30
+ try {
31
+ const pages = context.pages();
32
+ const page = pages.length > 0 ? pages[0] : await context.newPage();
33
+
34
+ await page.goto("https://x.com/login", {
35
+ waitUntil: "domcontentloaded",
36
+ timeout: 30_000,
37
+ });
38
+
39
+ notify("Log in via the browser window. Waiting for auth cookies...");
40
+
41
+ const deadline = Date.now() + 300_000;
42
+ while (Date.now() < deadline) {
43
+ try {
44
+ const cookies = await context.cookies("https://x.com");
45
+ const authNames = ["auth_token", "ct0", "twid"];
46
+ const found = cookies.filter((c) => authNames.includes(c.name));
47
+ if (found.length >= 2) {
48
+ await page.waitForTimeout(3000);
49
+ await context.close();
50
+ return { success: true, message: `Twitter login successful! (${found.map((c) => c.name).join(", ")})` };
51
+ }
52
+ } catch {
53
+ return { success: false, message: "Twitter login cancelled (browser closed)." };
54
+ }
55
+ await page.waitForTimeout(2000);
56
+ }
57
+
58
+ await context.close();
59
+ return { success: false, message: "Twitter login timed out after 5 minutes." };
60
+ } catch (error) {
61
+ try { await context.close(); } catch { /* already closed */ }
62
+ const msg = error instanceof Error ? error.message : String(error);
63
+ return { success: false, message: `Twitter login failed: ${msg}` };
64
+ }
65
+ }
66
+
67
+ export function registerTwitterLoginTool(pi: ExtensionAPI): void {
68
+ pi.registerTool({
69
+ name: "trigger_twitter_login",
70
+ label: "Twitter Login",
71
+ description:
72
+ "Open a browser window for the user to log in to Twitter/X. Call this when get_twitter_sentiment reports that login is needed and the user has confirmed they want to proceed. Returns success/failure. After success, retry get_twitter_sentiment.",
73
+ promptSnippet:
74
+ "trigger_twitter_login: Opens a browser for Twitter/X login. Use when Twitter sentiment is unavailable due to missing session.",
75
+ parameters: Type.Object({}),
76
+
77
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
78
+ if (!ctx?.hasUI) {
79
+ return {
80
+ content: [{ type: "text", text: "Cannot open browser in non-interactive mode. Twitter login requires a terminal session with UI." }],
81
+ details: { success: false },
82
+ };
83
+ }
84
+
85
+ const result = await runTwitterLogin((msg) => ctx.ui.notify(msg, "info"));
86
+
87
+ return {
88
+ content: [{ type: "text", text: result.message }],
89
+ details: { success: result.success },
90
+ };
91
+ },
92
+ });
93
+ }
@@ -0,0 +1,41 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getFearGreedIndex } from "../../providers/fear-greed.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import type { FearGreedData } from "../../types/sentiment.js";
6
+
7
+ const params = Type.Object({});
8
+
9
+ export const fearGreedTool: AgentTool<typeof params, FearGreedData> = {
10
+ name: "get_fear_greed",
11
+ label: "Fear & Greed Index",
12
+ description:
13
+ "Get the Crypto Fear & Greed Index (alternative.me) — a sentiment indicator from 0 (Extreme Fear) to 100 (Extreme Greed). Includes current value and previous close.",
14
+ parameters: params,
15
+ async execute(toolCallId, _args) {
16
+ const result = await wrapProvider("feargreed", () => getFearGreedIndex());
17
+ if (result.status === "unavailable") {
18
+ return {
19
+ content: [{ type: "text", text: `⚠ Fear & Greed Index unavailable (${result.reason}).` }],
20
+ details: null as any,
21
+ };
22
+ }
23
+ const fg = result.data;
24
+
25
+ const gauge = buildGauge(fg.value);
26
+ const text = [
27
+ `**Fear & Greed Index: ${fg.value} — ${fg.label}**`,
28
+ gauge,
29
+ `Previous Close: ${fg.previousClose}${fg.weekAgo != null ? ` | Week Ago: ${fg.weekAgo}` : ""}${fg.monthAgo != null ? ` | Month Ago: ${fg.monthAgo}` : ""}`,
30
+ ].join("\n");
31
+
32
+ return { content: [{ type: "text", text }], details: fg };
33
+ },
34
+ };
35
+
36
+ function buildGauge(value: number): string {
37
+ const width = 20;
38
+ const pos = Math.round((value / 100) * width);
39
+ const bar = "█".repeat(pos) + "░".repeat(width - pos);
40
+ return `[${bar}] ${value}/100`;
41
+ }
@@ -0,0 +1,54 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getSeries } from "../../providers/fred.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import { getConfig } from "../../config.js";
6
+ import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
7
+ import { FRED_SERIES } from "../../types/macro.js";
8
+ import type { FredSeries } from "../../types/macro.js";
9
+
10
+ const params = Type.Object({
11
+ series_id: Type.String({
12
+ description: `FRED series ID. Common ones: ${Object.entries(FRED_SERIES).map(([k, v]) => `${v} (${k})`).join(", ")}`,
13
+ }),
14
+ limit: Type.Optional(
15
+ Type.Number({ description: "Number of observations to return. Default: 30" }),
16
+ ),
17
+ });
18
+
19
+ export const fredDataTool: AgentTool<typeof params, FredSeries | { credentialRequired: unknown }> = {
20
+ name: "get_economic_data",
21
+ label: "FRED Economic Data",
22
+ description:
23
+ "Get economic data from FRED (Federal Reserve Economic Data): interest rates, CPI, GDP, unemployment, yield curve, and more. Requires FRED.",
24
+ parameters: params,
25
+ async execute(toolCallId, args) {
26
+ return withCredentialCheck("fred", async () => {
27
+ const apiKey = getConfig().fredApiKey!;
28
+ const limit = args.limit ?? 30;
29
+ const result = await wrapProvider("fred", () => getSeries(args.series_id.toUpperCase(), apiKey, limit));
30
+ if (result.status === "unavailable") {
31
+ return {
32
+ content: [{ type: "text", text: `⚠ FRED data unavailable for ${args.series_id.toUpperCase()} (${result.reason}).` }],
33
+ details: null as any,
34
+ };
35
+ }
36
+ const series = result.data;
37
+
38
+ const latest = series.observations[series.observations.length - 1];
39
+ const header = `**${series.title}** (${series.id})`;
40
+ const meta = `Units: ${series.units} | Frequency: ${series.frequency} | Last updated: ${series.lastUpdated}`;
41
+ const current = latest ? `Latest: ${latest.value} (${latest.date})` : "No data";
42
+
43
+ // Show last 10 observations
44
+ const recent = series.observations.slice(-10);
45
+ const table = recent.map((o) => `${o.date}: ${o.value}`).join("\n");
46
+
47
+ const text = [header, meta, current, "", "Recent observations:", table].join("\n");
48
+ const prefix = result.stale
49
+ ? `⚠ Using cached FRED data from ${result.timestamp} (FRED unavailable)\n`
50
+ : "";
51
+ return { content: [{ type: "text", text: prefix + text }], details: series };
52
+ });
53
+ },
54
+ };
@@ -0,0 +1,51 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getCryptoHistory } from "../../providers/coingecko.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import type { OHLCV } from "../../types/market.js";
6
+
7
+ const params = Type.Object({
8
+ id: Type.String({
9
+ description: "CoinGecko coin ID (e.g. bitcoin, ethereum, solana)",
10
+ }),
11
+ days: Type.Optional(
12
+ Type.Number({
13
+ description: "Number of days of history: 1, 7, 14, 30, 90, 180, 365, max. Default: 180",
14
+ }),
15
+ ),
16
+ });
17
+
18
+ export const cryptoHistoryTool: AgentTool<typeof params, OHLCV[]> = {
19
+ name: "get_crypto_history",
20
+ label: "Crypto History",
21
+ description: "Get historical OHLC data for a cryptocurrency",
22
+ parameters: params,
23
+ async execute(toolCallId, args) {
24
+ const id = args.id.toLowerCase();
25
+ const days = args.days ?? 180;
26
+ const result = await wrapProvider("coingecko", () => getCryptoHistory(id, days));
27
+ if (result.status === "unavailable") {
28
+ return {
29
+ content: [{ type: "text", text: `⚠ Crypto history unavailable for ${id} (${result.reason}).` }],
30
+ details: [],
31
+ };
32
+ }
33
+ const bars = result.data;
34
+
35
+ const summary = [
36
+ `${id} — ${bars.length} bars (${days} days)`,
37
+ `Period: ${bars[0]?.date} to ${bars[bars.length - 1]?.date}`,
38
+ ];
39
+
40
+ const recent = bars.slice(-10);
41
+ const table = recent
42
+ .map(
43
+ (b) =>
44
+ `${b.date} | O:${b.open.toFixed(2)} H:${b.high.toFixed(2)} L:${b.low.toFixed(2)} C:${b.close.toFixed(2)}`,
45
+ )
46
+ .join("\n");
47
+
48
+ const text = [...summary, "", "Recent bars:", table].join("\n");
49
+ return { content: [{ type: "text", text }], details: bars };
50
+ },
51
+ };
@@ -0,0 +1,53 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getCryptoPrice } from "../../providers/coingecko.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import type { CryptoPrice } from "../../types/market.js";
6
+
7
+ const params = Type.Object({
8
+ id: Type.String({
9
+ description:
10
+ "CoinGecko coin ID (e.g. bitcoin, ethereum, solana, dogecoin). Use lowercase.",
11
+ }),
12
+ });
13
+
14
+ export const cryptoPriceTool: AgentTool<typeof params, CryptoPrice> = {
15
+ name: "get_crypto_price",
16
+ label: "Crypto Price",
17
+ description:
18
+ "Get current crypto price, 24h change, market cap, volume, ATH, and supply data",
19
+ parameters: params,
20
+ async execute(toolCallId, args) {
21
+ const result = await wrapProvider("coingecko", () => getCryptoPrice(args.id.toLowerCase()));
22
+ if (result.status === "unavailable") {
23
+ return {
24
+ content: [{ type: "text", text: `⚠ Crypto price unavailable for ${args.id} (${result.reason}).` }],
25
+ details: null as any,
26
+ };
27
+ }
28
+ const crypto = result.data;
29
+ const sign = crypto.changePercent24h >= 0 ? "+" : "";
30
+ const text = [
31
+ `${crypto.name} (${crypto.symbol.toUpperCase()}): $${formatPrice(crypto.price)} (${sign}${crypto.changePercent24h.toFixed(2)}%)`,
32
+ `24h High: $${formatPrice(crypto.high24h)} | 24h Low: $${formatPrice(crypto.low24h)}`,
33
+ `Market Cap: $${formatLargeNumber(crypto.marketCap)} | 24h Volume: $${formatLargeNumber(crypto.volume24h)}`,
34
+ `ATH: $${formatPrice(crypto.ath)} (${crypto.athDate.split("T")[0]})`,
35
+ `Circulating: ${formatLargeNumber(crypto.circulatingSupply)} ${crypto.symbol.toUpperCase()}`,
36
+ ].join("\n");
37
+
38
+ return { content: [{ type: "text", text }], details: crypto };
39
+ },
40
+ };
41
+
42
+ function formatPrice(n: number): string {
43
+ if (n >= 1) return n.toFixed(2);
44
+ if (n >= 0.01) return n.toFixed(4);
45
+ return n.toFixed(8);
46
+ }
47
+
48
+ function formatLargeNumber(n: number): string {
49
+ if (n >= 1e12) return `${(n / 1e12).toFixed(2)}T`;
50
+ if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
51
+ if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
52
+ return n.toLocaleString();
53
+ }
@@ -0,0 +1,53 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { httpGet } from "../../infra/http-client.js";
4
+
5
+ const params = Type.Object({
6
+ query: Type.String({
7
+ description: "Search query — company name, ticker symbol, or crypto name (e.g. 'apple', 'AAPL', 'ethereum', 'bitcoin')",
8
+ }),
9
+ });
10
+
11
+ interface YahooSearchResponse {
12
+ quotes: Array<{
13
+ symbol: string;
14
+ shortname?: string;
15
+ longname?: string;
16
+ quoteType: string;
17
+ exchange: string;
18
+ score: number;
19
+ }>;
20
+ }
21
+
22
+ export const searchTickerTool: AgentTool<typeof params> = {
23
+ name: "search_ticker",
24
+ label: "Search Ticker",
25
+ description:
26
+ "Search for any ticker symbol — stocks, crypto, ETFs, indices, forex. Returns matching symbols with names and exchange info. Use this when you don't know the exact ticker for an asset.",
27
+ parameters: params,
28
+ async execute(toolCallId, args) {
29
+ const url = `https://query1.finance.yahoo.com/v1/finance/search?q=${encodeURIComponent(args.query)}&quotesCount=10&newsCount=0`;
30
+ const data = await httpGet<YahooSearchResponse>(url, {
31
+ headers: { "User-Agent": "OpenCandle/1.0" },
32
+ });
33
+
34
+ const quotes = data.quotes ?? [];
35
+ if (quotes.length === 0) {
36
+ return {
37
+ content: [{ type: "text", text: `No results found for "${args.query}"` }],
38
+ details: quotes,
39
+ };
40
+ }
41
+
42
+ const lines = [
43
+ `**Search results for "${args.query}"** — ${quotes.length} matches`,
44
+ "",
45
+ ...quotes.map(
46
+ (q) =>
47
+ ` ${q.symbol} — ${q.longname || q.shortname || "N/A"} (${q.quoteType}, ${q.exchange})`,
48
+ ),
49
+ ];
50
+
51
+ return { content: [{ type: "text", text: lines.join("\n") }], details: quotes };
52
+ },
53
+ };