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,150 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { searchWeb } from "../../providers/web-search.js";
4
+ import type { WebSearchEnvelope } from "../../types/sentiment.js";
5
+ import { hasCredential } from "../../onboarding/providers.js";
6
+ import { buildSoftDegradedTag } from "../../onboarding/tool-tags.js";
7
+
8
+ const params = Type.Object({
9
+ query: Type.String({ description: "Search query — ticker, topic, or question" }),
10
+ category: Type.Optional(
11
+ Type.Union([Type.Literal("news"), Type.Literal("general")], {
12
+ description: 'Search category. "news" for recent articles, "general" for broader web. Default: "news"',
13
+ }),
14
+ ),
15
+ freshness: Type.Optional(
16
+ Type.Union(
17
+ [Type.Literal("hours"), Type.Literal("day"), Type.Literal("week"), Type.Literal("month")],
18
+ { description: 'Time range filter. Default: "day"' },
19
+ ),
20
+ ),
21
+ limit: Type.Optional(
22
+ Type.Number({ description: "Number of results (1-20). Default: 10", minimum: 1, maximum: 20 }),
23
+ ),
24
+ provider: Type.Optional(
25
+ Type.Union([Type.Literal("exa"), Type.Literal("brave"), Type.Literal("ddg")], {
26
+ description: "Override search provider (skip cascade). Default: auto (Exa → Brave → DDG)",
27
+ }),
28
+ ),
29
+ });
30
+
31
+ function escapeMd(text: string): string {
32
+ return text.replace(/([[\]|])/g, "\\$1");
33
+ }
34
+
35
+ function safeUrl(url: string): string {
36
+ if (url.startsWith("https://") || url.startsWith("http://")) return url;
37
+ return `https://${url}`;
38
+ }
39
+
40
+ /**
41
+ * Build soft-degradation tags for search providers whose credentials are
42
+ * missing at call time. Returns an empty string when nothing is degraded, or
43
+ * a newline-terminated block of `[OPENCANDLE_SOFT_DEGRADED ...]` tags ready to
44
+ * prepend to the tool result content. The extension's `tool_result` handler
45
+ * records these into the per-turn degradation accumulator; the system prompt
46
+ * instructs the LLM to surface them in a `**Data gaps**` section.
47
+ *
48
+ * Emission rules:
49
+ * - Brave: if `hasCredential("brave") === false` AND the envelope's
50
+ * provider is not `"brave"`, the cascade fell back from Brave.
51
+ * - Exa: if `hasCredential("exa") === false` AND the envelope's provider
52
+ * is `"exa"`, the Exa provider used the keyless MCP path instead of the
53
+ * keyed API. Envelopes served by `ddg` are NOT tagged for Exa (Exa was
54
+ * tried first and failed for a reason unrelated to credentials).
55
+ */
56
+ function buildSoftDegradedPrefix(data: WebSearchEnvelope): string {
57
+ const tags: string[] = [];
58
+
59
+ if (!hasCredential("brave") && data.provider !== "brave") {
60
+ tags.push(
61
+ buildSoftDegradedTag({
62
+ provider: "brave",
63
+ fallback: data.provider === "exa" ? "exa" : "ddg",
64
+ remediation: "run /connect search to enable Brave",
65
+ }),
66
+ );
67
+ }
68
+
69
+ if (!hasCredential("exa") && data.provider === "exa") {
70
+ tags.push(
71
+ buildSoftDegradedTag({
72
+ provider: "exa",
73
+ fallback: "keyless-mcp",
74
+ remediation: "run /connect search to enable keyed Exa",
75
+ }),
76
+ );
77
+ }
78
+
79
+ return tags.length === 0 ? "" : `${tags.join("\n")}\n\n`;
80
+ }
81
+
82
+ export const webSearchTool: AgentTool<typeof params, WebSearchEnvelope> = {
83
+ name: "search_web",
84
+ label: "Web Search",
85
+ description:
86
+ "Search the web for financial news, earnings context, company events, regulatory developments, or general information. " +
87
+ "NOT for real-time prices, historical data, fundamentals, macro data, SEC filings, or social sentiment — those have dedicated tools.",
88
+ parameters: params,
89
+
90
+ async execute(toolCallId, args) {
91
+ const query = args.query?.trim();
92
+ if (!query) {
93
+ return {
94
+ content: [{ type: "text", text: "⚠ Cannot search with an empty query." }],
95
+ details: null as any,
96
+ };
97
+ }
98
+
99
+ const category = args.category ?? "news";
100
+ const freshness = args.freshness ?? "day";
101
+ const limit = Math.max(1, Math.min(args.limit ?? 10, 20));
102
+
103
+ const provider = args.provider;
104
+ const result = await searchWeb(query, { category, freshness, limit, provider });
105
+
106
+ if (result.status === "unavailable") {
107
+ return {
108
+ content: [{ type: "text", text: `⚠ Web search unavailable (${result.reason}).` }],
109
+ details: null as any,
110
+ };
111
+ }
112
+
113
+ const data = result.data;
114
+
115
+ if (data.resultCount === 0) {
116
+ const zeroPrefix = buildSoftDegradedPrefix(data);
117
+ return {
118
+ content: [
119
+ {
120
+ type: "text",
121
+ text: `${zeroPrefix}No results found for "${query}" (${category}, past ${freshness}).`,
122
+ },
123
+ ],
124
+ details: data,
125
+ };
126
+ }
127
+
128
+ const stalePrefix = result.stale
129
+ ? `⚠ Using cached data from ${result.timestamp}\n\n`
130
+ : "";
131
+
132
+ const softDegradedPrefix = buildSoftDegradedPrefix(data);
133
+
134
+ const header = `**Web Search** — ${data.resultCount} results for "${query}" (${category}, past ${freshness}, via ${data.provider})`;
135
+ const items = data.results.map((r) => {
136
+ const title = escapeMd(r.title);
137
+ const snippet = escapeMd(r.snippet);
138
+ const url = safeUrl(r.url);
139
+ const pub = r.published ? `Published: ${r.published}` : "Published: unknown";
140
+ return `• [${title}](${url}) — ${r.source}\n ${snippet}\n ${pub}`;
141
+ });
142
+
143
+ const text = `${softDegradedPrefix}${stalePrefix}${header}\n\n${items.join("\n\n")}`;
144
+
145
+ return {
146
+ content: [{ type: "text", text }],
147
+ details: data,
148
+ };
149
+ },
150
+ };
@@ -0,0 +1,76 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { searchWeb } from "../../providers/web-search.js";
4
+ import { WebAdapter } from "../../sentiment/adapters/web.js";
5
+ import { getSentimentPipeline } from "../../sentiment/index.js";
6
+
7
+ const params = Type.Object({
8
+ query: Type.String({ description: "Ticker or topic to search for web/news sentiment" }),
9
+ freshness: Type.Optional(
10
+ Type.Union([Type.Literal("day"), Type.Literal("week"), Type.Literal("month")], {
11
+ description: "Time window for results. Default: day",
12
+ }),
13
+ ),
14
+ limit: Type.Optional(
15
+ Type.Number({ description: "Max results. Default: 10, max: 20" }),
16
+ ),
17
+ });
18
+
19
+ export const webSentimentTool: AgentTool<typeof params> = {
20
+ name: "get_web_sentiment",
21
+ label: "Web/News Sentiment",
22
+ description:
23
+ "Analyze sentiment from web and news search results for a ticker or topic. Returns scored results with aggregate sentiment.",
24
+ parameters: params,
25
+ async execute(toolCallId, args) {
26
+ const freshness = args.freshness ?? "day";
27
+ const limit = Math.min(args.limit ?? 10, 20);
28
+
29
+ const providerResult = await searchWeb(args.query, { freshness, limit, category: "news" });
30
+
31
+ if (providerResult.status === "unavailable") {
32
+ return {
33
+ content: [{ type: "text", text: `⚠ Web sentiment unavailable for "${args.query}" (${providerResult.reason}).` }],
34
+ details: null as any,
35
+ };
36
+ }
37
+
38
+ const adapter = new WebAdapter();
39
+ const records = adapter.mapToRecords(providerResult.data, args.query);
40
+ const pipeline = getSentimentPipeline();
41
+ const result = await pipeline.processRecords(records, args.query);
42
+
43
+ const lines: string[] = [];
44
+ if (result.fresh.length === 0) {
45
+ lines.push(`No web results found for "${args.query}".`);
46
+ } else {
47
+ const avgScore = result.fresh.reduce((s, r) => s + r.sentiment.score, 0) / result.fresh.length;
48
+ const label = sentimentLabel(avgScore);
49
+ lines.push(`**Web sentiment for "${args.query}"** — ${result.fresh.length} results (${label}, ${avgScore.toFixed(2)})`);
50
+ lines.push("");
51
+
52
+ for (const rec of result.fresh.slice(0, limit)) {
53
+ const indicator = rec.sentiment.score > 0 ? "🟢" : rec.sentiment.score < 0 ? "🔴" : "⚪";
54
+ lines.push(`${indicator} [${rec.title}](${rec.url}) — *${rec.author}*`);
55
+ lines.push(` ${rec.text.slice(0, 150)}`);
56
+ lines.push(` Score: ${rec.sentiment.score.toFixed(2)} | Confidence: ${rec.sentiment.confidence.toFixed(2)}`);
57
+ }
58
+
59
+ if (result.trend) {
60
+ lines.push("");
61
+ const t = result.trend[0];
62
+ lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.count} records)`);
63
+ }
64
+ }
65
+
66
+ return { content: [{ type: "text", text: lines.join("\n") }], details: result };
67
+ },
68
+ };
69
+
70
+ function sentimentLabel(score: number): string {
71
+ if (score > 0.3) return "Bullish";
72
+ if (score < -0.3) return "Bearish";
73
+ if (score > 0) return "Leaning Bullish";
74
+ if (score < 0) return "Leaning Bearish";
75
+ return "Neutral";
76
+ }
@@ -0,0 +1,246 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
3
+ import { getHistory } from "../../providers/yahoo-finance.js";
4
+ import { wrapProvider } from "../../providers/wrap-provider.js";
5
+ import { computeSMA, computeRSI } from "./indicators.js";
6
+ import type { OHLCV } from "../../types/market.js";
7
+
8
+ export type Strategy = "sma_crossover" | "rsi_mean_reversion";
9
+
10
+ export interface BacktestResult {
11
+ strategy: string;
12
+ totalReturn: number;
13
+ buyAndHoldReturn: number;
14
+ trades: number;
15
+ wins: number;
16
+ winRate: number;
17
+ maxDrawdown: number;
18
+ tradeLog: Array<{ type: "buy" | "sell"; date: string; price: number; pnl?: number }>;
19
+ }
20
+
21
+ export function runBacktest(bars: OHLCV[], strategy: Strategy): BacktestResult {
22
+ const closes = bars.map((b) => b.close);
23
+ const buyAndHoldReturn = closes.length > 1
24
+ ? (closes[closes.length - 1] - closes[0]) / closes[0]
25
+ : 0;
26
+
27
+ if (strategy === "sma_crossover") {
28
+ return backtestSMACrossover(bars, closes);
29
+ }
30
+ return backtestRSIMeanReversion(bars, closes);
31
+ }
32
+
33
+ function backtestSMACrossover(bars: OHLCV[], closes: number[]): BacktestResult {
34
+ const sma20 = computeSMA(closes, 20);
35
+ const sma50 = computeSMA(closes, 50);
36
+
37
+ if (sma50.length === 0) {
38
+ return emptyResult("sma_crossover", closes);
39
+ }
40
+
41
+ // Align: SMA(20) starts at index 19, SMA(50) at index 49
42
+ // sma20[i] corresponds to closes[i + 19], sma50[i] to closes[i + 49]
43
+ const offset20 = 19;
44
+ const offset50 = 49;
45
+
46
+ let position = false;
47
+ let entryPrice = 0;
48
+ const tradeLog: BacktestResult["tradeLog"] = [];
49
+ let equity = 1.0;
50
+ let peak = 1.0;
51
+ let maxDd = 0;
52
+
53
+ for (let i = 0; i < sma50.length; i++) {
54
+ const barIdx = i + offset50;
55
+ const sma20Idx = i + (offset50 - offset20);
56
+ const s20 = sma20[sma20Idx];
57
+ const s50 = sma50[i];
58
+ const price = closes[barIdx];
59
+
60
+ if (!position && s20 > s50) {
61
+ // Buy signal
62
+ position = true;
63
+ entryPrice = price;
64
+ tradeLog.push({ type: "buy", date: bars[barIdx].date, price });
65
+ } else if (position && s20 < s50) {
66
+ // Sell signal
67
+ const pnl = (price - entryPrice) / entryPrice;
68
+ equity *= 1 + pnl;
69
+ tradeLog.push({ type: "sell", date: bars[barIdx].date, price, pnl });
70
+ position = false;
71
+ }
72
+
73
+ // Track mark-to-market equity for accurate drawdown
74
+ const currentEquity = position
75
+ ? equity * (1 + (price - entryPrice) / entryPrice)
76
+ : equity;
77
+ if (currentEquity > peak) peak = currentEquity;
78
+ const dd = (peak - currentEquity) / peak;
79
+ if (dd > maxDd) maxDd = dd;
80
+ }
81
+
82
+ // Close open position at end
83
+ if (position) {
84
+ const lastPrice = closes[closes.length - 1];
85
+ const pnl = (lastPrice - entryPrice) / entryPrice;
86
+ equity *= 1 + pnl;
87
+ tradeLog.push({ type: "sell", date: bars[bars.length - 1].date, price: lastPrice, pnl });
88
+ }
89
+
90
+ return buildResult("sma_crossover", equity - 1, closes, tradeLog, maxDd);
91
+ }
92
+
93
+ function backtestRSIMeanReversion(bars: OHLCV[], closes: number[]): BacktestResult {
94
+ const rsi = computeRSI(closes, 14);
95
+
96
+ if (rsi.length === 0) {
97
+ return emptyResult("rsi_mean_reversion", closes);
98
+ }
99
+
100
+ // RSI starts at index 14 (after 14 periods of data)
101
+ const rsiOffset = 14;
102
+ let position = false;
103
+ let entryPrice = 0;
104
+ const tradeLog: BacktestResult["tradeLog"] = [];
105
+ let equity = 1.0;
106
+ let peak = 1.0;
107
+ let maxDd = 0;
108
+
109
+ for (let i = 0; i < rsi.length; i++) {
110
+ const barIdx = i + rsiOffset;
111
+ const r = rsi[i];
112
+ const price = closes[barIdx];
113
+
114
+ if (!position && r < 30) {
115
+ // RSI oversold → buy
116
+ position = true;
117
+ entryPrice = price;
118
+ tradeLog.push({ type: "buy", date: bars[barIdx].date, price });
119
+ } else if (position && r > 70) {
120
+ // RSI overbought → sell
121
+ const pnl = (price - entryPrice) / entryPrice;
122
+ equity *= 1 + pnl;
123
+ tradeLog.push({ type: "sell", date: bars[barIdx].date, price, pnl });
124
+ position = false;
125
+ }
126
+
127
+ // Track mark-to-market equity for accurate drawdown
128
+ const currentEquity = position
129
+ ? equity * (1 + (price - entryPrice) / entryPrice)
130
+ : equity;
131
+ if (currentEquity > peak) peak = currentEquity;
132
+ const dd = (peak - currentEquity) / peak;
133
+ if (dd > maxDd) maxDd = dd;
134
+ }
135
+
136
+ // Close open position at end
137
+ if (position) {
138
+ const lastPrice = closes[closes.length - 1];
139
+ const pnl = (lastPrice - entryPrice) / entryPrice;
140
+ equity *= 1 + pnl;
141
+ tradeLog.push({ type: "sell", date: bars[bars.length - 1].date, price: lastPrice, pnl });
142
+ }
143
+
144
+ return buildResult("rsi_mean_reversion", equity - 1, closes, tradeLog, maxDd);
145
+ }
146
+
147
+ function buildResult(
148
+ strategy: string,
149
+ totalReturn: number,
150
+ closes: number[],
151
+ tradeLog: BacktestResult["tradeLog"],
152
+ maxDrawdown: number,
153
+ ): BacktestResult {
154
+ const sellTrades = tradeLog.filter((t) => t.type === "sell" && t.pnl != null);
155
+ const wins = sellTrades.filter((t) => t.pnl! > 0).length;
156
+ const buyAndHoldReturn = closes.length > 1
157
+ ? (closes[closes.length - 1] - closes[0]) / closes[0]
158
+ : 0;
159
+
160
+ return {
161
+ strategy,
162
+ totalReturn,
163
+ buyAndHoldReturn,
164
+ trades: sellTrades.length,
165
+ wins,
166
+ winRate: sellTrades.length > 0 ? wins / sellTrades.length : 0,
167
+ maxDrawdown,
168
+ tradeLog,
169
+ };
170
+ }
171
+
172
+ function emptyResult(strategy: string, closes: number[]): BacktestResult {
173
+ return {
174
+ strategy,
175
+ totalReturn: 0,
176
+ buyAndHoldReturn: closes.length > 1
177
+ ? (closes[closes.length - 1] - closes[0]) / closes[0]
178
+ : 0,
179
+ trades: 0,
180
+ wins: 0,
181
+ winRate: 0,
182
+ maxDrawdown: 0,
183
+ tradeLog: [],
184
+ };
185
+ }
186
+
187
+ const params = Type.Object({
188
+ symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT, SPY)" }),
189
+ strategy: Type.Union(
190
+ [Type.Literal("sma_crossover"), Type.Literal("rsi_mean_reversion")],
191
+ { description: "Strategy: sma_crossover (buy when SMA20 > SMA50, sell on reverse) or rsi_mean_reversion (buy when RSI < 30, sell when RSI > 70)" },
192
+ ),
193
+ period: Type.Optional(
194
+ Type.String({ description: "Historical period to backtest: 1y, 2y, 5y. Default: 2y" }),
195
+ ),
196
+ });
197
+
198
+ export const backtestTool: AgentTool<typeof params> = {
199
+ name: "backtest_strategy",
200
+ label: "Backtest Strategy",
201
+ description:
202
+ "Backtest a simple trading strategy against historical data. Supported strategies: SMA crossover (SMA20/SMA50) and RSI mean-reversion (buy <30, sell >70). Returns total return, win rate, max drawdown, and comparison to buy-and-hold.",
203
+ parameters: params,
204
+ async execute(toolCallId, args) {
205
+ const symbol = args.symbol.toUpperCase();
206
+ const period = args.period ?? "2y";
207
+ const historyResult = await wrapProvider("yahoo", () => getHistory(symbol, period, "1d"));
208
+ if (historyResult.status === "unavailable") {
209
+ return {
210
+ content: [{ type: "text", text: `⚠ Backtest unavailable for ${symbol} (${historyResult.reason}).` }],
211
+ details: null as any,
212
+ };
213
+ }
214
+ const bars = historyResult.data;
215
+
216
+ if (bars.length < 60) {
217
+ return {
218
+ content: [{ type: "text", text: `Insufficient data for backtesting ${symbol} (need 60+ days, got ${bars.length})` }],
219
+ details: null,
220
+ };
221
+ }
222
+
223
+ const result = runBacktest(bars, args.strategy);
224
+
225
+ const outperformance = result.totalReturn - result.buyAndHoldReturn;
226
+ const lines = [
227
+ `**${symbol} Backtest: ${args.strategy}** (${bars[0].date} to ${bars[bars.length - 1].date}, ${bars.length} days)`,
228
+ ``,
229
+ `Strategy Return: ${(result.totalReturn * 100).toFixed(2)}%`,
230
+ `Buy & Hold Return: ${(result.buyAndHoldReturn * 100).toFixed(2)}%`,
231
+ `Outperformance: ${outperformance >= 0 ? "+" : ""}${(outperformance * 100).toFixed(2)}%`,
232
+ ``,
233
+ `Trades: ${result.trades} | Wins: ${result.wins} | Win Rate: ${(result.winRate * 100).toFixed(0)}%`,
234
+ `Max Drawdown: ${(result.maxDrawdown * 100).toFixed(2)}%`,
235
+ ``,
236
+ result.totalReturn > result.buyAndHoldReturn
237
+ ? `Strategy outperformed buy-and-hold by ${(outperformance * 100).toFixed(2)}%.`
238
+ : `Buy-and-hold outperformed the strategy by ${(-outperformance * 100).toFixed(2)}%.`,
239
+ ];
240
+
241
+ return {
242
+ content: [{ type: "text", text: lines.join("\n") }],
243
+ details: result,
244
+ };
245
+ },
246
+ };