opencandle 0.5.0 → 0.6.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 (527) hide show
  1. package/README.md +164 -187
  2. package/dist/analysts/contracts.d.ts +1 -3
  3. package/dist/analysts/contracts.js +1 -11
  4. package/dist/analysts/contracts.js.map +1 -1
  5. package/dist/analysts/orchestrator.d.ts +1 -3
  6. package/dist/analysts/orchestrator.js +1 -26
  7. package/dist/analysts/orchestrator.js.map +1 -1
  8. package/dist/cli.js +30 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +3 -3
  11. package/dist/config.js +12 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/infra/browser.js +3 -1
  17. package/dist/infra/browser.js.map +1 -1
  18. package/dist/infra/cache.d.ts +8 -11
  19. package/dist/infra/cache.js +17 -15
  20. package/dist/infra/cache.js.map +1 -1
  21. package/dist/infra/http-client.d.ts +4 -1
  22. package/dist/infra/http-client.js +59 -6
  23. package/dist/infra/http-client.js.map +1 -1
  24. package/dist/infra/index.d.ts +3 -3
  25. package/dist/infra/index.js +3 -3
  26. package/dist/infra/index.js.map +1 -1
  27. package/dist/infra/native-dependencies.js +2 -2
  28. package/dist/infra/native-dependencies.js.map +1 -1
  29. package/dist/infra/node-version.js.map +1 -1
  30. package/dist/infra/opencandle-paths.d.ts +0 -3
  31. package/dist/infra/opencandle-paths.js +4 -11
  32. package/dist/infra/opencandle-paths.js.map +1 -1
  33. package/dist/infra/rate-limiter.js +12 -9
  34. package/dist/infra/rate-limiter.js.map +1 -1
  35. package/dist/market-state/alert-conditions.d.ts +34 -0
  36. package/dist/market-state/alert-conditions.js +23 -0
  37. package/dist/market-state/alert-conditions.js.map +1 -0
  38. package/dist/market-state/alert-runner.d.ts +55 -0
  39. package/dist/market-state/alert-runner.js +634 -0
  40. package/dist/market-state/alert-runner.js.map +1 -0
  41. package/dist/market-state/daily-report.d.ts +26 -0
  42. package/dist/market-state/daily-report.js +179 -0
  43. package/dist/market-state/daily-report.js.map +1 -0
  44. package/dist/market-state/local-automation-service.d.ts +25 -0
  45. package/dist/market-state/local-automation-service.js +119 -0
  46. package/dist/market-state/local-automation-service.js.map +1 -0
  47. package/dist/market-state/notification-delivery.d.ts +14 -0
  48. package/dist/market-state/notification-delivery.js +139 -0
  49. package/dist/market-state/notification-delivery.js.map +1 -0
  50. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  51. package/dist/market-state/resolve-for-mutation.js +15 -0
  52. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  53. package/dist/market-state/resolve.d.ts +14 -0
  54. package/dist/market-state/resolve.js +89 -0
  55. package/dist/market-state/resolve.js.map +1 -0
  56. package/dist/market-state/service.d.ts +527 -0
  57. package/dist/market-state/service.js +1099 -0
  58. package/dist/market-state/service.js.map +1 -0
  59. package/dist/memory/index.d.ts +7 -7
  60. package/dist/memory/index.js +6 -6
  61. package/dist/memory/index.js.map +1 -1
  62. package/dist/memory/manager.js +11 -11
  63. package/dist/memory/manager.js.map +1 -1
  64. package/dist/memory/retrieval.js +7 -4
  65. package/dist/memory/retrieval.js.map +1 -1
  66. package/dist/memory/sqlite.js +385 -3
  67. package/dist/memory/sqlite.js.map +1 -1
  68. package/dist/memory/storage.js +1 -2
  69. package/dist/memory/storage.js.map +1 -1
  70. package/dist/memory/tool-defaults.js +64 -28
  71. package/dist/memory/tool-defaults.js.map +1 -1
  72. package/dist/memory/types.js.map +1 -1
  73. package/dist/monitor.d.ts +2 -0
  74. package/dist/monitor.js +104 -0
  75. package/dist/monitor.js.map +1 -0
  76. package/dist/onboarding/connect.js +4 -6
  77. package/dist/onboarding/connect.js.map +1 -1
  78. package/dist/onboarding/credential-interceptor.js +1 -1
  79. package/dist/onboarding/credential-interceptor.js.map +1 -1
  80. package/dist/onboarding/degradation-accumulator.js +1 -3
  81. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  82. package/dist/onboarding/providers.js +3 -16
  83. package/dist/onboarding/providers.js.map +1 -1
  84. package/dist/onboarding/state.js.map +1 -1
  85. package/dist/onboarding/tool-helpers.js +1 -1
  86. package/dist/onboarding/tool-helpers.js.map +1 -1
  87. package/dist/onboarding/tool-tags.js +6 -4
  88. package/dist/onboarding/tool-tags.js.map +1 -1
  89. package/dist/onboarding/validation.js +1 -1
  90. package/dist/onboarding/validation.js.map +1 -1
  91. package/dist/pi/opencandle-extension.d.ts +8 -0
  92. package/dist/pi/opencandle-extension.js +412 -28
  93. package/dist/pi/opencandle-extension.js.map +1 -1
  94. package/dist/pi/session.d.ts +1 -1
  95. package/dist/pi/session.js +3 -1
  96. package/dist/pi/session.js.map +1 -1
  97. package/dist/pi/setup.js +8 -3
  98. package/dist/pi/setup.js.map +1 -1
  99. package/dist/pi/tool-adapter.js +5 -2
  100. package/dist/pi/tool-adapter.js.map +1 -1
  101. package/dist/prompts/context-builder.d.ts +1 -1
  102. package/dist/prompts/context-builder.js +19 -6
  103. package/dist/prompts/context-builder.js.map +1 -1
  104. package/dist/prompts/policy-cards.d.ts +1 -1
  105. package/dist/prompts/policy-cards.js +1 -1
  106. package/dist/prompts/policy-cards.js.map +1 -1
  107. package/dist/prompts/sections.d.ts +1 -1
  108. package/dist/prompts/symbol-preflight.d.ts +20 -0
  109. package/dist/prompts/symbol-preflight.js +49 -0
  110. package/dist/prompts/symbol-preflight.js.map +1 -0
  111. package/dist/prompts/workflow-prompts.d.ts +1 -1
  112. package/dist/prompts/workflow-prompts.js +54 -16
  113. package/dist/prompts/workflow-prompts.js.map +1 -1
  114. package/dist/providers/alpha-vantage.d.ts +1 -1
  115. package/dist/providers/alpha-vantage.js +26 -7
  116. package/dist/providers/alpha-vantage.js.map +1 -1
  117. package/dist/providers/coingecko.js +1 -1
  118. package/dist/providers/coingecko.js.map +1 -1
  119. package/dist/providers/errors.d.ts +5 -0
  120. package/dist/providers/errors.js +11 -0
  121. package/dist/providers/errors.js.map +1 -0
  122. package/dist/providers/exa-search.d.ts +2 -2
  123. package/dist/providers/exa-search.js +19 -11
  124. package/dist/providers/exa-search.js.map +1 -1
  125. package/dist/providers/fear-greed.js +1 -1
  126. package/dist/providers/fear-greed.js.map +1 -1
  127. package/dist/providers/finnhub.js +3 -5
  128. package/dist/providers/finnhub.js.map +1 -1
  129. package/dist/providers/fred.js +2 -2
  130. package/dist/providers/fred.js.map +1 -1
  131. package/dist/providers/index.d.ts +7 -6
  132. package/dist/providers/index.js +6 -5
  133. package/dist/providers/index.js.map +1 -1
  134. package/dist/providers/reddit.js +2 -2
  135. package/dist/providers/reddit.js.map +1 -1
  136. package/dist/providers/sec-edgar.d.ts +1 -0
  137. package/dist/providers/sec-edgar.js +12 -4
  138. package/dist/providers/sec-edgar.js.map +1 -1
  139. package/dist/providers/tradingview.d.ts +47 -0
  140. package/dist/providers/tradingview.js +275 -0
  141. package/dist/providers/tradingview.js.map +1 -0
  142. package/dist/providers/twitter.js +6 -8
  143. package/dist/providers/twitter.js.map +1 -1
  144. package/dist/providers/web-search.js +26 -12
  145. package/dist/providers/web-search.js.map +1 -1
  146. package/dist/providers/with-fallback.js +4 -2
  147. package/dist/providers/with-fallback.js.map +1 -1
  148. package/dist/providers/wrap-provider.d.ts +2 -3
  149. package/dist/providers/wrap-provider.js +14 -8
  150. package/dist/providers/wrap-provider.js.map +1 -1
  151. package/dist/providers/yahoo-finance.d.ts +1 -1
  152. package/dist/providers/yahoo-finance.js +101 -17
  153. package/dist/providers/yahoo-finance.js.map +1 -1
  154. package/dist/routing/classify-intent.d.ts +6 -0
  155. package/dist/routing/classify-intent.js +78 -7
  156. package/dist/routing/classify-intent.js.map +1 -1
  157. package/dist/routing/defaults.d.ts +1 -1
  158. package/dist/routing/entity-extractor.d.ts +1 -0
  159. package/dist/routing/entity-extractor.js +234 -29
  160. package/dist/routing/entity-extractor.js.map +1 -1
  161. package/dist/routing/fund-symbols.d.ts +2 -0
  162. package/dist/routing/fund-symbols.js +55 -0
  163. package/dist/routing/fund-symbols.js.map +1 -0
  164. package/dist/routing/horizon.d.ts +1 -0
  165. package/dist/routing/horizon.js +10 -0
  166. package/dist/routing/horizon.js.map +1 -0
  167. package/dist/routing/index.d.ts +10 -10
  168. package/dist/routing/index.js +6 -6
  169. package/dist/routing/index.js.map +1 -1
  170. package/dist/routing/planning.d.ts +1 -1
  171. package/dist/routing/planning.js +65 -34
  172. package/dist/routing/planning.js.map +1 -1
  173. package/dist/routing/route-manifest.d.ts +2 -2
  174. package/dist/routing/route-manifest.js +25 -4
  175. package/dist/routing/route-manifest.js.map +1 -1
  176. package/dist/routing/router-llm-client.js.map +1 -1
  177. package/dist/routing/router-prompt.js +7 -9
  178. package/dist/routing/router-prompt.js.map +1 -1
  179. package/dist/routing/router-types.d.ts +1 -0
  180. package/dist/routing/router.js +137 -22
  181. package/dist/routing/router.js.map +1 -1
  182. package/dist/routing/slot-resolver.d.ts +1 -1
  183. package/dist/routing/slot-resolver.js +2 -4
  184. package/dist/routing/slot-resolver.js.map +1 -1
  185. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  186. package/dist/routing/symbol-disambiguator.js +52 -0
  187. package/dist/routing/symbol-disambiguator.js.map +1 -0
  188. package/dist/routing/turn-context.d.ts +1 -1
  189. package/dist/routing/turn-context.js +1 -1
  190. package/dist/routing/turn-context.js.map +1 -1
  191. package/dist/routing/types.d.ts +2 -0
  192. package/dist/runtime/answer-contracts.js +36 -8
  193. package/dist/runtime/answer-contracts.js.map +1 -1
  194. package/dist/runtime/artifact-contracts.js.map +1 -1
  195. package/dist/runtime/planning-evidence.js +47 -26
  196. package/dist/runtime/planning-evidence.js.map +1 -1
  197. package/dist/runtime/prompt-step.d.ts +1 -9
  198. package/dist/runtime/prompt-step.js +0 -10
  199. package/dist/runtime/prompt-step.js.map +1 -1
  200. package/dist/runtime/run-context.d.ts +5 -2
  201. package/dist/runtime/run-context.js +8 -1
  202. package/dist/runtime/run-context.js.map +1 -1
  203. package/dist/runtime/session-coordinator.d.ts +13 -5
  204. package/dist/runtime/session-coordinator.js +160 -20
  205. package/dist/runtime/session-coordinator.js.map +1 -1
  206. package/dist/runtime/session-title.d.ts +14 -0
  207. package/dist/runtime/session-title.js +50 -0
  208. package/dist/runtime/session-title.js.map +1 -0
  209. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  210. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  211. package/dist/runtime/validation.js.map +1 -1
  212. package/dist/runtime/workflow-events.js.map +1 -1
  213. package/dist/runtime/workflow-runner.d.ts +3 -3
  214. package/dist/runtime/workflow-runner.js +1 -1
  215. package/dist/runtime/workflow-runner.js.map +1 -1
  216. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  217. package/dist/sentiment/adapters/finnhub.js +6 -1
  218. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  219. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  220. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  221. package/dist/sentiment/adapters/web.d.ts +1 -1
  222. package/dist/sentiment/index.d.ts +9 -11
  223. package/dist/sentiment/index.js +9 -20
  224. package/dist/sentiment/index.js.map +1 -1
  225. package/dist/sentiment/keywords.js +26 -4
  226. package/dist/sentiment/keywords.js.map +1 -1
  227. package/dist/sentiment/pipeline.d.ts +2 -2
  228. package/dist/sentiment/pipeline.js +1 -1
  229. package/dist/sentiment/pipeline.js.map +1 -1
  230. package/dist/sentiment/scorer.js +1 -1
  231. package/dist/sentiment/store.d.ts +1 -1
  232. package/dist/sentiment/store.js +1 -1
  233. package/dist/sentiment/store.js.map +1 -1
  234. package/dist/sentiment/trends.d.ts +1 -1
  235. package/dist/sentiment/trends.js.map +1 -1
  236. package/dist/sentiment/types.js.map +1 -1
  237. package/dist/system-prompt.js +3 -2
  238. package/dist/system-prompt.js.map +1 -1
  239. package/dist/tool-kit.d.ts +7 -7
  240. package/dist/tool-kit.js +4 -4
  241. package/dist/tool-kit.js.map +1 -1
  242. package/dist/tools/fundamentals/company-overview.js +11 -6
  243. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  244. package/dist/tools/fundamentals/comps.js +18 -9
  245. package/dist/tools/fundamentals/comps.js.map +1 -1
  246. package/dist/tools/fundamentals/dcf.js +23 -11
  247. package/dist/tools/fundamentals/dcf.js.map +1 -1
  248. package/dist/tools/fundamentals/earnings.js +8 -3
  249. package/dist/tools/fundamentals/earnings.js.map +1 -1
  250. package/dist/tools/fundamentals/financials.js +8 -3
  251. package/dist/tools/fundamentals/financials.js.map +1 -1
  252. package/dist/tools/fundamentals/sec-filings.js +21 -6
  253. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  254. package/dist/tools/index.d.ts +23 -19
  255. package/dist/tools/index.js +51 -39
  256. package/dist/tools/index.js.map +1 -1
  257. package/dist/tools/interaction/ask-user.js +15 -3
  258. package/dist/tools/interaction/ask-user.js.map +1 -1
  259. package/dist/tools/interaction/twitter-login.js +13 -3
  260. package/dist/tools/interaction/twitter-login.js.map +1 -1
  261. package/dist/tools/macro/fear-greed.js.map +1 -1
  262. package/dist/tools/macro/fred-data.d.ts +1 -1
  263. package/dist/tools/macro/fred-data.js +17 -6
  264. package/dist/tools/macro/fred-data.js.map +1 -1
  265. package/dist/tools/market/crypto-history.js +3 -1
  266. package/dist/tools/market/crypto-history.js.map +1 -1
  267. package/dist/tools/market/crypto-price.js +3 -1
  268. package/dist/tools/market/crypto-price.js.map +1 -1
  269. package/dist/tools/market/screen-stocks.d.ts +18 -0
  270. package/dist/tools/market/screen-stocks.js +252 -0
  271. package/dist/tools/market/screen-stocks.js.map +1 -0
  272. package/dist/tools/market/search-ticker.js +160 -8
  273. package/dist/tools/market/search-ticker.js.map +1 -1
  274. package/dist/tools/market/stock-history.d.ts +2 -2
  275. package/dist/tools/market/stock-history.js +26 -7
  276. package/dist/tools/market/stock-history.js.map +1 -1
  277. package/dist/tools/market/stock-quote.js +5 -3
  278. package/dist/tools/market/stock-quote.js.map +1 -1
  279. package/dist/tools/options/greeks.js +1 -1
  280. package/dist/tools/options/greeks.js.map +1 -1
  281. package/dist/tools/options/option-chain.js +19 -6
  282. package/dist/tools/options/option-chain.js.map +1 -1
  283. package/dist/tools/portfolio/alerts.d.ts +15 -0
  284. package/dist/tools/portfolio/alerts.js +357 -0
  285. package/dist/tools/portfolio/alerts.js.map +1 -0
  286. package/dist/tools/portfolio/correlation.d.ts +1 -1
  287. package/dist/tools/portfolio/correlation.js +33 -13
  288. package/dist/tools/portfolio/correlation.js.map +1 -1
  289. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  290. package/dist/tools/portfolio/daily-report.js +83 -0
  291. package/dist/tools/portfolio/daily-report.js.map +1 -0
  292. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  293. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  294. package/dist/tools/portfolio/notifications.d.ts +7 -0
  295. package/dist/tools/portfolio/notifications.js +43 -0
  296. package/dist/tools/portfolio/notifications.js.map +1 -0
  297. package/dist/tools/portfolio/predictions.d.ts +12 -6
  298. package/dist/tools/portfolio/predictions.js +337 -87
  299. package/dist/tools/portfolio/predictions.js.map +1 -1
  300. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  301. package/dist/tools/portfolio/risk-analysis.js +45 -6
  302. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  303. package/dist/tools/portfolio/tracker.d.ts +4 -3
  304. package/dist/tools/portfolio/tracker.js +246 -101
  305. package/dist/tools/portfolio/tracker.js.map +1 -1
  306. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  307. package/dist/tools/portfolio/watchlist.js +208 -108
  308. package/dist/tools/portfolio/watchlist.js.map +1 -1
  309. package/dist/tools/sentiment/reddit-sentiment.js +23 -10
  310. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  311. package/dist/tools/sentiment/sentiment-summary.js +15 -13
  312. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  313. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  314. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  315. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  316. package/dist/tools/sentiment/twitter-sentiment.js +12 -5
  317. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  318. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  319. package/dist/tools/sentiment/untrusted-text.js +17 -0
  320. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  321. package/dist/tools/sentiment/web-search.js +9 -13
  322. package/dist/tools/sentiment/web-search.js.map +1 -1
  323. package/dist/tools/sentiment/web-sentiment.js +15 -3
  324. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  325. package/dist/tools/technical/backtest.d.ts +1 -1
  326. package/dist/tools/technical/backtest.js +27 -20
  327. package/dist/tools/technical/backtest.js.map +1 -1
  328. package/dist/tools/technical/indicators.js +23 -5
  329. package/dist/tools/technical/indicators.js.map +1 -1
  330. package/dist/types/index.d.ts +3 -3
  331. package/dist/types/index.js.map +1 -1
  332. package/dist/types/market.d.ts +1 -0
  333. package/dist/types/portfolio.d.ts +14 -4
  334. package/dist/workflows/compare-assets.d.ts +0 -3
  335. package/dist/workflows/compare-assets.js +20 -11
  336. package/dist/workflows/compare-assets.js.map +1 -1
  337. package/dist/workflows/index.d.ts +3 -4
  338. package/dist/workflows/index.js +3 -3
  339. package/dist/workflows/index.js.map +1 -1
  340. package/dist/workflows/options-screener.d.ts +0 -3
  341. package/dist/workflows/options-screener.js +4 -11
  342. package/dist/workflows/options-screener.js.map +1 -1
  343. package/dist/workflows/portfolio-builder.d.ts +0 -3
  344. package/dist/workflows/portfolio-builder.js +0 -8
  345. package/dist/workflows/portfolio-builder.js.map +1 -1
  346. package/gui/server/ask-user-bridge.ts +1 -1
  347. package/gui/server/automation-heartbeat.ts +97 -0
  348. package/gui/server/background-quotes.ts +97 -1
  349. package/gui/server/chat-event-adapter.ts +32 -10
  350. package/gui/server/chat-run-session.ts +16 -0
  351. package/gui/server/invoke-tool.ts +144 -1
  352. package/gui/server/live-chat-event-adapter.ts +21 -6
  353. package/gui/server/market-state-api.ts +315 -0
  354. package/gui/server/model-setup.ts +149 -2
  355. package/gui/server/private-api-access.ts +62 -0
  356. package/gui/server/projector.ts +12 -7
  357. package/gui/server/prompt-observation.ts +4 -7
  358. package/gui/server/quote-snapshot-store.ts +50 -0
  359. package/gui/server/server.ts +200 -451
  360. package/gui/server/session-actions.ts +186 -1
  361. package/gui/server/shutdown.ts +47 -0
  362. package/gui/server/tool-invoke-ack.ts +49 -0
  363. package/gui/server/tool-metadata.ts +23 -10
  364. package/gui/server/websocket.ts +13 -3
  365. package/gui/server/writer-lock.ts +6 -2
  366. package/gui/server/ws-hub.ts +292 -0
  367. package/gui/shared/chat-events.ts +16 -1
  368. package/gui/shared/event-reducer.ts +24 -6
  369. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  370. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  371. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  372. package/gui/web/dist/index.html +2 -2
  373. package/package.json +5 -1
  374. package/src/analysts/contracts.ts +10 -23
  375. package/src/analysts/orchestrator.ts +8 -43
  376. package/src/cli.ts +35 -12
  377. package/src/config.ts +17 -9
  378. package/src/index.ts +1 -1
  379. package/src/infra/browser.ts +3 -1
  380. package/src/infra/cache.ts +41 -30
  381. package/src/infra/http-client.ts +72 -6
  382. package/src/infra/index.ts +7 -10
  383. package/src/infra/native-dependencies.ts +8 -3
  384. package/src/infra/node-version.ts +3 -1
  385. package/src/infra/opencandle-paths.ts +3 -14
  386. package/src/infra/rate-limiter.ts +22 -19
  387. package/src/market-state/alert-conditions.ts +82 -0
  388. package/src/market-state/alert-runner.ts +863 -0
  389. package/src/market-state/daily-report.ts +247 -0
  390. package/src/market-state/local-automation-service.ts +162 -0
  391. package/src/market-state/notification-delivery.ts +158 -0
  392. package/src/market-state/resolve-for-mutation.ts +24 -0
  393. package/src/market-state/resolve.ts +112 -0
  394. package/src/market-state/service.ts +2344 -0
  395. package/src/memory/index.ts +7 -7
  396. package/src/memory/manager.ts +14 -16
  397. package/src/memory/retrieval.ts +8 -7
  398. package/src/memory/sqlite.ts +407 -6
  399. package/src/memory/storage.ts +5 -15
  400. package/src/memory/tool-defaults.ts +60 -39
  401. package/src/memory/types.ts +3 -3
  402. package/src/monitor.ts +121 -0
  403. package/src/onboarding/connect.ts +10 -33
  404. package/src/onboarding/credential-interceptor.ts +3 -15
  405. package/src/onboarding/degradation-accumulator.ts +1 -3
  406. package/src/onboarding/providers.ts +9 -40
  407. package/src/onboarding/state.ts +4 -15
  408. package/src/onboarding/tool-helpers.ts +2 -9
  409. package/src/onboarding/tool-tags.ts +6 -6
  410. package/src/onboarding/validation.ts +14 -20
  411. package/src/pi/opencandle-extension.ts +529 -85
  412. package/src/pi/session.ts +7 -5
  413. package/src/pi/setup.ts +61 -43
  414. package/src/pi/tool-adapter.ts +5 -2
  415. package/src/prompts/context-builder.ts +23 -12
  416. package/src/prompts/policy-cards.ts +2 -2
  417. package/src/prompts/sections.ts +1 -1
  418. package/src/prompts/symbol-preflight.ts +80 -0
  419. package/src/prompts/workflow-prompts.ts +77 -28
  420. package/src/providers/alpha-vantage.ts +58 -39
  421. package/src/providers/coingecko.ts +2 -5
  422. package/src/providers/errors.ts +9 -0
  423. package/src/providers/exa-search.ts +24 -22
  424. package/src/providers/fear-greed.ts +1 -1
  425. package/src/providers/finnhub.ts +7 -6
  426. package/src/providers/fred.ts +3 -3
  427. package/src/providers/index.ts +14 -6
  428. package/src/providers/reddit.ts +17 -6
  429. package/src/providers/sec-edgar.ts +20 -6
  430. package/src/providers/tradingview.ts +399 -0
  431. package/src/providers/twitter.ts +6 -8
  432. package/src/providers/web-search.ts +30 -20
  433. package/src/providers/with-fallback.ts +8 -7
  434. package/src/providers/wrap-provider.ts +15 -10
  435. package/src/providers/yahoo-finance.ts +140 -35
  436. package/src/routing/classify-intent.ts +101 -10
  437. package/src/routing/defaults.ts +1 -1
  438. package/src/routing/entity-extractor.ts +287 -38
  439. package/src/routing/fund-symbols.ts +58 -0
  440. package/src/routing/horizon.ts +7 -0
  441. package/src/routing/index.ts +48 -48
  442. package/src/routing/planning.ts +144 -53
  443. package/src/routing/route-manifest.ts +37 -15
  444. package/src/routing/router-llm-client.ts +4 -4
  445. package/src/routing/router-prompt.ts +15 -19
  446. package/src/routing/router-types.ts +2 -5
  447. package/src/routing/router.ts +251 -53
  448. package/src/routing/slot-resolver.ts +34 -11
  449. package/src/routing/symbol-disambiguator.ts +72 -0
  450. package/src/routing/turn-context.ts +6 -9
  451. package/src/routing/types.ts +2 -0
  452. package/src/runtime/answer-contracts.ts +82 -43
  453. package/src/runtime/artifact-contracts.ts +2 -1
  454. package/src/runtime/planning-evidence.ts +157 -66
  455. package/src/runtime/prompt-step.ts +1 -16
  456. package/src/runtime/run-context.ts +12 -2
  457. package/src/runtime/session-coordinator.ts +238 -63
  458. package/src/runtime/session-title.ts +60 -0
  459. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  460. package/src/runtime/validation.ts +1 -4
  461. package/src/runtime/workflow-events.ts +7 -7
  462. package/src/runtime/workflow-runner.ts +5 -11
  463. package/src/sentiment/adapters/finnhub.ts +7 -2
  464. package/src/sentiment/adapters/reddit.ts +2 -2
  465. package/src/sentiment/adapters/twitter.ts +1 -1
  466. package/src/sentiment/adapters/web.ts +1 -1
  467. package/src/sentiment/index.ts +16 -26
  468. package/src/sentiment/keywords.ts +26 -4
  469. package/src/sentiment/pipeline.ts +15 -4
  470. package/src/sentiment/scorer.ts +1 -1
  471. package/src/sentiment/store.ts +2 -2
  472. package/src/sentiment/trends.ts +9 -3
  473. package/src/sentiment/types.ts +5 -4
  474. package/src/system-prompt.ts +3 -2
  475. package/src/tool-kit.ts +10 -9
  476. package/src/tools/fundamentals/company-overview.ts +19 -9
  477. package/src/tools/fundamentals/comps.ts +68 -55
  478. package/src/tools/fundamentals/dcf.ts +145 -95
  479. package/src/tools/fundamentals/earnings.ts +16 -6
  480. package/src/tools/fundamentals/financials.ts +16 -7
  481. package/src/tools/fundamentals/sec-filings.ts +37 -16
  482. package/src/tools/index.ts +51 -39
  483. package/src/tools/interaction/ask-user.ts +22 -10
  484. package/src/tools/interaction/twitter-login.ts +17 -5
  485. package/src/tools/macro/fear-greed.ts +1 -1
  486. package/src/tools/macro/fred-data.ts +58 -46
  487. package/src/tools/market/crypto-history.ts +8 -3
  488. package/src/tools/market/crypto-price.ts +6 -6
  489. package/src/tools/market/screen-stocks.ts +279 -0
  490. package/src/tools/market/search-ticker.ts +218 -17
  491. package/src/tools/market/stock-history.ts +37 -12
  492. package/src/tools/market/stock-quote.ts +10 -7
  493. package/src/tools/options/greeks.ts +5 -5
  494. package/src/tools/options/option-chain.ts +41 -17
  495. package/src/tools/portfolio/alerts.ts +457 -0
  496. package/src/tools/portfolio/correlation.ts +47 -20
  497. package/src/tools/portfolio/daily-report.ts +101 -0
  498. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  499. package/src/tools/portfolio/notifications.ts +45 -0
  500. package/src/tools/portfolio/predictions.ts +406 -106
  501. package/src/tools/portfolio/risk-analysis.ts +46 -7
  502. package/src/tools/portfolio/tracker.ts +270 -109
  503. package/src/tools/portfolio/watchlist.ts +250 -121
  504. package/src/tools/sentiment/reddit-sentiment.ts +50 -24
  505. package/src/tools/sentiment/sentiment-summary.ts +62 -41
  506. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  507. package/src/tools/sentiment/twitter-sentiment.ts +22 -15
  508. package/src/tools/sentiment/untrusted-text.ts +21 -0
  509. package/src/tools/sentiment/web-search.ts +21 -18
  510. package/src/tools/sentiment/web-sentiment.ts +26 -10
  511. package/src/tools/technical/backtest.ts +32 -22
  512. package/src/tools/technical/indicators.ts +39 -14
  513. package/src/types/index.ts +8 -3
  514. package/src/types/market.ts +1 -0
  515. package/src/types/portfolio.ts +14 -4
  516. package/src/types/sentiment.ts +2 -2
  517. package/src/workflows/compare-assets.ts +33 -21
  518. package/src/workflows/index.ts +3 -4
  519. package/src/workflows/options-screener.ts +27 -29
  520. package/src/workflows/portfolio-builder.ts +34 -27
  521. package/dist/workflows/types.d.ts +0 -4
  522. package/dist/workflows/types.js +0 -2
  523. package/dist/workflows/types.js.map +0 -1
  524. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  525. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  526. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  527. package/src/workflows/types.ts +0 -4
