opencandle 0.3.0 → 0.5.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 (408) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +106 -14
  3. package/assets/logo.svg +187 -0
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +40 -3
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +25 -0
  8. package/dist/config.js +72 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/infra/browser.d.ts +11 -3
  11. package/dist/infra/browser.js +2 -1
  12. package/dist/infra/browser.js.map +1 -1
  13. package/dist/infra/native-dependencies.d.ts +1 -0
  14. package/dist/infra/native-dependencies.js +10 -0
  15. package/dist/infra/native-dependencies.js.map +1 -0
  16. package/dist/infra/node-version.d.ts +2 -0
  17. package/dist/infra/node-version.js +23 -0
  18. package/dist/infra/node-version.js.map +1 -0
  19. package/dist/infra/rate-limiter.d.ts +4 -0
  20. package/dist/infra/rate-limiter.js +5 -1
  21. package/dist/infra/rate-limiter.js.map +1 -1
  22. package/dist/memory/index.d.ts +2 -0
  23. package/dist/memory/index.js +1 -0
  24. package/dist/memory/index.js.map +1 -1
  25. package/dist/memory/manager.d.ts +9 -0
  26. package/dist/memory/manager.js +28 -11
  27. package/dist/memory/manager.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 +7 -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/memory/types.js +4 -0
  37. package/dist/memory/types.js.map +1 -1
  38. package/dist/onboarding/connect.d.ts +13 -1
  39. package/dist/onboarding/connect.js +21 -10
  40. package/dist/onboarding/connect.js.map +1 -1
  41. package/dist/onboarding/prompt-user.d.ts +1 -1
  42. package/dist/onboarding/providers.d.ts +7 -0
  43. package/dist/onboarding/providers.js +6 -3
  44. package/dist/onboarding/providers.js.map +1 -1
  45. package/dist/onboarding/tool-helpers.d.ts +1 -1
  46. package/dist/pi/opencandle-extension.d.ts +7 -1
  47. package/dist/pi/opencandle-extension.js +391 -21
  48. package/dist/pi/opencandle-extension.js.map +1 -1
  49. package/dist/pi/session-storage.d.ts +2 -0
  50. package/dist/pi/session-storage.js +5 -0
  51. package/dist/pi/session-storage.js.map +1 -0
  52. package/dist/pi/session.d.ts +4 -1
  53. package/dist/pi/session.js +25 -3
  54. package/dist/pi/session.js.map +1 -1
  55. package/dist/pi/setup.d.ts +1 -1
  56. package/dist/pi/setup.js +11 -1
  57. package/dist/pi/setup.js.map +1 -1
  58. package/dist/pi/tool-adapter.d.ts +2 -2
  59. package/dist/pi/tool-adapter.js +14 -1
  60. package/dist/pi/tool-adapter.js.map +1 -1
  61. package/dist/prompts/context-builder.d.ts +40 -3
  62. package/dist/prompts/context-builder.js +140 -19
  63. package/dist/prompts/context-builder.js.map +1 -1
  64. package/dist/prompts/disclaimer.d.ts +6 -0
  65. package/dist/prompts/disclaimer.js +9 -0
  66. package/dist/prompts/disclaimer.js.map +1 -0
  67. package/dist/prompts/policy-cards.d.ts +13 -0
  68. package/dist/prompts/policy-cards.js +197 -0
  69. package/dist/prompts/policy-cards.js.map +1 -0
  70. package/dist/prompts/sections.js +3 -3
  71. package/dist/prompts/sections.js.map +1 -1
  72. package/dist/prompts/workflow-prompts.d.ts +8 -0
  73. package/dist/prompts/workflow-prompts.js +208 -22
  74. package/dist/prompts/workflow-prompts.js.map +1 -1
  75. package/dist/providers/alpha-vantage.js +23 -1
  76. package/dist/providers/alpha-vantage.js.map +1 -1
  77. package/dist/providers/sec-edgar.d.ts +8 -1
  78. package/dist/providers/sec-edgar.js +172 -5
  79. package/dist/providers/sec-edgar.js.map +1 -1
  80. package/dist/providers/yahoo-finance.d.ts +2 -0
  81. package/dist/providers/yahoo-finance.js +203 -35
  82. package/dist/providers/yahoo-finance.js.map +1 -1
  83. package/dist/routing/classify-intent.d.ts +3 -0
  84. package/dist/routing/classify-intent.js +82 -3
  85. package/dist/routing/classify-intent.js.map +1 -1
  86. package/dist/routing/defaults.js +4 -4
  87. package/dist/routing/defaults.js.map +1 -1
  88. package/dist/routing/entity-extractor.d.ts +1 -0
  89. package/dist/routing/entity-extractor.js +158 -12
  90. package/dist/routing/entity-extractor.js.map +1 -1
  91. package/dist/routing/index.d.ts +10 -0
  92. package/dist/routing/index.js +7 -0
  93. package/dist/routing/index.js.map +1 -1
  94. package/dist/routing/legacy-rule-router.d.ts +9 -0
  95. package/dist/routing/legacy-rule-router.js +12 -0
  96. package/dist/routing/legacy-rule-router.js.map +1 -0
  97. package/dist/routing/planning.d.ts +54 -0
  98. package/dist/routing/planning.js +531 -0
  99. package/dist/routing/planning.js.map +1 -0
  100. package/dist/routing/route-manifest.d.ts +35 -0
  101. package/dist/routing/route-manifest.js +221 -0
  102. package/dist/routing/route-manifest.js.map +1 -0
  103. package/dist/routing/router-llm-client.d.ts +11 -0
  104. package/dist/routing/router-llm-client.js +42 -0
  105. package/dist/routing/router-llm-client.js.map +1 -0
  106. package/dist/routing/router-prompt.d.ts +2 -0
  107. package/dist/routing/router-prompt.js +141 -0
  108. package/dist/routing/router-prompt.js.map +1 -0
  109. package/dist/routing/router-types.d.ts +71 -0
  110. package/dist/routing/router-types.js +2 -0
  111. package/dist/routing/router-types.js.map +1 -0
  112. package/dist/routing/router.d.ts +11 -0
  113. package/dist/routing/router.js +638 -0
  114. package/dist/routing/router.js.map +1 -0
  115. package/dist/routing/slot-resolver.js +46 -6
  116. package/dist/routing/slot-resolver.js.map +1 -1
  117. package/dist/routing/turn-context.d.ts +44 -0
  118. package/dist/routing/turn-context.js +45 -0
  119. package/dist/routing/turn-context.js.map +1 -0
  120. package/dist/routing/types.d.ts +13 -1
  121. package/dist/runtime/answer-contracts.d.ts +82 -0
  122. package/dist/runtime/answer-contracts.js +414 -0
  123. package/dist/runtime/answer-contracts.js.map +1 -0
  124. package/dist/runtime/artifact-contracts.d.ts +14 -0
  125. package/dist/runtime/artifact-contracts.js +57 -0
  126. package/dist/runtime/artifact-contracts.js.map +1 -0
  127. package/dist/runtime/planning-evidence.d.ts +99 -0
  128. package/dist/runtime/planning-evidence.js +445 -0
  129. package/dist/runtime/planning-evidence.js.map +1 -0
  130. package/dist/runtime/session-coordinator.d.ts +81 -3
  131. package/dist/runtime/session-coordinator.js +201 -17
  132. package/dist/runtime/session-coordinator.js.map +1 -1
  133. package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
  134. package/dist/runtime/tool-defaults-wrapper.js +25 -0
  135. package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
  136. package/dist/sentiment/store.js +5 -0
  137. package/dist/sentiment/store.js.map +1 -1
  138. package/dist/system-prompt.js +23 -12
  139. package/dist/system-prompt.js.map +1 -1
  140. package/dist/tool-kit.d.ts +4 -4
  141. package/dist/tools/fundamentals/company-overview.d.ts +1 -1
  142. package/dist/tools/fundamentals/company-overview.js +1 -1
  143. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  144. package/dist/tools/fundamentals/comps.d.ts +1 -1
  145. package/dist/tools/fundamentals/comps.js +1 -1
  146. package/dist/tools/fundamentals/comps.js.map +1 -1
  147. package/dist/tools/fundamentals/dcf.d.ts +1 -1
  148. package/dist/tools/fundamentals/dcf.js +1 -1
  149. package/dist/tools/fundamentals/dcf.js.map +1 -1
  150. package/dist/tools/fundamentals/earnings.d.ts +1 -1
  151. package/dist/tools/fundamentals/earnings.js +1 -1
  152. package/dist/tools/fundamentals/earnings.js.map +1 -1
  153. package/dist/tools/fundamentals/financials.d.ts +1 -1
  154. package/dist/tools/fundamentals/financials.js +1 -1
  155. package/dist/tools/fundamentals/financials.js.map +1 -1
  156. package/dist/tools/fundamentals/sec-filings.d.ts +2 -1
  157. package/dist/tools/fundamentals/sec-filings.js +19 -2
  158. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  159. package/dist/tools/index.d.ts +29 -1
  160. package/dist/tools/index.js +30 -0
  161. package/dist/tools/index.js.map +1 -1
  162. package/dist/tools/interaction/ask-user.d.ts +1 -1
  163. package/dist/tools/interaction/twitter-login.d.ts +1 -1
  164. package/dist/tools/macro/fear-greed.d.ts +1 -1
  165. package/dist/tools/macro/fear-greed.js +1 -1
  166. package/dist/tools/macro/fear-greed.js.map +1 -1
  167. package/dist/tools/macro/fred-data.d.ts +1 -1
  168. package/dist/tools/macro/fred-data.js +29 -5
  169. package/dist/tools/macro/fred-data.js.map +1 -1
  170. package/dist/tools/market/crypto-history.d.ts +1 -1
  171. package/dist/tools/market/crypto-history.js +18 -2
  172. package/dist/tools/market/crypto-history.js.map +1 -1
  173. package/dist/tools/market/crypto-price.d.ts +1 -1
  174. package/dist/tools/market/crypto-price.js +1 -1
  175. package/dist/tools/market/crypto-price.js.map +1 -1
  176. package/dist/tools/market/search-ticker.d.ts +1 -1
  177. package/dist/tools/market/search-ticker.js +1 -1
  178. package/dist/tools/market/search-ticker.js.map +1 -1
  179. package/dist/tools/market/stock-history.d.ts +1 -1
  180. package/dist/tools/market/stock-history.js +1 -1
  181. package/dist/tools/market/stock-history.js.map +1 -1
  182. package/dist/tools/market/stock-quote.d.ts +1 -1
  183. package/dist/tools/market/stock-quote.js +1 -1
  184. package/dist/tools/market/stock-quote.js.map +1 -1
  185. package/dist/tools/options/greeks.js +0 -1
  186. package/dist/tools/options/greeks.js.map +1 -1
  187. package/dist/tools/options/option-chain.d.ts +1 -1
  188. package/dist/tools/options/option-chain.js +13 -5
  189. package/dist/tools/options/option-chain.js.map +1 -1
  190. package/dist/tools/portfolio/correlation.d.ts +1 -1
  191. package/dist/tools/portfolio/correlation.js +1 -1
  192. package/dist/tools/portfolio/correlation.js.map +1 -1
  193. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  194. package/dist/tools/portfolio/holdings-overlap.js +105 -0
  195. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  196. package/dist/tools/portfolio/predictions.d.ts +1 -1
  197. package/dist/tools/portfolio/predictions.js +1 -1
  198. package/dist/tools/portfolio/predictions.js.map +1 -1
  199. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  200. package/dist/tools/portfolio/risk-analysis.js +1 -1
  201. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  202. package/dist/tools/portfolio/tracker.d.ts +1 -1
  203. package/dist/tools/portfolio/tracker.js +1 -1
  204. package/dist/tools/portfolio/tracker.js.map +1 -1
  205. package/dist/tools/portfolio/watchlist.d.ts +1 -1
  206. package/dist/tools/portfolio/watchlist.js +12 -4
  207. package/dist/tools/portfolio/watchlist.js.map +1 -1
  208. package/dist/tools/sentiment/reddit-sentiment.d.ts +1 -1
  209. package/dist/tools/sentiment/reddit-sentiment.js +1 -1
  210. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  211. package/dist/tools/sentiment/sentiment-summary.d.ts +1 -1
  212. package/dist/tools/sentiment/sentiment-summary.js +57 -2
  213. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  214. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  215. package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
  216. package/dist/tools/sentiment/twitter-sentiment.js +1 -1
  217. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  218. package/dist/tools/sentiment/web-search.d.ts +1 -1
  219. package/dist/tools/sentiment/web-search.js +32 -3
  220. package/dist/tools/sentiment/web-search.js.map +1 -1
  221. package/dist/tools/sentiment/web-sentiment.d.ts +1 -1
  222. package/dist/tools/sentiment/web-sentiment.js +1 -1
  223. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  224. package/dist/tools/technical/backtest.d.ts +3 -3
  225. package/dist/tools/technical/backtest.js +41 -27
  226. package/dist/tools/technical/backtest.js.map +1 -1
  227. package/dist/tools/technical/indicators.d.ts +1 -1
  228. package/dist/tools/technical/indicators.js +8 -4
  229. package/dist/tools/technical/indicators.js.map +1 -1
  230. package/dist/types/options.d.ts +10 -0
  231. package/dist/types/portfolio.d.ts +27 -0
  232. package/dist/workflows/compare-assets.js +38 -2
  233. package/dist/workflows/compare-assets.js.map +1 -1
  234. package/dist/workflows/options-screener.js +94 -8
  235. package/dist/workflows/options-screener.js.map +1 -1
  236. package/dist/workflows/portfolio-builder.js +9 -5
  237. package/dist/workflows/portfolio-builder.js.map +1 -1
  238. package/gui/server/ask-user-bridge.ts +82 -0
  239. package/gui/server/background-quotes.ts +31 -0
  240. package/gui/server/chat-event-adapter.ts +142 -0
  241. package/gui/server/gui-session-manager.ts +5 -0
  242. package/gui/server/invoke-tool.ts +89 -0
  243. package/gui/server/live-chat-event-adapter.ts +181 -0
  244. package/gui/server/model-setup.ts +100 -0
  245. package/gui/server/package.json +5 -0
  246. package/gui/server/projector.ts +254 -0
  247. package/gui/server/prompt-observation.ts +61 -0
  248. package/gui/server/server.ts +703 -0
  249. package/gui/server/session-actions.ts +31 -0
  250. package/gui/server/session-entry-wait.ts +81 -0
  251. package/gui/server/tool-metadata.ts +88 -0
  252. package/gui/server/websocket.ts +128 -0
  253. package/gui/server/writer-lock.ts +118 -0
  254. package/gui/shared/chat-events.ts +118 -0
  255. package/gui/shared/event-reducer.ts +186 -0
  256. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +1 -0
  257. package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
  258. package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
  259. package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
  260. package/gui/web/dist/index.html +17 -0
  261. package/package.json +50 -18
  262. package/src/analysts/contracts.ts +189 -0
  263. package/src/analysts/orchestrator.ts +300 -0
  264. package/src/cli.ts +206 -0
  265. package/src/config.ts +245 -0
  266. package/src/index.ts +5 -0
  267. package/src/infra/browser.ts +111 -0
  268. package/src/infra/cache.ts +103 -0
  269. package/src/infra/http-client.ts +68 -0
  270. package/src/infra/index.ts +18 -0
  271. package/src/infra/native-dependencies.ts +12 -0
  272. package/src/infra/node-version.ts +24 -0
  273. package/src/infra/open-url.ts +28 -0
  274. package/src/infra/opencandle-paths.ts +64 -0
  275. package/src/infra/rate-limiter.ts +73 -0
  276. package/src/memory/index.ts +10 -0
  277. package/src/memory/manager.ts +192 -0
  278. package/src/memory/preference-extractor.ts +106 -0
  279. package/src/memory/retrieval.ts +70 -0
  280. package/src/memory/sqlite.ts +172 -0
  281. package/src/memory/storage.ts +205 -0
  282. package/src/memory/tool-defaults.ts +87 -0
  283. package/src/memory/types.ts +71 -0
  284. package/src/onboarding/connect.ts +184 -0
  285. package/src/onboarding/credential-interceptor.ts +134 -0
  286. package/src/onboarding/degradation-accumulator.ts +79 -0
  287. package/src/onboarding/prompt-user.ts +85 -0
  288. package/src/onboarding/providers.ts +315 -0
  289. package/src/onboarding/state.ts +218 -0
  290. package/src/onboarding/tool-helpers.ts +111 -0
  291. package/src/onboarding/tool-tags.ts +201 -0
  292. package/src/onboarding/validation.ts +158 -0
  293. package/src/pi/opencandle-extension.ts +955 -0
  294. package/src/pi/session-storage.ts +5 -0
  295. package/src/pi/session.ts +81 -0
  296. package/src/pi/setup.ts +381 -0
  297. package/src/pi/tool-adapter.ts +36 -0
  298. package/src/prompts/context-builder.ts +315 -0
  299. package/src/prompts/disclaimer.ts +9 -0
  300. package/src/prompts/policy-cards.ts +220 -0
  301. package/src/prompts/sections.ts +46 -0
  302. package/src/prompts/workflow-prompts.ts +433 -0
  303. package/src/providers/alpha-vantage.ts +315 -0
  304. package/src/providers/coingecko.ts +96 -0
  305. package/src/providers/exa-search.ts +373 -0
  306. package/src/providers/fear-greed.ts +45 -0
  307. package/src/providers/finnhub.ts +124 -0
  308. package/src/providers/fred.ts +83 -0
  309. package/src/providers/index.ts +9 -0
  310. package/src/providers/provider-credential-error.ts +23 -0
  311. package/src/providers/reddit.ts +151 -0
  312. package/src/providers/sec-edgar.ts +312 -0
  313. package/src/providers/twitter.ts +173 -0
  314. package/src/providers/web-search.ts +293 -0
  315. package/src/providers/with-fallback.ts +41 -0
  316. package/src/providers/wrap-provider.ts +64 -0
  317. package/src/providers/yahoo-finance.ts +534 -0
  318. package/src/routing/classify-intent.ts +285 -0
  319. package/src/routing/defaults.ts +29 -0
  320. package/src/routing/entity-extractor.ts +291 -0
  321. package/src/routing/index.ts +70 -0
  322. package/src/routing/legacy-rule-router.ts +13 -0
  323. package/src/routing/planning.ts +732 -0
  324. package/src/routing/route-manifest.ts +287 -0
  325. package/src/routing/router-llm-client.ts +51 -0
  326. package/src/routing/router-prompt.ts +163 -0
  327. package/src/routing/router-types.ts +87 -0
  328. package/src/routing/router.ts +712 -0
  329. package/src/routing/slot-resolver.ts +190 -0
  330. package/src/routing/turn-context.ts +111 -0
  331. package/src/routing/types.ts +75 -0
  332. package/src/runtime/answer-contracts.ts +633 -0
  333. package/src/runtime/artifact-contracts.ts +76 -0
  334. package/src/runtime/evidence.ts +77 -0
  335. package/src/runtime/planning-evidence.ts +591 -0
  336. package/src/runtime/prompt-step.ts +75 -0
  337. package/src/runtime/provider-tracker.ts +40 -0
  338. package/src/runtime/run-context.ts +22 -0
  339. package/src/runtime/session-coordinator.ts +472 -0
  340. package/src/runtime/tool-defaults-wrapper.ts +35 -0
  341. package/src/runtime/validation.ts +214 -0
  342. package/src/runtime/workflow-events.ts +75 -0
  343. package/src/runtime/workflow-runner.ts +188 -0
  344. package/src/runtime/workflow-types.ts +102 -0
  345. package/src/sentiment/adapters/finnhub.ts +44 -0
  346. package/src/sentiment/adapters/reddit.ts +65 -0
  347. package/src/sentiment/adapters/twitter.ts +36 -0
  348. package/src/sentiment/adapters/web.ts +44 -0
  349. package/src/sentiment/index.ts +58 -0
  350. package/src/sentiment/keywords.ts +9 -0
  351. package/src/sentiment/pipeline.ts +68 -0
  352. package/src/sentiment/scorer.ts +78 -0
  353. package/src/sentiment/store.ts +260 -0
  354. package/src/sentiment/trends.ts +90 -0
  355. package/src/sentiment/types.ts +108 -0
  356. package/src/system-prompt.ts +118 -0
  357. package/src/tool-kit.ts +68 -0
  358. package/src/tools/AGENTS.md +36 -0
  359. package/src/tools/fundamentals/company-overview.ts +54 -0
  360. package/src/tools/fundamentals/comps.ts +156 -0
  361. package/src/tools/fundamentals/dcf.ts +267 -0
  362. package/src/tools/fundamentals/earnings.ts +47 -0
  363. package/src/tools/fundamentals/financials.ts +54 -0
  364. package/src/tools/fundamentals/sec-filings.ts +84 -0
  365. package/src/tools/index.ts +91 -0
  366. package/src/tools/interaction/ask-user.ts +81 -0
  367. package/src/tools/interaction/twitter-login.ts +93 -0
  368. package/src/tools/macro/fear-greed.ts +41 -0
  369. package/src/tools/macro/fred-data.ts +80 -0
  370. package/src/tools/market/crypto-history.ts +67 -0
  371. package/src/tools/market/crypto-price.ts +53 -0
  372. package/src/tools/market/search-ticker.ts +53 -0
  373. package/src/tools/market/stock-history.ts +79 -0
  374. package/src/tools/market/stock-quote.ts +64 -0
  375. package/src/tools/options/greeks.ts +81 -0
  376. package/src/tools/options/option-chain.ts +96 -0
  377. package/src/tools/portfolio/correlation.ts +162 -0
  378. package/src/tools/portfolio/holdings-overlap.ts +123 -0
  379. package/src/tools/portfolio/predictions.ts +253 -0
  380. package/src/tools/portfolio/risk-analysis.ts +134 -0
  381. package/src/tools/portfolio/tracker.ts +147 -0
  382. package/src/tools/portfolio/watchlist.ts +159 -0
  383. package/src/tools/sentiment/reddit-sentiment.ts +164 -0
  384. package/src/tools/sentiment/sentiment-summary.ts +316 -0
  385. package/src/tools/sentiment/sentiment-trend.ts +58 -0
  386. package/src/tools/sentiment/twitter-sentiment.ts +96 -0
  387. package/src/tools/sentiment/web-search.ts +183 -0
  388. package/src/tools/sentiment/web-sentiment.ts +76 -0
  389. package/src/tools/technical/backtest.ts +267 -0
  390. package/src/tools/technical/indicators.ts +256 -0
  391. package/src/types/fundamentals.ts +46 -0
  392. package/src/types/index.ts +20 -0
  393. package/src/types/macro.ts +27 -0
  394. package/src/types/market.ts +43 -0
  395. package/src/types/options.ts +52 -0
  396. package/src/types/portfolio.ts +73 -0
  397. package/src/types/sentiment.ts +70 -0
  398. package/src/workflows/compare-assets.ts +75 -0
  399. package/src/workflows/index.ts +4 -0
  400. package/src/workflows/options-screener.ts +127 -0
  401. package/src/workflows/portfolio-builder.ts +56 -0
  402. package/src/workflows/types.ts +4 -0
  403. package/dist/runtime/index.d.ts +0 -16
  404. package/dist/runtime/index.js +0 -10
  405. package/dist/runtime/index.js.map +0 -1
  406. package/dist/runtime/provider-ids.d.ts +0 -14
  407. package/dist/runtime/provider-ids.js +0 -14
  408. package/dist/runtime/provider-ids.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ interface BucketConfig {
2
+ maxTokens: number;
3
+ refillRate: number; // tokens per second
4
+ }
5
+
6
+ interface Bucket {
7
+ tokens: number;
8
+ lastRefill: number;
9
+ config: BucketConfig;
10
+ }
11
+
12
+ export const ALPHA_VANTAGE_RATE_LIMIT = {
13
+ maxTokens: 5,
14
+ refillRate: 0.083,
15
+ } as const;
16
+
17
+ export class RateLimiter {
18
+ private buckets = new Map<string, Bucket>();
19
+
20
+ configure(provider: string, maxTokens: number, refillRate: number): void {
21
+ this.buckets.set(provider, {
22
+ tokens: maxTokens,
23
+ lastRefill: Date.now(),
24
+ config: { maxTokens, refillRate },
25
+ });
26
+ }
27
+
28
+ async acquire(provider: string): Promise<void> {
29
+ const bucket = this.buckets.get(provider);
30
+ if (!bucket) return; // No limit configured
31
+
32
+ this.refill(bucket);
33
+
34
+ if (bucket.tokens >= 1) {
35
+ bucket.tokens -= 1;
36
+ return;
37
+ }
38
+
39
+ // Wait until a token is available
40
+ const waitMs = ((1 - bucket.tokens) / bucket.config.refillRate) * 1000;
41
+ await new Promise((resolve) => setTimeout(resolve, Math.ceil(waitMs)));
42
+ this.refill(bucket);
43
+ bucket.tokens -= 1;
44
+ }
45
+
46
+ private refill(bucket: Bucket): void {
47
+ const now = Date.now();
48
+ const elapsed = (now - bucket.lastRefill) / 1000;
49
+ bucket.tokens = Math.min(
50
+ bucket.config.maxTokens,
51
+ bucket.tokens + elapsed * bucket.config.refillRate,
52
+ );
53
+ bucket.lastRefill = now;
54
+ }
55
+ }
56
+
57
+ // Shared instance with default provider limits
58
+ export const rateLimiter = new RateLimiter();
59
+ rateLimiter.configure("yahoo", 5, 5); // 5 req/s
60
+ rateLimiter.configure("coingecko", 10, 0.167); // 10 req/min
61
+ rateLimiter.configure(
62
+ "alphavantage",
63
+ ALPHA_VANTAGE_RATE_LIMIT.maxTokens,
64
+ ALPHA_VANTAGE_RATE_LIMIT.refillRate,
65
+ ); // 5 req/min (free tier)
66
+ rateLimiter.configure("fred", 120, 2); // 120 req/min
67
+ rateLimiter.configure("twitter", 5, 0.167); // 5 req, ~10 req/min
68
+ rateLimiter.configure("reddit", 5, 0.167); // 5 req, ~10 req/min
69
+ rateLimiter.configure("reddit_comments", 10, 0.5); // 10 req, ~30 req/min
70
+ rateLimiter.configure("ddg", 3, 0.1); // 3 req, ~6 req/min
71
+ rateLimiter.configure("brave_search", 5, 0.083); // 5 req, ~5 req/min
72
+ rateLimiter.configure("exa", 5, 0.1); // 5 req, ~6 req/min
73
+ rateLimiter.configure("finnhub", 60, 1); // 60 req/min (free tier)
@@ -0,0 +1,10 @@
1
+ export { initDatabase, initDefaultDatabase, getTableNames, getSchemaVersion } from "./sqlite.js";
2
+ export { MemoryStorage } from "./storage.js";
3
+ export type { WorkflowPreferences } from "./storage.js";
4
+ export { buildMemoryContext } from "./retrieval.js";
5
+ export { extractPreferences } from "./preference-extractor.js";
6
+ export { MemoryManager } from "./manager.js";
7
+ export { getAllDefaults, getDefaults, setDefault, clearDefault } from "./tool-defaults.js";
8
+ export type { ToolDefaults } from "./tool-defaults.js";
9
+ export type { MemoryCategory, MemoryEntry } from "./types.js";
10
+ export { isStale, STALENESS_THRESHOLDS, NEVER_TRUST_FROM_MEMORY } from "./types.js";
@@ -0,0 +1,192 @@
1
+ import type { MemoryStorage } from "./storage.js";
2
+ import type { MemoryCategory, MemoryEntry } from "./types.js";
3
+ import {
4
+ KEY_TO_CATEGORY,
5
+ WORKFLOW_RELEVANT_CATEGORIES,
6
+ NEVER_TRUST_FROM_MEMORY,
7
+ isStale,
8
+ } from "./types.js";
9
+
10
+ export interface FilteredMemoryEntry {
11
+ entry: MemoryEntry;
12
+ reason: "suppressed_by_user_slot" | "never_trust" | "stale" | "irrelevant_category";
13
+ }
14
+
15
+ export interface MemoryRetrievalResult {
16
+ entries: MemoryEntry[];
17
+ filtered: FilteredMemoryEntry[];
18
+ }
19
+
20
+ /** Slot name → preference key(s) mapping for suppression. */
21
+ const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
22
+ riskProfile: ["risk_profile"],
23
+ assetScope: ["asset_scope"],
24
+ timeHorizon: ["time_horizon"],
25
+ dteTarget: ["dte_target"],
26
+ moneynessPreference: ["moneyness_preference"],
27
+ liquidityMinimum: ["liquidity_minimum", "options_liquidity"],
28
+ };
29
+
30
+ const MAX_WORKFLOW_HISTORY_PER_TYPE = 3;
31
+ const MAX_PREFERENCE_LINES = 15;
32
+
33
+ /**
34
+ * Selective, typed memory retrieval with staleness rules
35
+ * and override suppression.
36
+ */
37
+ export class MemoryManager {
38
+ constructor(private readonly storage: MemoryStorage) {}
39
+
40
+ /**
41
+ * Retrieve memory entries relevant to the given workflow type,
42
+ * filtering by category, staleness, and overrides.
43
+ */
44
+ retrieve(
45
+ workflowType: string,
46
+ overriddenSlots?: string[],
47
+ now: Date = new Date(),
48
+ ): MemoryEntry[] {
49
+ return this.retrieveDetailed(workflowType, overriddenSlots, now).entries;
50
+ }
51
+
52
+ retrieveDetailed(
53
+ workflowType: string,
54
+ overriddenSlots?: string[],
55
+ now: Date = new Date(),
56
+ ): MemoryRetrievalResult {
57
+ const relevantCategories = WORKFLOW_RELEVANT_CATEGORIES[workflowType] ??
58
+ WORKFLOW_RELEVANT_CATEGORIES["unclassified"];
59
+
60
+ // Build set of preference keys to suppress
61
+ const suppressedKeys = new Set<string>();
62
+ if (overriddenSlots) {
63
+ for (const slot of overriddenSlots) {
64
+ const keys = SLOT_TO_PREF_KEYS[slot];
65
+ if (keys) keys.forEach((k) => suppressedKeys.add(k));
66
+ }
67
+ }
68
+
69
+ const entries: MemoryEntry[] = [];
70
+ const filtered: FilteredMemoryEntry[] = [];
71
+
72
+ // Preferences as investor_profile entries
73
+ if (relevantCategories.includes("investor_profile")) {
74
+ const prefs = this.storage.getPreferencesByNamespace("global");
75
+ for (const pref of prefs) {
76
+ const key = String(pref.key);
77
+ const category = KEY_TO_CATEGORY[key] ?? "investor_profile";
78
+ const entry: MemoryEntry = {
79
+ key,
80
+ value: tryParseValue(String(pref.value_json ?? "")),
81
+ category,
82
+ recordedAt: String(pref.updated_at ?? pref.created_at ?? now.toISOString()),
83
+ confidence: pref.confidence != null ? String(pref.confidence) : undefined,
84
+ source: pref.source != null ? String(pref.source) : undefined,
85
+ };
86
+
87
+ if (suppressedKeys.has(key)) {
88
+ filtered.push({ entry, reason: "suppressed_by_user_slot" });
89
+ continue;
90
+ }
91
+ if (NEVER_TRUST_FROM_MEMORY.has(key)) {
92
+ filtered.push({ entry, reason: "never_trust" });
93
+ continue;
94
+ }
95
+ if (!relevantCategories.includes(category)) {
96
+ filtered.push({ entry, reason: "irrelevant_category" });
97
+ continue;
98
+ }
99
+ if (isStale(entry, now)) {
100
+ filtered.push({ entry, reason: "stale" });
101
+ continue;
102
+ }
103
+ entries.push(entry);
104
+ }
105
+ }
106
+
107
+ // Workflow history
108
+ if (relevantCategories.includes("workflow_history")) {
109
+ const runs = this.storage.getRecentWorkflowRuns(MAX_WORKFLOW_HISTORY_PER_TYPE * 4);
110
+ const countsByType = new Map<string, number>();
111
+
112
+ for (const run of runs) {
113
+ const wfType = String(run.workflow_type);
114
+ const count = countsByType.get(wfType) ?? 0;
115
+ if (count >= MAX_WORKFLOW_HISTORY_PER_TYPE) continue;
116
+ countsByType.set(wfType, count + 1);
117
+
118
+ const recordedAt = String(run.created_at ?? now.toISOString());
119
+ const entry: MemoryEntry = {
120
+ key: `workflow_run_${run.id}`,
121
+ value: run.output_summary
122
+ ? `${wfType}: ${run.output_summary}`
123
+ : wfType,
124
+ category: "workflow_history",
125
+ recordedAt,
126
+ };
127
+
128
+ if (isStale(entry, now)) {
129
+ filtered.push({ entry, reason: "stale" });
130
+ continue;
131
+ }
132
+ entries.push(entry);
133
+ }
134
+ }
135
+
136
+ return {
137
+ entries: entries.slice(0, MAX_PREFERENCE_LINES + MAX_WORKFLOW_HISTORY_PER_TYPE * 4),
138
+ filtered,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Build compact text context from retrieved memory entries.
144
+ */
145
+ buildContext(
146
+ workflowType: string,
147
+ overriddenSlots?: string[],
148
+ now: Date = new Date(),
149
+ ): string {
150
+ const entries = this.retrieve(workflowType, overriddenSlots, now);
151
+ if (entries.length === 0) return "";
152
+
153
+ const sections: string[] = [];
154
+
155
+ // Group by category
156
+ const byCategory = new Map<MemoryCategory, MemoryEntry[]>();
157
+ for (const entry of entries) {
158
+ const list = byCategory.get(entry.category) ?? [];
159
+ list.push(entry);
160
+ byCategory.set(entry.category, list);
161
+ }
162
+
163
+ const profileEntries = byCategory.get("investor_profile");
164
+ if (profileEntries && profileEntries.length > 0) {
165
+ const lines = profileEntries.map((e) => `- ${e.key}: ${e.value}`);
166
+ sections.push("User Preferences:\n" + lines.join("\n"));
167
+ }
168
+
169
+ const historyEntries = byCategory.get("workflow_history");
170
+ if (historyEntries && historyEntries.length > 0) {
171
+ const lines = historyEntries.map((e) => `- ${e.value} (${e.recordedAt})`);
172
+ sections.push("Recent Workflows:\n" + lines.join("\n"));
173
+ }
174
+
175
+ const feedbackEntries = byCategory.get("interaction_feedback");
176
+ if (feedbackEntries && feedbackEntries.length > 0) {
177
+ const lines = feedbackEntries.map((e) => `- ${e.key}: ${e.value}`);
178
+ sections.push("Feedback:\n" + lines.join("\n"));
179
+ }
180
+
181
+ return sections.join("\n\n");
182
+ }
183
+ }
184
+
185
+ function tryParseValue(json: string): string {
186
+ try {
187
+ const parsed = JSON.parse(json);
188
+ return typeof parsed === "string" ? parsed : JSON.stringify(parsed);
189
+ } catch {
190
+ return json;
191
+ }
192
+ }
@@ -0,0 +1,106 @@
1
+ export interface ExtractedPreference {
2
+ key: string;
3
+ value: string;
4
+ confidence: "high" | "medium" | "low";
5
+ }
6
+
7
+ interface PatternRule {
8
+ pattern: RegExp;
9
+ key: string;
10
+ value: string;
11
+ confidence: "high" | "medium" | "low";
12
+ }
13
+
14
+ const PREFERENCE_PATTERNS: PatternRule[] = [
15
+ // Asset scope
16
+ {
17
+ pattern: /\bprefer\s+etfs?\b/i,
18
+ key: "asset_scope",
19
+ value: "etf_focused",
20
+ confidence: "high",
21
+ },
22
+ {
23
+ pattern: /\betf[\s-]*(?:heavy|focused|only)\b/i,
24
+ key: "asset_scope",
25
+ value: "etf_focused",
26
+ confidence: "high",
27
+ },
28
+
29
+ // Risk profile
30
+ {
31
+ pattern: /\b(?:i'?m|i\s+am)\s+conservative\b/i,
32
+ key: "risk_profile",
33
+ value: "conservative",
34
+ confidence: "high",
35
+ },
36
+ {
37
+ pattern: /\brisk\s*averse\b/i,
38
+ key: "risk_profile",
39
+ value: "conservative",
40
+ confidence: "high",
41
+ },
42
+ {
43
+ pattern: /\b(?:i'?m|i\s+am)\s+aggressive\b/i,
44
+ key: "risk_profile",
45
+ value: "aggressive",
46
+ confidence: "high",
47
+ },
48
+ {
49
+ pattern: /\baggressive\s+growth\b/i,
50
+ key: "risk_profile",
51
+ value: "aggressive",
52
+ confidence: "high",
53
+ },
54
+ {
55
+ pattern: /\b(?:i'?m|i\s+am)\s+(?:moderate|balanced)\b/i,
56
+ key: "risk_profile",
57
+ value: "balanced",
58
+ confidence: "high",
59
+ },
60
+
61
+ // Time horizon
62
+ {
63
+ pattern: /\b(?:12\s*month|one\s*year|1\s*year)\s*horizon/i,
64
+ key: "time_horizon",
65
+ value: "1y_plus",
66
+ confidence: "high",
67
+ },
68
+ {
69
+ pattern: /\blong[\s-]*term\s+(?:invest|hold|horizon)/i,
70
+ key: "time_horizon",
71
+ value: "long",
72
+ confidence: "medium",
73
+ },
74
+ {
75
+ pattern: /\bshort[\s-]*term\s+(?:trad|invest|horizon)/i,
76
+ key: "time_horizon",
77
+ value: "short",
78
+ confidence: "medium",
79
+ },
80
+
81
+ // Options liquidity
82
+ {
83
+ pattern: /\b(?:only|prefer)\s+(?:trade\s+)?liquid\s+options?\b/i,
84
+ key: "options_liquidity",
85
+ value: "high",
86
+ confidence: "high",
87
+ },
88
+ ];
89
+
90
+ export function extractPreferences(input: string): ExtractedPreference[] {
91
+ const preferences: ExtractedPreference[] = [];
92
+ const seenKeys = new Set<string>();
93
+
94
+ for (const rule of PREFERENCE_PATTERNS) {
95
+ if (rule.pattern.test(input) && !seenKeys.has(rule.key)) {
96
+ preferences.push({
97
+ key: rule.key,
98
+ value: rule.value,
99
+ confidence: rule.confidence,
100
+ });
101
+ seenKeys.add(rule.key);
102
+ }
103
+ }
104
+
105
+ return preferences;
106
+ }
@@ -0,0 +1,70 @@
1
+ import type { MemoryStorage } from "./storage.js";
2
+
3
+ const MAX_PREFERENCE_LINES = 15;
4
+ const MAX_WORKFLOW_SUMMARY_LINES = 12;
5
+
6
+ /** Slot name → preference key(s) mapping for suppression. */
7
+ const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
8
+ riskProfile: ["risk_profile"],
9
+ assetScope: ["asset_scope"],
10
+ timeHorizon: ["time_horizon"],
11
+ dteTarget: ["dte_target"],
12
+ moneynessPreference: ["moneyness_preference"],
13
+ liquidityMinimum: ["liquidity_minimum", "options_liquidity"],
14
+ };
15
+
16
+ /**
17
+ * Build compact memory context for agent injection.
18
+ * @param overriddenSlots Slot names whose values were overridden by current-turn user input.
19
+ * The corresponding preference keys will be excluded from memory context to avoid
20
+ * conflicting provenance signals.
21
+ */
22
+ export function buildMemoryContext(
23
+ storage: MemoryStorage,
24
+ overriddenSlots?: string[],
25
+ ): string {
26
+ const sections: string[] = [];
27
+
28
+ // Build set of preference keys to suppress
29
+ const suppressedKeys = new Set<string>();
30
+ if (overriddenSlots) {
31
+ for (const slot of overriddenSlots) {
32
+ const keys = SLOT_TO_PREF_KEYS[slot];
33
+ if (keys) keys.forEach((k) => suppressedKeys.add(k));
34
+ }
35
+ }
36
+
37
+ // Preferences
38
+ const prefs = storage.getPreferencesByNamespace("global");
39
+ if (prefs.length > 0) {
40
+ const filtered = prefs.filter((p) => !suppressedKeys.has(String(p.key)));
41
+ if (filtered.length > 0) {
42
+ const lines = filtered.slice(0, MAX_PREFERENCE_LINES).map((p) => {
43
+ const value = tryParseJson(p.value_json as string);
44
+ return `- ${p.key}: ${value}`;
45
+ });
46
+ sections.push("User Preferences:\n" + lines.join("\n"));
47
+ }
48
+ }
49
+
50
+ // Recent workflow runs
51
+ const runs = storage.getRecentWorkflowRuns(3);
52
+ if (runs.length > 0) {
53
+ const lines = runs.slice(0, MAX_WORKFLOW_SUMMARY_LINES).map((r) => {
54
+ const summary = r.output_summary ? ` — ${r.output_summary}` : "";
55
+ return `- ${r.workflow_type} (${r.created_at})${summary}`;
56
+ });
57
+ sections.push("Recent Workflows:\n" + lines.join("\n"));
58
+ }
59
+
60
+ return sections.join("\n\n");
61
+ }
62
+
63
+ function tryParseJson(json: string): string {
64
+ try {
65
+ const parsed = JSON.parse(json);
66
+ return typeof parsed === "string" ? parsed : JSON.stringify(parsed);
67
+ } catch {
68
+ return json;
69
+ }
70
+ }
@@ -0,0 +1,172 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import Database from "better-sqlite3";
4
+ import { getStateDbPath } from "../infra/opencandle-paths.js";
5
+
6
+ const CURRENT_SCHEMA_VERSION = 4;
7
+
8
+ const CURRENT_SCHEMA = `
9
+ CREATE TABLE IF NOT EXISTS schema_version (
10
+ version INTEGER NOT NULL
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS user_preferences (
14
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
15
+ namespace TEXT NOT NULL DEFAULT 'global',
16
+ key TEXT NOT NULL,
17
+ value_json TEXT NOT NULL,
18
+ confidence TEXT DEFAULT 'medium',
19
+ source TEXT DEFAULT 'explicit',
20
+ created_at TEXT NOT NULL,
21
+ updated_at TEXT NOT NULL,
22
+ UNIQUE(namespace, key)
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS workflow_runs (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ session_id TEXT NOT NULL,
28
+ workflow_type TEXT NOT NULL,
29
+ input_slots_json TEXT,
30
+ resolved_slots_json TEXT,
31
+ defaults_used_json TEXT,
32
+ output_summary TEXT,
33
+ created_at TEXT NOT NULL,
34
+ turn_type TEXT NOT NULL DEFAULT 'workflow'
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS recommendations (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ workflow_run_id INTEGER NOT NULL,
40
+ recommendation_type TEXT NOT NULL,
41
+ symbol TEXT,
42
+ payload_json TEXT,
43
+ created_at TEXT NOT NULL,
44
+ FOREIGN KEY (workflow_run_id) REFERENCES workflow_runs(id)
45
+ );
46
+
47
+ CREATE TABLE IF NOT EXISTS workflow_events (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ run_id TEXT NOT NULL,
50
+ step_index INTEGER NOT NULL,
51
+ event_type TEXT NOT NULL,
52
+ payload_json TEXT,
53
+ timestamp TEXT NOT NULL
54
+ );
55
+
56
+ CREATE INDEX IF NOT EXISTS idx_workflow_events_run_id ON workflow_events(run_id);
57
+
58
+ CREATE TABLE IF NOT EXISTS tool_defaults (
59
+ tool_name TEXT NOT NULL,
60
+ param_path TEXT NOT NULL,
61
+ value_json TEXT NOT NULL,
62
+ set_at TEXT NOT NULL,
63
+ PRIMARY KEY (tool_name, param_path)
64
+ );
65
+ `;
66
+
67
+ export function initDatabase(path: string): Database.Database {
68
+ if (path !== ":memory:") {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ }
71
+ const db = new Database(path);
72
+ db.pragma("journal_mode = WAL");
73
+ db.pragma("foreign_keys = ON");
74
+ ensureCurrentSchema(db);
75
+
76
+ return db;
77
+ }
78
+
79
+ export function initDefaultDatabase(): Database.Database {
80
+ return initDatabase(getStateDbPath());
81
+ }
82
+
83
+ function ensureCurrentSchema(db: Database.Database): void {
84
+ const currentVersion = readSchemaVersion(db);
85
+
86
+ if (currentVersion === CURRENT_SCHEMA_VERSION) {
87
+ // Up to date — still run CREATE TABLE IF NOT EXISTS for any missing auxiliary
88
+ // tables (e.g. workflow_events added out-of-band).
89
+ db.exec(CURRENT_SCHEMA);
90
+ return;
91
+ }
92
+
93
+ if (currentVersion === 3) {
94
+ migrateV3ToV4(db);
95
+ return;
96
+ }
97
+
98
+ // Additive v2 → v3 → v4 migration without dropping data.
99
+ if (currentVersion === 2) {
100
+ migrateV2ToV3(db);
101
+ migrateV3ToV4(db);
102
+ return;
103
+ }
104
+
105
+ // Any other mismatch (null first-run, or a foreign schema): reset.
106
+ resetSchema(db);
107
+ }
108
+
109
+ function migrateV2ToV3(db: Database.Database): void {
110
+ const cols = (db.pragma("table_info(workflow_runs)") as Array<{ name: string }>).map(
111
+ (c) => c.name,
112
+ );
113
+
114
+ if (!cols.includes("turn_type")) {
115
+ db.exec(
116
+ `ALTER TABLE workflow_runs ADD COLUMN turn_type TEXT NOT NULL DEFAULT 'workflow'`,
117
+ );
118
+ }
119
+
120
+ // Ensure any tables or indexes added between versions are present.
121
+ db.exec(CURRENT_SCHEMA);
122
+
123
+ db.prepare("DELETE FROM schema_version").run();
124
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(3);
125
+ }
126
+
127
+ function migrateV3ToV4(db: Database.Database): void {
128
+ db.exec(CURRENT_SCHEMA);
129
+
130
+ db.prepare("DELETE FROM schema_version").run();
131
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
132
+ }
133
+
134
+ function readSchemaVersion(db: Database.Database): number | null {
135
+ const table = db
136
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_version'")
137
+ .get() as { name: string } | undefined;
138
+ if (!table) {
139
+ return null;
140
+ }
141
+
142
+ const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get() as
143
+ | { version: number }
144
+ | undefined;
145
+ return row?.version ?? null;
146
+ }
147
+
148
+ function resetSchema(db: Database.Database): void {
149
+ db.exec(`
150
+ DROP TABLE IF EXISTS recommendations;
151
+ DROP TABLE IF EXISTS workflow_runs;
152
+ DROP TABLE IF EXISTS user_preferences;
153
+ DROP TABLE IF EXISTS tool_defaults;
154
+ DROP TABLE IF EXISTS schema_version;
155
+ `);
156
+ db.exec(CURRENT_SCHEMA);
157
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
158
+ }
159
+
160
+ export function getTableNames(db: Database.Database): string[] {
161
+ const rows = db
162
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
163
+ .all() as Array<{ name: string }>;
164
+ return rows.map((r) => r.name);
165
+ }
166
+
167
+ export function getSchemaVersion(db: Database.Database): number {
168
+ const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get() as
169
+ | { version: number }
170
+ | undefined;
171
+ return row?.version ?? 0;
172
+ }