@@ -8,8 +8,8 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
11
- <script type="module" crossorigin src="/assets/index-CZ9DHZYy.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-Bxt9QpLX.css">
11
+ <script type="module" crossorigin src="/assets/index-CveNgtDg.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-2KZtKBmu.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencandle",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Financial trading & investing agent",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/Kahtaf/OpenCandle#readme",
@@ -90,6 +90,8 @@
90
90
  "docs:site:serve": "node website/serve.mjs",
91
91
  "gui": "tsx gui/server/server.ts",
92
92
  "gui:dev": "tsx gui/server/server.ts",
93
+ "lint": "biome check .",
94
+ "format": "biome check --write .",
93
95
  "pretest": "npm run check:node",
94
96
  "test": "vitest run",
95
97
  "test:watch": "vitest",
@@ -107,6 +109,7 @@
107
109
  "test:evals:product": "tsx tests/scripts/run-product-evals.ts",
108
110
  "test:evals:competitive": "tsx tests/scripts/run-competitive-finance-eval.ts",
109
111
  "eval:competitive:analyze": "tsx tests/scripts/analyze-competitive-finance-report.ts",
112
+ "review:pr": ".agents/skills/autoreview/scripts/autoreview --mode branch --prompt-file .agents/skills/autoreview/references/opencandle-review.md --parallel-tests \"npx tsc --noEmit && npx vitest run\"",
110
113
  "version:patch": "npm version patch --no-git-tag-version",
111
114
  "version:minor": "npm version minor --no-git-tag-version",
112
115
  "version:major": "npm version major --no-git-tag-version",
@@ -136,6 +139,7 @@
136
139
  },
137
140
  "devDependencies": {
138
141
  "@agentclientprotocol/claude-agent-acp": "^0.37.0",
142
+ "@biomejs/biome": "2.5.0",
139
143
  "@sinclair/typebox": "^0.34.0",
140
144
  "@types/better-sqlite3": "^7.6.13",
141
145
  "@types/node": "^22.19.19",
@@ -1,31 +1,19 @@
1
- import type { AnalystOutput, AnalystSignal, DebateSide, DebateOutput } from "../runtime/workflow-types.js";
2
1
  import type { EvidenceRecord } from "../runtime/evidence.js";
2
+ import type {
3
+ AnalystOutput,
4
+ AnalystSignal,
5
+ DebateOutput,
6
+ DebateSide,
7
+ } from "../runtime/workflow-types.js";
3
8
 
4
9
  /** All analyst roles. */
5
- export type AnalystRole =
6
- | "valuation"
7
- | "momentum"
8
- | "options"
9
- | "contrarian"
10
- | "risk";
11
-
12
- /** Evidence fields expected per analyst role. */
13
- export const ROLE_EXPECTED_EVIDENCE: Record<AnalystRole, string[]> = {
14
- valuation: ["P/E Ratio", "Forward P/E", "EPS", "Intrinsic Value", "Revenue Growth"],
15
- momentum: ["RSI", "MACD", "SMA 50", "SMA 200", "Volume Trend"],
16
- options: ["Put/Call Ratio", "IV Level", "Unusual Volume", "Max Pain"],
17
- contrarian: ["Fear & Greed Index", "Reddit Sentiment", "Sentiment Score"],
18
- risk: ["Annualized Volatility", "Sharpe Ratio", "Max Drawdown", "VaR 95%", "Position Size"],
19
- };
10
+ export type AnalystRole = "valuation" | "momentum" | "options" | "contrarian" | "risk";
20
11
 
21
12
  /**
22
13
  * Parse an LLM response into a structured AnalystOutput.
23
14
  * Falls back to raw text if parsing fails.
24
15
  */
25
- export function parseAnalystOutput(
26
- role: string,
27
- responseText: string,
28
- ): AnalystOutput {
16
+ export function parseAnalystOutput(role: string, responseText: string): AnalystOutput {
29
17
  const signal = extractSignal(responseText);
30
18
  const conviction = extractConviction(responseText);
31
19
  const thesis = extractThesis(responseText);
@@ -91,9 +79,8 @@ export function tallyVotes(outputs: AnalystOutput[]): {
91
79
  weightedSum += signalValue * output.conviction;
92
80
  }
93
81
 
94
- const weightedConviction = totalWeight > 0
95
- ? Math.round((totalWeight / outputs.length) * 10) / 10
96
- : 0;
82
+ const weightedConviction =
83
+ totalWeight > 0 ? Math.round((totalWeight / outputs.length) * 10) / 10 : 0;
97
84
 
98
85
  let verdict: AnalystSignal;
99
86
  if (weightedSum > 0) verdict = "BUY";
@@ -1,9 +1,7 @@
1
- export type AnalystRole =
2
- | "valuation"
3
- | "momentum"
4
- | "options"
5
- | "contrarian"
6
- | "risk";
1
+ import type { WorkflowDefinition } from "../runtime/prompt-step.js";
2
+ import { promptStep } from "../runtime/prompt-step.js";
3
+
4
+ export type AnalystRole = "valuation" | "momentum" | "options" | "contrarian" | "risk";
7
5
 
8
6
  const SYMBOL_CAPTURE = "(\\$?[A-Za-z]{1,5}(?:[./-][A-Za-z]{1,2})?)";
9
7
  const NORMALIZED_SYMBOL_PATTERN = /^[A-Z]{1,5}(?:[./-][A-Z]{1,2})?$/;
@@ -173,33 +171,10 @@ export interface ComprehensiveAnalysisOptions {
173
171
  debate?: boolean;
174
172
  }
175
173
 
176
- export function getComprehensiveAnalysisPrompts(symbol: string, options?: ComprehensiveAnalysisOptions): string[] {
177
- const debate = options?.debate ?? true;
178
- const roles: AnalystRole[] = ["valuation", "momentum", "options", "contrarian", "risk"];
179
- const prompts = [getInitialAnalysisPrompt(symbol)];
180
-
181
- for (const role of roles) {
182
- prompts.push(ANALYST_PROMPTS[role](symbol));
183
- }
184
-
185
- if (debate) {
186
- prompts.push(buildBullPrompt(symbol));
187
- prompts.push(buildBearPrompt(symbol));
188
- prompts.push(buildRebuttalPrompt(symbol));
189
- prompts.push(buildSynthesisPrompt(symbol));
190
- prompts.push(VALIDATION_PROMPT_DEBATE(symbol));
191
- } else {
192
- prompts.push(SYNTHESIS_PROMPT_NO_DEBATE(symbol));
193
- prompts.push(VALIDATION_PROMPT_NO_DEBATE(symbol));
194
- }
195
-
196
- return prompts;
197
- }
198
-
199
- import type { WorkflowDefinition } from "../runtime/prompt-step.js";
200
- import { promptStep } from "../runtime/prompt-step.js";
201
-
202
- export function buildComprehensiveAnalysisDefinition(symbol: string, options?: ComprehensiveAnalysisOptions): WorkflowDefinition {
174
+ export function buildComprehensiveAnalysisDefinition(
175
+ symbol: string,
176
+ options?: ComprehensiveAnalysisOptions,
177
+ ): WorkflowDefinition {
203
178
  const debate = options?.debate ?? true;
204
179
  const roles: AnalystRole[] = ["valuation", "momentum", "options", "contrarian", "risk"];
205
180
 
@@ -265,16 +240,6 @@ export function buildComprehensiveAnalysisDefinition(symbol: string, options?: C
265
240
  };
266
241
  }
267
242
 
268
- export function runComprehensiveAnalysis(
269
- enqueueFollowUp: (prompt: string) => void,
270
- symbol: string,
271
- options?: ComprehensiveAnalysisOptions,
272
- ): void {
273
- for (const prompt of getComprehensiveAnalysisPrompts(symbol, options).slice(1)) {
274
- enqueueFollowUp(prompt);
275
- }
276
- }
277
-
278
243
  export function isAnalysisRequest(input: string): { match: boolean; symbol?: string } {
279
244
  const patterns = [
280
245
  new RegExp(`^analyze\\s+${SYMBOL_CAPTURE}\\s*$`, "i"),
package/src/cli.ts CHANGED
@@ -3,22 +3,21 @@ import "./infra/node-version.js";
3
3
  import { spawn } from "node:child_process";
4
4
  import { createRequire } from "node:module";
5
5
  import { dirname, resolve } from "node:path";
6
- import { parseArgs } from "node:util";
7
6
  import { fileURLToPath } from "node:url";
8
7
  import {
9
8
  AuthStorage,
10
- DefaultPackageManager,
11
- InteractiveMode,
12
- ModelRegistry,
13
- SettingsManager,
14
9
  createAgentSessionRuntime,
15
10
  createAgentSessionServices,
11
+ DefaultPackageManager,
16
12
  getAgentDir,
13
+ InteractiveMode,
17
14
  initTheme,
15
+ ModelRegistry,
16
+ SettingsManager,
18
17
  } from "@earendil-works/pi-coding-agent";
18
+ import { loadEnv } from "./config.js";
19
19
  import { createOpenCandleSession } from "./pi/session.js";
20
20
  import { continueOpenCandleSession } from "./pi/session-storage.js";
21
- import { loadEnv } from "./config.js";
22
21
 
23
22
  const require = createRequire(import.meta.url);
24
23
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -29,10 +28,7 @@ async function handlePackageCommand(
29
28
  agentDir: string,
30
29
  ): Promise<boolean> {
31
30
  const [command, ...rest] = args;
32
- if (
33
- !command ||
34
- !["install", "remove", "uninstall", "list", "update"].includes(command)
35
- ) {
31
+ if (!command || !["install", "remove", "uninstall", "list", "update"].includes(command)) {
36
32
  return false;
37
33
  }
38
34
 
@@ -139,13 +135,40 @@ async function handleGuiCommand(args: string[], cwd: string): Promise<boolean> {
139
135
  return true;
140
136
  }
141
137
 
138
+ async function handleMonitorCommand(args: string[], cwd: string): Promise<boolean> {
139
+ if (args[0] !== "monitor") return false;
140
+
141
+ const tsxCli = require.resolve("tsx/cli");
142
+ const monitorPath = resolve(packageRoot, "src/monitor.ts");
143
+ const child = spawn(process.execPath, [tsxCli, monitorPath, ...args.slice(1)], {
144
+ cwd,
145
+ env: process.env,
146
+ stdio: "inherit",
147
+ });
148
+
149
+ const exitCode = await new Promise<number>((resolveExit) => {
150
+ child.on("close", (code, signal) => {
151
+ if (signal) {
152
+ resolveExit(1);
153
+ } else {
154
+ resolveExit(code ?? 0);
155
+ }
156
+ });
157
+ });
158
+ process.exitCode = exitCode;
159
+ return true;
160
+ }
161
+
142
162
  async function main(): Promise<void> {
143
163
  const rawArgs = process.argv.slice(2);
144
- const { positionals } = parseArgs({ allowPositionals: true, strict: false });
145
164
  const cwd = process.cwd();
146
165
  const agentDir = getAgentDir();
147
166
 
148
- if (await handleGuiCommand(positionals, cwd)) {
167
+ if (await handleGuiCommand(rawArgs, cwd)) {
168
+ return;
169
+ }
170
+
171
+ if (await handleMonitorCommand(rawArgs, cwd)) {
149
172
  return;
150
173
  }
151
174
 
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { ensureParentDir, getConfigPath } from "./infra/opencandle-paths.js";
3
3
  import type { PlanningBehaviorMode, TaskFamily } from "./routing/planning.js";
4
4
 
@@ -22,9 +22,9 @@ export interface Config {
22
22
  /** Enable adversarial bull/bear debate in comprehensive analysis. Default: true. */
23
23
  debate?: boolean;
24
24
  /**
25
- * Intent-router mode. `"llm"` (default) runs the LLM router ahead of prompt
26
- * assembly. `"rules"` is the explicit legacy rule-router rollback path
27
- * (`classifyIntent` + `extractPreferences`). Controlled by
25
+ * Intent-router mode. `"rules"` (default) uses the deterministic rule
26
+ * router (`classifyIntent` + `extractPreferences`). `"llm"` opts into the
27
+ * LLM router ahead of prompt assembly. Controlled by
28
28
  * `OPENCANDLE_ROUTER_MODE`.
29
29
  */
30
30
  routerMode: RouterMode;
@@ -126,10 +126,10 @@ const PLANNING_BEHAVIOR_MODES = [
126
126
 
127
127
  function resolveRouterMode(): RouterMode {
128
128
  const raw = process.env.OPENCANDLE_ROUTER_MODE;
129
- if (raw === undefined || raw === "") return "llm";
129
+ if (raw === undefined || raw === "") return "rules";
130
130
  if (raw === "rules" || raw === "llm") return raw;
131
131
  throw new Error(
132
- `Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "llm" (default) or "rules".`,
132
+ `Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "rules" (default) or "llm".`,
133
133
  );
134
134
  }
135
135
 
@@ -185,7 +185,10 @@ function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
185
185
  braveApiKey: process.env.BRAVE_API_KEY ?? fileConfig.providers?.brave?.apiKey,
186
186
  exaApiKey: process.env.EXA_API_KEY ?? fileConfig.providers?.exa?.apiKey,
187
187
  finnhubApiKey: process.env.FINNHUB_API_KEY ?? fileConfig.providers?.finnhub?.apiKey,
188
- debate: debateEnv !== undefined ? debateEnv !== "false" && debateEnv !== "0" : fileConfig.debate ?? true,
188
+ debate:
189
+ debateEnv !== undefined
190
+ ? debateEnv !== "false" && debateEnv !== "0"
191
+ : (fileConfig.debate ?? true),
189
192
  routerMode: resolveRouterMode(),
190
193
  toolScopeMode: resolveToolScopeMode(),
191
194
  planningMigrationStatuses: resolvePlanningMigrationStatuses(),
@@ -193,7 +196,8 @@ function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
193
196
  retentionDays: fileSentiment?.retentionDays ?? SENTIMENT_DEFAULTS.retentionDays,
194
197
  defaultSubreddits: fileSentiment?.defaultSubreddits ?? SENTIMENT_DEFAULTS.defaultSubreddits,
195
198
  commentsPerPost: fileSentiment?.commentsPerPost ?? SENTIMENT_DEFAULTS.commentsPerPost,
196
- divergenceThreshold: fileSentiment?.divergenceThreshold ?? SENTIMENT_DEFAULTS.divergenceThreshold,
199
+ divergenceThreshold:
200
+ fileSentiment?.divergenceThreshold ?? SENTIMENT_DEFAULTS.divergenceThreshold,
197
201
  },
198
202
  };
199
203
  }
@@ -222,7 +226,11 @@ export function loadFileConfig(path = getConfigPath()): OpenCandleFileConfig {
222
226
 
223
227
  export function saveFileConfig(config: OpenCandleFileConfig, path = getConfigPath()): void {
224
228
  ensureParentDir(path);
225
- writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
229
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, {
230
+ encoding: "utf-8",
231
+ mode: 0o600,
232
+ });
233
+ if (process.platform !== "win32") chmodSync(path, 0o600);
226
234
  }
227
235
 
228
236
  export function loadConfig(): Config {
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { createOpenCandleSession, type CreateOpenCandleSessionOptions } from "./pi/session.js";
2
1
  export { default as openCandleExtension } from "./pi/opencandle-extension.js";
2
+ export { type CreateOpenCandleSessionOptions, createOpenCandleSession } from "./pi/session.js";
3
3
  export { agentToolToPiTool, getOpenCandleToolDefinitions } from "./pi/tool-adapter.js";
4
4
  export { registerTools } from "./tool-kit.js";
5
5
  export { getAllTools } from "./tools/index.js";
@@ -38,7 +38,9 @@ async function ensureBrowser(): Promise<Page> {
38
38
 
39
39
  async function withPage<T>(fn: (p: Page) => Promise<T>): Promise<T> {
40
40
  let resolve!: () => void;
41
- const next = new Promise<void>((r) => { resolve = r; });
41
+ const next = new Promise<void>((r) => {
42
+ resolve = r;
43
+ });
42
44
  const prev = pageQueue;
43
45
  pageQueue = next;
44
46
  await prev;
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
1
3
  interface CacheEntry<T> {
2
4
  value: T;
3
5
  expiresAt: number;
@@ -10,10 +12,26 @@ export interface StaleResult<T> {
10
12
  cachedAt: number;
11
13
  }
12
14
 
15
+ interface StaleMetadata {
16
+ stale: boolean;
17
+ cachedAt: number;
18
+ }
19
+
20
+ const staleMetadataStorage = new AsyncLocalStorage<StaleMetadata>();
21
+
22
+ export async function runWithStaleMetadata<T>(
23
+ fn: () => Promise<T>,
24
+ ): Promise<{ value: T; stale?: { cachedAt: number } }> {
25
+ const metadata: StaleMetadata = { stale: false, cachedAt: 0 };
26
+ const value = await staleMetadataStorage.run(metadata, fn);
27
+ return {
28
+ value,
29
+ stale: metadata.stale ? { cachedAt: metadata.cachedAt } : undefined,
30
+ };
31
+ }
32
+
13
33
  export class Cache {
14
34
  private store = new Map<string, CacheEntry<unknown>>();
15
- private lastStaleHit = false;
16
- private lastStaleCachedAt = 0;
17
35
 
18
36
  get<T>(key: string): T | undefined {
19
37
  const entry = this.store.get(key);
@@ -36,23 +54,14 @@ export class Cache {
36
54
  return undefined;
37
55
  }
38
56
 
39
- this.lastStaleHit = true;
40
- this.lastStaleCachedAt = entry.cachedAt;
57
+ const metadata = staleMetadataStorage.getStore();
58
+ if (metadata) {
59
+ metadata.stale = true;
60
+ metadata.cachedAt = entry.cachedAt;
61
+ }
41
62
  return { value: entry.value as T, stale: true, cachedAt: entry.cachedAt };
42
63
  }
43
64
 
44
- /**
45
- * Consume the stale flag set by the last getStale() hit.
46
- * Returns { stale: true, cachedAt } if the last getStale() found data,
47
- * then resets the flag. Used by wrapProvider to propagate stale metadata.
48
- */
49
- consumeStaleFlag(): { stale: boolean; cachedAt: number } {
50
- const result = { stale: this.lastStaleHit, cachedAt: this.lastStaleCachedAt };
51
- this.lastStaleHit = false;
52
- this.lastStaleCachedAt = 0;
53
- return result;
54
- }
55
-
56
65
  set<T>(key: string, value: T, ttlMs: number): void {
57
66
  this.store.set(key, { value, expiresAt: Date.now() + ttlMs, cachedAt: Date.now() });
58
67
  }
@@ -79,25 +88,27 @@ export const cache = new Cache();
79
88
 
80
89
  // Default TTLs
81
90
  export const TTL = {
82
- QUOTE: 60_000, // 1 minute
83
- HISTORY: 3_600_000, // 1 hour
91
+ QUOTE: 60_000, // 1 minute
92
+ HISTORY: 3_600_000, // 1 hour
84
93
  FUNDAMENTALS: 86_400_000, // 24 hours
85
- MACRO: 3_600_000, // 1 hour
86
- SENTIMENT: 300_000, // 5 minutes
94
+ MACRO: 3_600_000, // 1 hour
95
+ SENTIMENT: 300_000, // 5 minutes
87
96
  OPTIONS_CHAIN: 120_000, // 2 minutes
88
- CRUMB: 900_000, // 15 minutes
89
- WEB_SEARCH: 300_000, // 5 minutes
97
+ SCREENER: 60_000, // 1 minute
98
+ CRUMB: 900_000, // 15 minutes
99
+ WEB_SEARCH: 300_000, // 5 minutes
90
100
  FINNHUB_NEWS: 300_000, // 5 minutes
91
101
  } as const;
92
102
 
93
103
  // Stale limits — how long past TTL expiry a cached value is still useful as fallback
94
104
  export const STALE_LIMIT = {
95
- QUOTE: 15 * 60_000, // 15 minutes
96
- HISTORY: 24 * 3_600_000, // 24 hours
97
- FUNDAMENTALS: 7 * 86_400_000, // 7 days
98
- MACRO: 24 * 3_600_000, // 24 hours
99
- SENTIMENT: 3_600_000, // 1 hour
100
- OPTIONS_CHAIN: 30 * 60_000, // 30 minutes
101
- WEB_SEARCH: 3_600_000, // 1 hour
102
- FINNHUB_NEWS: 3_600_000, // 1 hour
105
+ QUOTE: 15 * 60_000, // 15 minutes
106
+ HISTORY: 24 * 3_600_000, // 24 hours
107
+ FUNDAMENTALS: 7 * 86_400_000, // 7 days
108
+ MACRO: 24 * 3_600_000, // 24 hours
109
+ SENTIMENT: 3_600_000, // 1 hour
110
+ OPTIONS_CHAIN: 30 * 60_000, // 30 minutes
111
+ SCREENER: 15 * 60_000, // 15 minutes
112
+ WEB_SEARCH: 3_600_000, // 1 hour
113
+ FINNHUB_NEWS: 3_600_000, // 1 hour
103
114
  } as const;
@@ -2,6 +2,7 @@ export interface HttpClientOptions {
2
2
  timeoutMs?: number;
3
3
  maxRetries?: number;
4
4
  retryDelayMs?: number;
5
+ maxRetryAfterMs?: number;
5
6
  headers?: Record<string, string>;
6
7
  }
7
8
 
@@ -9,6 +10,7 @@ const DEFAULT_OPTIONS: Required<HttpClientOptions> = {
9
10
  timeoutMs: 10_000,
10
11
  maxRetries: 2,
11
12
  retryDelayMs: 1_000,
13
+ maxRetryAfterMs: 5_000,
12
14
  headers: {},
13
15
  };
14
16
 
@@ -17,52 +19,116 @@ export class HttpError extends Error {
17
19
  public readonly status: number,
18
20
  public readonly statusText: string,
19
21
  public readonly body: string,
22
+ public readonly retryAfterMs?: number,
20
23
  ) {
21
24
  super(`HTTP ${status} ${statusText}`);
22
25
  this.name = "HttpError";
23
26
  }
24
27
  }
25
28
 
26
- export async function httpGet<T>(
29
+ export async function httpGet<T>(url: string, options: HttpClientOptions = {}): Promise<T> {
30
+ return httpRequest<T>(url, { ...options, method: "GET" });
31
+ }
32
+
33
+ export async function httpPost<T>(
27
34
  url: string,
35
+ body: unknown,
28
36
  options: HttpClientOptions = {},
29
37
  ): Promise<T> {
38
+ return httpRequest<T>(url, {
39
+ ...options,
40
+ method: "POST",
41
+ body: JSON.stringify(body),
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ ...options.headers,
45
+ },
46
+ });
47
+ }
48
+
49
+ interface HttpRequestOptions extends HttpClientOptions {
50
+ method: "GET" | "POST";
51
+ body?: string;
52
+ }
53
+
54
+ async function httpRequest<T>(url: string, options: HttpRequestOptions): Promise<T> {
30
55
  const opts = { ...DEFAULT_OPTIONS, ...options };
31
56
  let lastError: Error | undefined;
32
57
 
33
58
  for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
34
- if (attempt > 0) {
35
- await sleep(opts.retryDelayMs * attempt);
36
- }
59
+ let retryDelayMs: number | undefined;
37
60
 
38
61
  const controller = new AbortController();
39
62
  const timeout = setTimeout(() => controller.abort(), opts.timeoutMs);
40
63
 
41
64
  try {
42
65
  const response = await fetch(url, {
66
+ method: opts.method,
43
67
  signal: controller.signal,
44
68
  headers: opts.headers,
69
+ ...(opts.body !== undefined && { body: opts.body }),
45
70
  });
46
71
 
47
72
  if (!response.ok) {
48
73
  const body = await response.text().catch(() => "");
49
- throw new HttpError(response.status, response.statusText, body);
74
+ throw new HttpError(
75
+ response.status,
76
+ response.statusText,
77
+ body,
78
+ parseRetryAfterMs(response.headers?.get?.("retry-after") ?? null),
79
+ );
50
80
  }
51
81
 
52
82
  return (await response.json()) as T;
53
83
  } catch (error) {
54
84
  lastError = error as Error;
55
- if (error instanceof HttpError && error.status >= 400 && error.status < 500) {
85
+ if (!isRetryableError(error)) {
56
86
  throw error; // Don't retry client errors
57
87
  }
88
+ if (attempt < opts.maxRetries) {
89
+ retryDelayMs =
90
+ error instanceof HttpError && error.status === 429 && error.retryAfterMs !== undefined
91
+ ? capRetryAfterMs(error.retryAfterMs, opts.maxRetryAfterMs)
92
+ : opts.retryDelayMs * (attempt + 1);
93
+ }
58
94
  } finally {
59
95
  clearTimeout(timeout);
60
96
  }
97
+
98
+ if (retryDelayMs !== undefined) {
99
+ await sleep(retryDelayMs);
100
+ }
61
101
  }
62
102
 
63
103
  throw lastError!;
64
104
  }
65
105
 
106
+ function isRetryableError(error: unknown): boolean {
107
+ if (!(error instanceof HttpError)) return true;
108
+ if (error.status === 429) return true;
109
+ return error.status < 400 || error.status >= 500;
110
+ }
111
+
112
+ function parseRetryAfterMs(value: string | null): number | undefined {
113
+ if (!value) return undefined;
114
+ const trimmed = value.trim();
115
+ if (trimmed.length === 0) return undefined;
116
+
117
+ const seconds = Number(trimmed);
118
+ if (Number.isFinite(seconds) && seconds >= 0) {
119
+ return seconds * 1000;
120
+ }
121
+
122
+ const dateMs = Date.parse(trimmed);
123
+ if (Number.isNaN(dateMs)) return undefined;
124
+ return Math.max(0, dateMs - Date.now());
125
+ }
126
+
127
+ function capRetryAfterMs(retryAfterMs: number, maxRetryAfterMs: number | undefined): number {
128
+ if (maxRetryAfterMs === undefined) return retryAfterMs;
129
+ return Math.min(retryAfterMs, Math.max(0, maxRetryAfterMs));
130
+ }
131
+
66
132
  function sleep(ms: number): Promise<void> {
67
133
  return new Promise((resolve) => setTimeout(resolve, ms));
68
134
  }
@@ -1,18 +1,15 @@
1
- export { Cache, cache, TTL } from "./cache.js";
2
- export { RateLimiter, rateLimiter } from "./rate-limiter.js";
3
- export { httpGet, HttpError, type HttpClientOptions } from "./http-client.js";
4
1
  export { StealthBrowser } from "./browser.js";
2
+ export { Cache, cache, TTL } from "./cache.js";
3
+ export { type HttpClientOptions, HttpError, httpGet } from "./http-client.js";
5
4
  export {
6
- getOpenCandleHomeDir,
7
5
  ensureOpenCandleHomeDir,
8
- resolveOpenCandlePath,
9
6
  ensureParentDir,
10
- getWatchlistPath,
11
- getPortfolioPath,
12
- getPredictionsPath,
7
+ getBrowserProfileDir,
13
8
  getConfigPath,
9
+ getLogsDir,
14
10
  getOnboardingPath,
11
+ getOpenCandleHomeDir,
15
12
  getStateDbPath,
16
- getLogsDir,
17
- getBrowserProfileDir,
13
+ resolveOpenCandlePath,
18
14
  } from "./opencandle-paths.js";
15
+ export { RateLimiter, rateLimiter } from "./rate-limiter.js";
@@ -1,4 +1,7 @@
1
- export function getNativeDependencyErrorMessage(error: unknown, dependencyName: string): string | null {
1
+ export function getNativeDependencyErrorMessage(
2
+ error: unknown,
3
+ dependencyName: string,
4
+ ): string | null {
2
5
  const message = error instanceof Error ? error.message : String(error);
3
6
  if (
4
7
  !message.includes("NODE_MODULE_VERSION") &&
@@ -7,6 +10,8 @@ export function getNativeDependencyErrorMessage(error: unknown, dependencyName:
7
10
  return null;
8
11
  }
9
12
 
10
- return `${dependencyName} native binding was built for a different Node ABI than the active Node ${process.versions.node}. ` +
11
- `Run \`npm rebuild ${dependencyName}\` or reinstall dependencies under the active Node with \`npm install\`.`;
13
+ return (
14
+ `${dependencyName} native binding was built for a different Node ABI than the active Node ${process.versions.node}. ` +
15
+ `Run \`npm rebuild ${dependencyName}\` or reinstall dependencies under the active Node with \`npm install\`.`
16
+ );
12
17
  }
@@ -10,7 +10,9 @@ function isSupportedNodeVersion(version: string): boolean {
10
10
  return major >= 24 && major < 27;
11
11
  }
12
12
 
13
- export function getUnsupportedNodeVersionMessage(version: string = process.versions.node): string | null {
13
+ export function getUnsupportedNodeVersionMessage(
14
+ version: string = process.versions.node,
15
+ ): string | null {
14
16
  if (isSupportedNodeVersion(version)) return null;
15
17
 
16
18
  return `OpenCandle supports Node ${SUPPORTED_NODE_RANGE}. Current Node is ${version}. Use Node ${SUPPORTED_NODE_RANGE}; the repo default is Node 22.22.0 via \`nvm use\`. After switching Node versions, reinstall dependencies under the active Node with \`npm install\` or rebuild native modules with \`npm rebuild better-sqlite3\`.`;