opencandle 0.5.0 → 0.7.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 (574) hide show
  1. package/README.md +170 -186
  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 +66 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +13 -3
  11. package/dist/config.js +25 -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/cache.d.ts +8 -11
  17. package/dist/infra/cache.js +17 -15
  18. package/dist/infra/cache.js.map +1 -1
  19. package/dist/infra/http-client.d.ts +4 -1
  20. package/dist/infra/http-client.js +59 -6
  21. package/dist/infra/http-client.js.map +1 -1
  22. package/dist/infra/index.d.ts +2 -3
  23. package/dist/infra/index.js +2 -3
  24. package/dist/infra/index.js.map +1 -1
  25. package/dist/infra/native-dependencies.js +2 -2
  26. package/dist/infra/native-dependencies.js.map +1 -1
  27. package/dist/infra/node-version.js.map +1 -1
  28. package/dist/infra/opencandle-paths.d.ts +0 -3
  29. package/dist/infra/opencandle-paths.js +4 -11
  30. package/dist/infra/opencandle-paths.js.map +1 -1
  31. package/dist/infra/rate-limiter.js +12 -9
  32. package/dist/infra/rate-limiter.js.map +1 -1
  33. package/dist/market-state/alert-conditions.d.ts +34 -0
  34. package/dist/market-state/alert-conditions.js +23 -0
  35. package/dist/market-state/alert-conditions.js.map +1 -0
  36. package/dist/market-state/alert-runner.d.ts +55 -0
  37. package/dist/market-state/alert-runner.js +634 -0
  38. package/dist/market-state/alert-runner.js.map +1 -0
  39. package/dist/market-state/daily-report.d.ts +26 -0
  40. package/dist/market-state/daily-report.js +179 -0
  41. package/dist/market-state/daily-report.js.map +1 -0
  42. package/dist/market-state/local-automation-service.d.ts +25 -0
  43. package/dist/market-state/local-automation-service.js +119 -0
  44. package/dist/market-state/local-automation-service.js.map +1 -0
  45. package/dist/market-state/notification-delivery.d.ts +14 -0
  46. package/dist/market-state/notification-delivery.js +139 -0
  47. package/dist/market-state/notification-delivery.js.map +1 -0
  48. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  49. package/dist/market-state/resolve-for-mutation.js +15 -0
  50. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  51. package/dist/market-state/resolve.d.ts +14 -0
  52. package/dist/market-state/resolve.js +89 -0
  53. package/dist/market-state/resolve.js.map +1 -0
  54. package/dist/market-state/service.d.ts +527 -0
  55. package/dist/market-state/service.js +1099 -0
  56. package/dist/market-state/service.js.map +1 -0
  57. package/dist/memory/index.d.ts +7 -7
  58. package/dist/memory/index.js +6 -6
  59. package/dist/memory/index.js.map +1 -1
  60. package/dist/memory/manager.js +11 -11
  61. package/dist/memory/manager.js.map +1 -1
  62. package/dist/memory/retrieval.js +7 -4
  63. package/dist/memory/retrieval.js.map +1 -1
  64. package/dist/memory/sqlite.js +385 -3
  65. package/dist/memory/sqlite.js.map +1 -1
  66. package/dist/memory/storage.js +1 -2
  67. package/dist/memory/storage.js.map +1 -1
  68. package/dist/memory/tool-defaults.js +64 -28
  69. package/dist/memory/tool-defaults.js.map +1 -1
  70. package/dist/memory/types.js.map +1 -1
  71. package/dist/monitor.d.ts +2 -0
  72. package/dist/monitor.js +104 -0
  73. package/dist/monitor.js.map +1 -0
  74. package/dist/onboarding/connect.d.ts +2 -2
  75. package/dist/onboarding/connect.js +13 -8
  76. package/dist/onboarding/connect.js.map +1 -1
  77. package/dist/onboarding/credential-interceptor.js +1 -1
  78. package/dist/onboarding/credential-interceptor.js.map +1 -1
  79. package/dist/onboarding/degradation-accumulator.js +1 -3
  80. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  81. package/dist/onboarding/provider-status.d.ts +48 -0
  82. package/dist/onboarding/provider-status.js +285 -0
  83. package/dist/onboarding/provider-status.js.map +1 -0
  84. package/dist/onboarding/providers.d.ts +85 -8
  85. package/dist/onboarding/providers.js +83 -18
  86. package/dist/onboarding/providers.js.map +1 -1
  87. package/dist/onboarding/state.d.ts +1 -0
  88. package/dist/onboarding/state.js +5 -0
  89. package/dist/onboarding/state.js.map +1 -1
  90. package/dist/onboarding/tool-helpers.js +1 -1
  91. package/dist/onboarding/tool-helpers.js.map +1 -1
  92. package/dist/onboarding/tool-tags.d.ts +12 -1
  93. package/dist/onboarding/tool-tags.js +37 -5
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.d.ts +2 -2
  96. package/dist/onboarding/validation.js +1 -1
  97. package/dist/onboarding/validation.js.map +1 -1
  98. package/dist/pi/opencandle-extension.d.ts +8 -0
  99. package/dist/pi/opencandle-extension.js +502 -42
  100. package/dist/pi/opencandle-extension.js.map +1 -1
  101. package/dist/pi/session.d.ts +1 -1
  102. package/dist/pi/session.js +3 -1
  103. package/dist/pi/session.js.map +1 -1
  104. package/dist/pi/setup.js +8 -3
  105. package/dist/pi/setup.js.map +1 -1
  106. package/dist/pi/tool-adapter.d.ts +4 -1
  107. package/dist/pi/tool-adapter.js +10 -6
  108. package/dist/pi/tool-adapter.js.map +1 -1
  109. package/dist/prompts/context-builder.d.ts +1 -1
  110. package/dist/prompts/context-builder.js +20 -7
  111. package/dist/prompts/context-builder.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +1 -1
  113. package/dist/prompts/policy-cards.js +2 -2
  114. package/dist/prompts/policy-cards.js.map +1 -1
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/symbol-preflight.d.ts +20 -0
  117. package/dist/prompts/symbol-preflight.js +49 -0
  118. package/dist/prompts/symbol-preflight.js.map +1 -0
  119. package/dist/prompts/workflow-prompts.d.ts +1 -1
  120. package/dist/prompts/workflow-prompts.js +54 -16
  121. package/dist/prompts/workflow-prompts.js.map +1 -1
  122. package/dist/providers/alpha-vantage.d.ts +1 -1
  123. package/dist/providers/alpha-vantage.js +26 -7
  124. package/dist/providers/alpha-vantage.js.map +1 -1
  125. package/dist/providers/coingecko.js +1 -1
  126. package/dist/providers/coingecko.js.map +1 -1
  127. package/dist/providers/errors.d.ts +5 -0
  128. package/dist/providers/errors.js +11 -0
  129. package/dist/providers/errors.js.map +1 -0
  130. package/dist/providers/exa-search.d.ts +2 -2
  131. package/dist/providers/exa-search.js +19 -11
  132. package/dist/providers/exa-search.js.map +1 -1
  133. package/dist/providers/external-tool-error.d.ts +10 -0
  134. package/dist/providers/external-tool-error.js +21 -0
  135. package/dist/providers/external-tool-error.js.map +1 -0
  136. package/dist/providers/fear-greed.js +1 -1
  137. package/dist/providers/fear-greed.js.map +1 -1
  138. package/dist/providers/finnhub.js +3 -5
  139. package/dist/providers/finnhub.js.map +1 -1
  140. package/dist/providers/fred.js +2 -2
  141. package/dist/providers/fred.js.map +1 -1
  142. package/dist/providers/index.d.ts +7 -6
  143. package/dist/providers/index.js +6 -5
  144. package/dist/providers/index.js.map +1 -1
  145. package/dist/providers/reddit-cli.d.ts +36 -0
  146. package/dist/providers/reddit-cli.js +201 -0
  147. package/dist/providers/reddit-cli.js.map +1 -0
  148. package/dist/providers/reddit.d.ts +1 -1
  149. package/dist/providers/reddit.js +9 -37
  150. package/dist/providers/reddit.js.map +1 -1
  151. package/dist/providers/sec-edgar.d.ts +1 -0
  152. package/dist/providers/sec-edgar.js +12 -4
  153. package/dist/providers/sec-edgar.js.map +1 -1
  154. package/dist/providers/tradingview.d.ts +47 -0
  155. package/dist/providers/tradingview.js +275 -0
  156. package/dist/providers/tradingview.js.map +1 -0
  157. package/dist/providers/twitter-cli.d.ts +40 -0
  158. package/dist/providers/twitter-cli.js +153 -0
  159. package/dist/providers/twitter-cli.js.map +1 -0
  160. package/dist/providers/twitter.d.ts +0 -8
  161. package/dist/providers/twitter.js +8 -60
  162. package/dist/providers/twitter.js.map +1 -1
  163. package/dist/providers/web-search.js +26 -12
  164. package/dist/providers/web-search.js.map +1 -1
  165. package/dist/providers/with-fallback.js +4 -2
  166. package/dist/providers/with-fallback.js.map +1 -1
  167. package/dist/providers/wrap-provider.d.ts +2 -3
  168. package/dist/providers/wrap-provider.js +44 -8
  169. package/dist/providers/wrap-provider.js.map +1 -1
  170. package/dist/providers/yahoo-finance.d.ts +1 -1
  171. package/dist/providers/yahoo-finance.js +153 -48
  172. package/dist/providers/yahoo-finance.js.map +1 -1
  173. package/dist/routing/classify-intent.d.ts +6 -0
  174. package/dist/routing/classify-intent.js +78 -7
  175. package/dist/routing/classify-intent.js.map +1 -1
  176. package/dist/routing/defaults.d.ts +1 -1
  177. package/dist/routing/entity-extractor.d.ts +1 -0
  178. package/dist/routing/entity-extractor.js +234 -29
  179. package/dist/routing/entity-extractor.js.map +1 -1
  180. package/dist/routing/fund-symbols.d.ts +2 -0
  181. package/dist/routing/fund-symbols.js +55 -0
  182. package/dist/routing/fund-symbols.js.map +1 -0
  183. package/dist/routing/horizon.d.ts +1 -0
  184. package/dist/routing/horizon.js +10 -0
  185. package/dist/routing/horizon.js.map +1 -0
  186. package/dist/routing/index.d.ts +10 -10
  187. package/dist/routing/index.js +6 -6
  188. package/dist/routing/index.js.map +1 -1
  189. package/dist/routing/planning.d.ts +2 -2
  190. package/dist/routing/planning.js +65 -34
  191. package/dist/routing/planning.js.map +1 -1
  192. package/dist/routing/route-manifest.d.ts +2 -2
  193. package/dist/routing/route-manifest.js +25 -4
  194. package/dist/routing/route-manifest.js.map +1 -1
  195. package/dist/routing/router-llm-client.js.map +1 -1
  196. package/dist/routing/router-prompt.js +7 -9
  197. package/dist/routing/router-prompt.js.map +1 -1
  198. package/dist/routing/router-types.d.ts +1 -0
  199. package/dist/routing/router.js +137 -22
  200. package/dist/routing/router.js.map +1 -1
  201. package/dist/routing/slot-resolver.d.ts +1 -1
  202. package/dist/routing/slot-resolver.js +2 -4
  203. package/dist/routing/slot-resolver.js.map +1 -1
  204. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  205. package/dist/routing/symbol-disambiguator.js +52 -0
  206. package/dist/routing/symbol-disambiguator.js.map +1 -0
  207. package/dist/routing/turn-context.d.ts +1 -1
  208. package/dist/routing/turn-context.js +1 -1
  209. package/dist/routing/turn-context.js.map +1 -1
  210. package/dist/routing/types.d.ts +2 -0
  211. package/dist/runtime/answer-contracts.d.ts +1 -1
  212. package/dist/runtime/answer-contracts.js +48 -9
  213. package/dist/runtime/answer-contracts.js.map +1 -1
  214. package/dist/runtime/artifact-contracts.js.map +1 -1
  215. package/dist/runtime/planning-evidence.js +47 -26
  216. package/dist/runtime/planning-evidence.js.map +1 -1
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +13 -5
  224. package/dist/runtime/session-coordinator.js +160 -20
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +7 -5
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +10 -11
  243. package/dist/sentiment/index.js +10 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/insights.d.ts +17 -0
  246. package/dist/sentiment/insights.js +206 -0
  247. package/dist/sentiment/insights.js.map +1 -0
  248. package/dist/sentiment/keywords.js +26 -4
  249. package/dist/sentiment/keywords.js.map +1 -1
  250. package/dist/sentiment/pipeline.d.ts +2 -2
  251. package/dist/sentiment/pipeline.js +14 -2
  252. package/dist/sentiment/pipeline.js.map +1 -1
  253. package/dist/sentiment/scorer.d.ts +2 -0
  254. package/dist/sentiment/scorer.js +11 -2
  255. package/dist/sentiment/scorer.js.map +1 -1
  256. package/dist/sentiment/store.d.ts +1 -1
  257. package/dist/sentiment/store.js +1 -1
  258. package/dist/sentiment/store.js.map +1 -1
  259. package/dist/sentiment/trends.d.ts +1 -1
  260. package/dist/sentiment/trends.js.map +1 -1
  261. package/dist/sentiment/types.d.ts +2 -0
  262. package/dist/sentiment/types.js.map +1 -1
  263. package/dist/system-prompt.js +6 -9
  264. package/dist/system-prompt.js.map +1 -1
  265. package/dist/tool-kit.d.ts +7 -7
  266. package/dist/tool-kit.js +4 -4
  267. package/dist/tool-kit.js.map +1 -1
  268. package/dist/tools/fundamentals/company-overview.js +11 -6
  269. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  270. package/dist/tools/fundamentals/comps.js +18 -9
  271. package/dist/tools/fundamentals/comps.js.map +1 -1
  272. package/dist/tools/fundamentals/dcf.js +23 -11
  273. package/dist/tools/fundamentals/dcf.js.map +1 -1
  274. package/dist/tools/fundamentals/earnings.js +8 -3
  275. package/dist/tools/fundamentals/earnings.js.map +1 -1
  276. package/dist/tools/fundamentals/financials.js +8 -3
  277. package/dist/tools/fundamentals/financials.js.map +1 -1
  278. package/dist/tools/fundamentals/sec-filings.js +21 -6
  279. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  280. package/dist/tools/index.d.ts +27 -20
  281. package/dist/tools/index.js +55 -43
  282. package/dist/tools/index.js.map +1 -1
  283. package/dist/tools/interaction/ask-user.js +15 -3
  284. package/dist/tools/interaction/ask-user.js.map +1 -1
  285. package/dist/tools/macro/fear-greed.js.map +1 -1
  286. package/dist/tools/macro/fred-data.d.ts +1 -1
  287. package/dist/tools/macro/fred-data.js +17 -6
  288. package/dist/tools/macro/fred-data.js.map +1 -1
  289. package/dist/tools/market/crypto-history.js +3 -1
  290. package/dist/tools/market/crypto-history.js.map +1 -1
  291. package/dist/tools/market/crypto-price.js +3 -1
  292. package/dist/tools/market/crypto-price.js.map +1 -1
  293. package/dist/tools/market/screen-stocks.d.ts +18 -0
  294. package/dist/tools/market/screen-stocks.js +252 -0
  295. package/dist/tools/market/screen-stocks.js.map +1 -0
  296. package/dist/tools/market/search-ticker.js +160 -8
  297. package/dist/tools/market/search-ticker.js.map +1 -1
  298. package/dist/tools/market/stock-history.d.ts +2 -2
  299. package/dist/tools/market/stock-history.js +26 -7
  300. package/dist/tools/market/stock-history.js.map +1 -1
  301. package/dist/tools/market/stock-quote.js +5 -3
  302. package/dist/tools/market/stock-quote.js.map +1 -1
  303. package/dist/tools/options/greeks.js +1 -1
  304. package/dist/tools/options/greeks.js.map +1 -1
  305. package/dist/tools/options/option-chain.js +19 -6
  306. package/dist/tools/options/option-chain.js.map +1 -1
  307. package/dist/tools/portfolio/alerts.d.ts +15 -0
  308. package/dist/tools/portfolio/alerts.js +357 -0
  309. package/dist/tools/portfolio/alerts.js.map +1 -0
  310. package/dist/tools/portfolio/correlation.d.ts +1 -1
  311. package/dist/tools/portfolio/correlation.js +33 -13
  312. package/dist/tools/portfolio/correlation.js.map +1 -1
  313. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  314. package/dist/tools/portfolio/daily-report.js +83 -0
  315. package/dist/tools/portfolio/daily-report.js.map +1 -0
  316. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  317. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  318. package/dist/tools/portfolio/notifications.d.ts +7 -0
  319. package/dist/tools/portfolio/notifications.js +43 -0
  320. package/dist/tools/portfolio/notifications.js.map +1 -0
  321. package/dist/tools/portfolio/predictions.d.ts +12 -6
  322. package/dist/tools/portfolio/predictions.js +337 -87
  323. package/dist/tools/portfolio/predictions.js.map +1 -1
  324. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  325. package/dist/tools/portfolio/risk-analysis.js +45 -6
  326. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  327. package/dist/tools/portfolio/tracker.d.ts +4 -3
  328. package/dist/tools/portfolio/tracker.js +246 -101
  329. package/dist/tools/portfolio/tracker.js.map +1 -1
  330. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  331. package/dist/tools/portfolio/watchlist.js +208 -108
  332. package/dist/tools/portfolio/watchlist.js.map +1 -1
  333. package/dist/tools/sentiment/insight-format.d.ts +2 -0
  334. package/dist/tools/sentiment/insight-format.js +36 -0
  335. package/dist/tools/sentiment/insight-format.js.map +1 -0
  336. package/dist/tools/sentiment/query-match.d.ts +3 -0
  337. package/dist/tools/sentiment/query-match.js +113 -0
  338. package/dist/tools/sentiment/query-match.js.map +1 -0
  339. package/dist/tools/sentiment/reddit-sentiment.d.ts +12 -1
  340. package/dist/tools/sentiment/reddit-sentiment.js +266 -107
  341. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  342. package/dist/tools/sentiment/sentiment-summary.d.ts +9 -1
  343. package/dist/tools/sentiment/sentiment-summary.js +223 -205
  344. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  345. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  346. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  347. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  348. package/dist/tools/sentiment/twitter-sentiment.d.ts +11 -1
  349. package/dist/tools/sentiment/twitter-sentiment.js +188 -58
  350. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  351. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  352. package/dist/tools/sentiment/untrusted-text.js +17 -0
  353. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  354. package/dist/tools/sentiment/web-search.js +9 -13
  355. package/dist/tools/sentiment/web-search.js.map +1 -1
  356. package/dist/tools/sentiment/web-sentiment.js +19 -3
  357. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  358. package/dist/tools/technical/backtest.d.ts +1 -1
  359. package/dist/tools/technical/backtest.js +27 -20
  360. package/dist/tools/technical/backtest.js.map +1 -1
  361. package/dist/tools/technical/indicators.js +23 -5
  362. package/dist/tools/technical/indicators.js.map +1 -1
  363. package/dist/types/index.d.ts +3 -3
  364. package/dist/types/index.js.map +1 -1
  365. package/dist/types/market.d.ts +1 -0
  366. package/dist/types/portfolio.d.ts +14 -4
  367. package/dist/types/sentiment.d.ts +52 -0
  368. package/dist/workflows/compare-assets.d.ts +0 -3
  369. package/dist/workflows/compare-assets.js +20 -11
  370. package/dist/workflows/compare-assets.js.map +1 -1
  371. package/dist/workflows/index.d.ts +3 -4
  372. package/dist/workflows/index.js +3 -3
  373. package/dist/workflows/index.js.map +1 -1
  374. package/dist/workflows/options-screener.d.ts +0 -3
  375. package/dist/workflows/options-screener.js +4 -11
  376. package/dist/workflows/options-screener.js.map +1 -1
  377. package/dist/workflows/portfolio-builder.d.ts +0 -3
  378. package/dist/workflows/portfolio-builder.js +0 -8
  379. package/dist/workflows/portfolio-builder.js.map +1 -1
  380. package/gui/server/ask-user-bridge.ts +1 -1
  381. package/gui/server/automation-heartbeat.ts +97 -0
  382. package/gui/server/background-quotes.ts +97 -1
  383. package/gui/server/chat-event-adapter.ts +32 -10
  384. package/gui/server/chat-run-session.ts +16 -0
  385. package/gui/server/invoke-tool.ts +160 -3
  386. package/gui/server/live-chat-event-adapter.ts +21 -6
  387. package/gui/server/market-state-api.ts +315 -0
  388. package/gui/server/model-setup.ts +156 -2
  389. package/gui/server/private-api-access.ts +62 -0
  390. package/gui/server/projector.ts +18 -9
  391. package/gui/server/prompt-observation.ts +4 -7
  392. package/gui/server/quote-snapshot-store.ts +50 -0
  393. package/gui/server/server.ts +218 -451
  394. package/gui/server/session-actions.ts +186 -1
  395. package/gui/server/shutdown.ts +47 -0
  396. package/gui/server/tool-invoke-ack.ts +49 -0
  397. package/gui/server/tool-metadata.ts +101 -24
  398. package/gui/server/websocket.ts +13 -3
  399. package/gui/server/writer-lock.ts +6 -2
  400. package/gui/server/ws-hub.ts +311 -0
  401. package/gui/shared/chat-events.ts +16 -1
  402. package/gui/shared/event-reducer.ts +24 -6
  403. package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +1 -0
  404. package/gui/web/dist/assets/index-C6W_2eAn.js +69 -0
  405. package/gui/web/dist/assets/index-hwbx24a5.css +1 -0
  406. package/gui/web/dist/index.html +2 -2
  407. package/package.json +9 -6
  408. package/src/analysts/contracts.ts +10 -23
  409. package/src/analysts/orchestrator.ts +8 -43
  410. package/src/cli.ts +76 -12
  411. package/src/config.ts +44 -9
  412. package/src/index.ts +1 -1
  413. package/src/infra/cache.ts +41 -30
  414. package/src/infra/http-client.ts +72 -6
  415. package/src/infra/index.ts +6 -10
  416. package/src/infra/native-dependencies.ts +8 -3
  417. package/src/infra/node-version.ts +3 -1
  418. package/src/infra/opencandle-paths.ts +3 -14
  419. package/src/infra/rate-limiter.ts +22 -19
  420. package/src/market-state/alert-conditions.ts +82 -0
  421. package/src/market-state/alert-runner.ts +863 -0
  422. package/src/market-state/daily-report.ts +247 -0
  423. package/src/market-state/local-automation-service.ts +162 -0
  424. package/src/market-state/notification-delivery.ts +158 -0
  425. package/src/market-state/resolve-for-mutation.ts +24 -0
  426. package/src/market-state/resolve.ts +112 -0
  427. package/src/market-state/service.ts +2344 -0
  428. package/src/memory/index.ts +7 -7
  429. package/src/memory/manager.ts +14 -16
  430. package/src/memory/retrieval.ts +8 -7
  431. package/src/memory/sqlite.ts +407 -6
  432. package/src/memory/storage.ts +5 -15
  433. package/src/memory/tool-defaults.ts +60 -39
  434. package/src/memory/types.ts +3 -3
  435. package/src/monitor.ts +121 -0
  436. package/src/onboarding/connect.ts +24 -31
  437. package/src/onboarding/credential-interceptor.ts +3 -15
  438. package/src/onboarding/degradation-accumulator.ts +1 -3
  439. package/src/onboarding/provider-status.ts +410 -0
  440. package/src/onboarding/providers.ts +144 -45
  441. package/src/onboarding/state.ts +13 -15
  442. package/src/onboarding/tool-helpers.ts +2 -9
  443. package/src/onboarding/tool-tags.ts +51 -8
  444. package/src/onboarding/validation.ts +16 -22
  445. package/src/pi/opencandle-extension.ts +643 -101
  446. package/src/pi/session.ts +7 -5
  447. package/src/pi/setup.ts +61 -43
  448. package/src/pi/tool-adapter.ts +19 -6
  449. package/src/prompts/context-builder.ts +24 -13
  450. package/src/prompts/policy-cards.ts +3 -3
  451. package/src/prompts/sections.ts +1 -1
  452. package/src/prompts/symbol-preflight.ts +80 -0
  453. package/src/prompts/workflow-prompts.ts +77 -28
  454. package/src/providers/alpha-vantage.ts +58 -39
  455. package/src/providers/coingecko.ts +2 -5
  456. package/src/providers/errors.ts +9 -0
  457. package/src/providers/exa-search.ts +24 -22
  458. package/src/providers/external-tool-error.ts +20 -0
  459. package/src/providers/fear-greed.ts +1 -1
  460. package/src/providers/finnhub.ts +7 -6
  461. package/src/providers/fred.ts +3 -3
  462. package/src/providers/index.ts +14 -6
  463. package/src/providers/reddit-cli.ts +317 -0
  464. package/src/providers/reddit.ts +14 -59
  465. package/src/providers/sec-edgar.ts +20 -6
  466. package/src/providers/tradingview.ts +399 -0
  467. package/src/providers/twitter-cli.ts +233 -0
  468. package/src/providers/twitter.ts +8 -79
  469. package/src/providers/web-search.ts +30 -20
  470. package/src/providers/with-fallback.ts +8 -7
  471. package/src/providers/wrap-provider.ts +49 -10
  472. package/src/providers/yahoo-finance.ts +204 -66
  473. package/src/routing/classify-intent.ts +101 -10
  474. package/src/routing/defaults.ts +1 -1
  475. package/src/routing/entity-extractor.ts +287 -38
  476. package/src/routing/fund-symbols.ts +58 -0
  477. package/src/routing/horizon.ts +7 -0
  478. package/src/routing/index.ts +48 -48
  479. package/src/routing/planning.ts +145 -53
  480. package/src/routing/route-manifest.ts +37 -15
  481. package/src/routing/router-llm-client.ts +4 -4
  482. package/src/routing/router-prompt.ts +15 -19
  483. package/src/routing/router-types.ts +2 -5
  484. package/src/routing/router.ts +251 -53
  485. package/src/routing/slot-resolver.ts +34 -11
  486. package/src/routing/symbol-disambiguator.ts +72 -0
  487. package/src/routing/turn-context.ts +6 -9
  488. package/src/routing/types.ts +2 -0
  489. package/src/runtime/answer-contracts.ts +105 -45
  490. package/src/runtime/artifact-contracts.ts +2 -1
  491. package/src/runtime/planning-evidence.ts +157 -66
  492. package/src/runtime/prompt-step.ts +1 -16
  493. package/src/runtime/run-context.ts +12 -2
  494. package/src/runtime/session-coordinator.ts +238 -63
  495. package/src/runtime/session-title.ts +60 -0
  496. package/src/runtime/tool-defaults-wrapper.ts +13 -5
  497. package/src/runtime/validation.ts +1 -4
  498. package/src/runtime/workflow-events.ts +7 -7
  499. package/src/runtime/workflow-runner.ts +5 -11
  500. package/src/sentiment/adapters/finnhub.ts +7 -2
  501. package/src/sentiment/adapters/reddit.ts +2 -2
  502. package/src/sentiment/adapters/twitter.ts +1 -1
  503. package/src/sentiment/adapters/web.ts +1 -1
  504. package/src/sentiment/index.ts +17 -26
  505. package/src/sentiment/insights.ts +269 -0
  506. package/src/sentiment/keywords.ts +26 -4
  507. package/src/sentiment/pipeline.ts +28 -5
  508. package/src/sentiment/scorer.ts +13 -2
  509. package/src/sentiment/store.ts +2 -2
  510. package/src/sentiment/trends.ts +9 -3
  511. package/src/sentiment/types.ts +8 -4
  512. package/src/system-prompt.ts +6 -9
  513. package/src/tool-kit.ts +10 -9
  514. package/src/tools/fundamentals/company-overview.ts +19 -9
  515. package/src/tools/fundamentals/comps.ts +68 -55
  516. package/src/tools/fundamentals/dcf.ts +145 -95
  517. package/src/tools/fundamentals/earnings.ts +16 -6
  518. package/src/tools/fundamentals/financials.ts +16 -7
  519. package/src/tools/fundamentals/sec-filings.ts +37 -16
  520. package/src/tools/index.ts +56 -43
  521. package/src/tools/interaction/ask-user.ts +22 -10
  522. package/src/tools/macro/fear-greed.ts +1 -1
  523. package/src/tools/macro/fred-data.ts +58 -46
  524. package/src/tools/market/crypto-history.ts +8 -3
  525. package/src/tools/market/crypto-price.ts +6 -6
  526. package/src/tools/market/screen-stocks.ts +279 -0
  527. package/src/tools/market/search-ticker.ts +218 -17
  528. package/src/tools/market/stock-history.ts +37 -12
  529. package/src/tools/market/stock-quote.ts +10 -7
  530. package/src/tools/options/greeks.ts +5 -5
  531. package/src/tools/options/option-chain.ts +41 -17
  532. package/src/tools/portfolio/alerts.ts +457 -0
  533. package/src/tools/portfolio/correlation.ts +47 -20
  534. package/src/tools/portfolio/daily-report.ts +101 -0
  535. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  536. package/src/tools/portfolio/notifications.ts +45 -0
  537. package/src/tools/portfolio/predictions.ts +406 -106
  538. package/src/tools/portfolio/risk-analysis.ts +46 -7
  539. package/src/tools/portfolio/tracker.ts +270 -109
  540. package/src/tools/portfolio/watchlist.ts +250 -121
  541. package/src/tools/sentiment/insight-format.ts +50 -0
  542. package/src/tools/sentiment/query-match.ts +117 -0
  543. package/src/tools/sentiment/reddit-sentiment.ts +360 -121
  544. package/src/tools/sentiment/sentiment-summary.ts +302 -235
  545. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  546. package/src/tools/sentiment/twitter-sentiment.ts +264 -73
  547. package/src/tools/sentiment/untrusted-text.ts +21 -0
  548. package/src/tools/sentiment/web-search.ts +21 -18
  549. package/src/tools/sentiment/web-sentiment.ts +30 -10
  550. package/src/tools/technical/backtest.ts +32 -22
  551. package/src/tools/technical/indicators.ts +39 -14
  552. package/src/types/index.ts +8 -3
  553. package/src/types/market.ts +1 -0
  554. package/src/types/portfolio.ts +14 -4
  555. package/src/types/sentiment.ts +61 -2
  556. package/src/workflows/compare-assets.ts +33 -21
  557. package/src/workflows/index.ts +3 -4
  558. package/src/workflows/options-screener.ts +27 -29
  559. package/src/workflows/portfolio-builder.ts +34 -27
  560. package/dist/infra/browser.d.ts +0 -35
  561. package/dist/infra/browser.js +0 -103
  562. package/dist/infra/browser.js.map +0 -1
  563. package/dist/tools/interaction/twitter-login.d.ts +0 -8
  564. package/dist/tools/interaction/twitter-login.js +0 -77
  565. package/dist/tools/interaction/twitter-login.js.map +0 -1
  566. package/dist/workflows/types.d.ts +0 -4
  567. package/dist/workflows/types.js +0 -2
  568. package/dist/workflows/types.js.map +0 -1
  569. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  570. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  571. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  572. package/src/infra/browser.ts +0 -111
  573. package/src/tools/interaction/twitter-login.ts +0 -93
  574. package/src/workflows/types.ts +0 -4
@@ -1,32 +1,33 @@
1
- // Provider registry — single source of truth for OpenCandle's credentialed
2
- // third-party data providers. Every setup pathway iterates this registry:
1
+ // Provider registry — single source of truth for OpenCandle's third-party
2
+ // data providers. Every setup/status pathway iterates this registry:
3
3
  // first-run startup, the `/connect` command, the `tool_result` credential
4
- // interception handler, and the gap-note generator all read from here.
4
+ // interception handler, GUI provider rows, doctor checks, and gap-note
5
+ // generation all read from here.
5
6
  //
6
- // Adding a new credentialed provider is a two-step change: add its `ProviderId`
7
- // to the literal union below, and add its descriptor to the `PROVIDERS` array.
8
- // The `satisfies` check ensures TypeScript fails the build if the union and
9
- // the array ever disagree.
7
+ // Adding a new provider is a two-step change: add its id to the relevant
8
+ // literal union below, and add its descriptor to the `PROVIDERS` array. The
9
+ // `satisfies` and exhaustiveness checks keep the registry and id unions aligned.
10
10
 
11
11
  import { getConfig, loadFileConfig } from "../config.js";
12
12
 
13
- export type ProviderId =
14
- | "alpha_vantage"
15
- | "fred"
16
- | "finnhub"
17
- | "brave"
18
- | "exa";
13
+ export type ApiKeyProviderId = "alpha_vantage" | "fred" | "finnhub" | "brave" | "exa";
14
+ export type ExternalToolProviderId = "twitter" | "reddit";
15
+ export type PublicHttpProviderId = "yahoo";
16
+ export type ProviderId = ApiKeyProviderId | ExternalToolProviderId | PublicHttpProviderId;
19
17
 
20
18
  export type ProviderCategory =
21
19
  | "fundamentals"
22
20
  | "macro"
23
21
  | "news"
24
- | "web_search";
22
+ | "web_search"
23
+ | "sentiment"
24
+ | "market";
25
25
 
26
26
  export type ProviderTier = "hard" | "soft";
27
27
 
28
- export interface ProviderDescriptor {
28
+ interface BaseProviderDescriptor {
29
29
  readonly id: ProviderId;
30
+ readonly kind: "api-key" | "external-tool" | "public-http";
30
31
  readonly displayName: string;
31
32
  readonly category: ProviderCategory;
32
33
  /**
@@ -39,11 +40,6 @@ export interface ProviderDescriptor {
39
40
  readonly tier: ProviderTier;
40
41
  /** Lowercase friendly aliases accepted by `/connect` in addition to the id. */
41
42
  readonly aliases: readonly string[];
42
- readonly signupUrl: string;
43
- readonly freeTier: boolean;
44
- readonly envVar: string;
45
- /** Nested key path into `OpenCandleFileConfig` where the key is persisted. */
46
- readonly configPath: readonly string[];
47
43
  readonly unlocks: readonly string[];
48
44
  /**
49
45
  * Human copy describing the degraded experience when missing, or `null`
@@ -54,11 +50,43 @@ export interface ProviderDescriptor {
54
50
  readonly instructionsHint: string;
55
51
  }
56
52
 
53
+ export interface ApiKeyProviderDescriptor extends BaseProviderDescriptor {
54
+ readonly id: ApiKeyProviderId;
55
+ readonly kind: "api-key";
56
+ readonly signupUrl: string;
57
+ readonly freeTier: boolean;
58
+ readonly envVar: string;
59
+ /** Nested key path into `OpenCandleFileConfig` where the key is persisted. */
60
+ readonly configPath: readonly string[];
61
+ }
62
+
63
+ export interface ExternalToolProviderDescriptor extends BaseProviderDescriptor {
64
+ readonly id: ExternalToolProviderId;
65
+ readonly kind: "external-tool";
66
+ readonly binary: string;
67
+ readonly installCmd: string;
68
+ readonly sessionSource: "browser-cookies";
69
+ readonly supportedBrowsers?: readonly string[];
70
+ readonly sessionProbeArgs?: readonly string[];
71
+ }
72
+
73
+ export interface PublicHttpProviderDescriptor extends BaseProviderDescriptor {
74
+ readonly id: PublicHttpProviderId;
75
+ readonly kind: "public-http";
76
+ readonly probeUrl: string;
77
+ }
78
+
79
+ export type ProviderDescriptor =
80
+ | ApiKeyProviderDescriptor
81
+ | ExternalToolProviderDescriptor
82
+ | PublicHttpProviderDescriptor;
83
+
57
84
  // Declaration order matters: picker display order, per-workflow prompt priority,
58
85
  // getProvidersByCategory/getProvidersByTier iteration order.
59
86
  export const PROVIDERS = [
60
87
  {
61
88
  id: "alpha_vantage",
89
+ kind: "api-key",
62
90
  displayName: "Alpha Vantage",
63
91
  category: "fundamentals",
64
92
  tier: "hard",
@@ -79,6 +107,7 @@ export const PROVIDERS = [
79
107
  },
80
108
  {
81
109
  id: "fred",
110
+ kind: "api-key",
82
111
  displayName: "FRED",
83
112
  category: "macro",
84
113
  tier: "hard",
@@ -87,18 +116,14 @@ export const PROVIDERS = [
87
116
  freeTier: true,
88
117
  envVar: "FRED_API_KEY",
89
118
  configPath: ["providers", "fred", "apiKey"],
90
- unlocks: [
91
- "interest rates",
92
- "inflation data",
93
- "yield curve",
94
- "economic indicators",
95
- ],
119
+ unlocks: ["interest rates", "inflation data", "yield curve", "economic indicators"],
96
120
  fallbackDescription: null,
97
121
  snoozeDurationDays: 7,
98
122
  instructionsHint: "Free, about 30 seconds, requires a St. Louis Fed account",
99
123
  },
100
124
  {
101
125
  id: "finnhub",
126
+ kind: "api-key",
102
127
  displayName: "Finnhub",
103
128
  category: "news",
104
129
  tier: "soft",
@@ -107,10 +132,7 @@ export const PROVIDERS = [
107
132
  freeTier: true,
108
133
  envVar: "FINNHUB_API_KEY",
109
134
  configPath: ["providers", "finnhub", "apiKey"],
110
- unlocks: [
111
- "ticker-tagged company news",
112
- "sentiment enrichment with a dedicated news source",
113
- ],
135
+ unlocks: ["ticker-tagged company news", "sentiment enrichment with a dedicated news source"],
114
136
  // Finnhub is a soft enrichment source — sentiment-summary continues to work
115
137
  // with Twitter/Reddit/web search when Finnhub is missing. The fallback is
116
138
  // "the other sentiment sources still run".
@@ -121,6 +143,7 @@ export const PROVIDERS = [
121
143
  },
122
144
  {
123
145
  id: "brave",
146
+ kind: "api-key",
124
147
  displayName: "Brave Search",
125
148
  category: "web_search",
126
149
  tier: "soft",
@@ -140,6 +163,7 @@ export const PROVIDERS = [
140
163
  },
141
164
  {
142
165
  id: "exa",
166
+ kind: "api-key",
143
167
  displayName: "Exa",
144
168
  category: "web_search",
145
169
  tier: "soft",
@@ -164,8 +188,64 @@ export const PROVIDERS = [
164
188
  snoozeDurationDays: 7,
165
189
  instructionsHint: "Paid with free tier, signup opens in your browser",
166
190
  },
191
+ {
192
+ id: "yahoo",
193
+ kind: "public-http",
194
+ displayName: "Yahoo Finance",
195
+ category: "market",
196
+ tier: "hard",
197
+ aliases: ["yahoo", "yahoo-finance", "market-data", "options"],
198
+ probeUrl: "https://query1.finance.yahoo.com/v8/finance/chart/SPY?interval=1d&range=1d",
199
+ unlocks: ["quotes", "historical prices", "option chains", "market data"],
200
+ fallbackDescription: null,
201
+ snoozeDurationDays: 7,
202
+ instructionsHint: "No account needed; OpenCandle checks public Yahoo Finance reachability",
203
+ },
204
+ {
205
+ id: "twitter",
206
+ kind: "external-tool",
207
+ displayName: "X / Twitter",
208
+ category: "sentiment",
209
+ tier: "soft",
210
+ aliases: ["twitter", "x", "x-sentiment", "twitter-sentiment"],
211
+ binary: "twitter",
212
+ installCmd: "uv tool install twitter-cli",
213
+ sessionSource: "browser-cookies",
214
+ sessionProbeArgs: ["feed", "--max", "1", "--json"],
215
+ supportedBrowsers: ["Chrome", "Arc", "Edge", "Firefox", "Brave"],
216
+ unlocks: ["X/Twitter sentiment", "recent ticker mentions", "social engagement context"],
217
+ fallbackDescription:
218
+ "Sentiment summaries continue with Reddit, web search, and news when X is unavailable",
219
+ snoozeDurationDays: 7,
220
+ instructionsHint: "Install twitter-cli and stay logged into x.com in a supported browser",
221
+ },
222
+ {
223
+ id: "reddit",
224
+ kind: "external-tool",
225
+ displayName: "Reddit",
226
+ category: "sentiment",
227
+ tier: "soft",
228
+ aliases: ["reddit", "reddit-sentiment", "subreddit"],
229
+ binary: "rdt",
230
+ installCmd: "uv tool install rdt-cli",
231
+ sessionSource: "browser-cookies",
232
+ supportedBrowsers: ["Chrome", "Arc", "Edge", "Firefox", "Brave"],
233
+ sessionProbeArgs: ["status", "--json"],
234
+ unlocks: ["Reddit sentiment", "ticker discussion context", "retail investor discussion"],
235
+ fallbackDescription:
236
+ "Sentiment summaries continue with X/Twitter, web search, and news when Reddit is unavailable",
237
+ snoozeDurationDays: 7,
238
+ instructionsHint: "Install rdt-cli and stay logged into reddit.com in a supported browser",
239
+ },
167
240
  ] as const satisfies readonly ProviderDescriptor[];
168
241
 
242
+ const _providerIdExhaustivenessCheck: Record<
243
+ | Exclude<ProviderId, (typeof PROVIDERS)[number]["id"]>
244
+ | Exclude<(typeof PROVIDERS)[number]["id"], ProviderId>,
245
+ never
246
+ > = {};
247
+ void _providerIdExhaustivenessCheck;
248
+
169
249
  // -----------------------------------------------------------------------------
170
250
  // Lookup helpers
171
251
  // -----------------------------------------------------------------------------
@@ -186,6 +266,28 @@ export function listAllProviders(): readonly ProviderDescriptor[] {
186
266
  return PROVIDERS;
187
267
  }
188
268
 
269
+ export function isApiKeyProvider(
270
+ provider: ProviderDescriptor,
271
+ ): provider is ApiKeyProviderDescriptor {
272
+ return provider.kind === "api-key";
273
+ }
274
+
275
+ export function isExternalToolProvider(
276
+ provider: ProviderDescriptor,
277
+ ): provider is ExternalToolProviderDescriptor {
278
+ return provider.kind === "external-tool";
279
+ }
280
+
281
+ export function isPublicHttpProvider(
282
+ provider: ProviderDescriptor,
283
+ ): provider is PublicHttpProviderDescriptor {
284
+ return provider.kind === "public-http";
285
+ }
286
+
287
+ export function listApiKeyProviders(): readonly ApiKeyProviderDescriptor[] {
288
+ return (PROVIDERS as readonly ProviderDescriptor[]).filter(isApiKeyProvider);
289
+ }
290
+
189
291
  export function getProvider(id: ProviderId): ProviderDescriptor {
190
292
  const found = byId().get(id);
191
293
  if (!found) {
@@ -194,15 +296,11 @@ export function getProvider(id: ProviderId): ProviderDescriptor {
194
296
  return found;
195
297
  }
196
298
 
197
- export function getProvidersByCategory(
198
- category: ProviderCategory,
199
- ): readonly ProviderDescriptor[] {
299
+ export function getProvidersByCategory(category: ProviderCategory): readonly ProviderDescriptor[] {
200
300
  return PROVIDERS.filter((p) => p.category === category);
201
301
  }
202
302
 
203
- export function getProvidersByTier(
204
- tier: ProviderTier,
205
- ): readonly ProviderDescriptor[] {
303
+ export function getProvidersByTier(tier: ProviderTier): readonly ProviderDescriptor[] {
206
304
  return PROVIDERS.filter((p) => p.tier === tier);
207
305
  }
208
306
 
@@ -230,7 +328,7 @@ function readConfigValueByPath(
230
328
  // `hasCredential` reads from `getConfig()` so that tests mocking `getConfig`
231
329
  // see a consistent view; `getCredentialSource` reads `process.env` +
232
330
  // `loadFileConfig` directly because it needs to distinguish env from file.
233
- const CONFIG_FIELD_BY_ID: Record<ProviderId, keyof ReturnType<typeof getConfig>> = {
331
+ const CONFIG_FIELD_BY_ID: Record<ApiKeyProviderId, keyof ReturnType<typeof getConfig>> = {
234
332
  alpha_vantage: "alphaVantageApiKey",
235
333
  fred: "fredApiKey",
236
334
  finnhub: "finnhubApiKey",
@@ -239,14 +337,14 @@ const CONFIG_FIELD_BY_ID: Record<ProviderId, keyof ReturnType<typeof getConfig>>
239
337
  };
240
338
 
241
339
  export function hasCredential(id: ProviderId): boolean {
242
- const field = CONFIG_FIELD_BY_ID[id];
340
+ const descriptor = getProvider(id);
341
+ if (!isApiKeyProvider(descriptor)) return false;
342
+ const field = CONFIG_FIELD_BY_ID[descriptor.id];
243
343
  const value = getConfig()[field];
244
344
  return typeof value === "string" && value.length > 0;
245
345
  }
246
346
 
247
- export function getCredentialSource(
248
- id: ProviderId,
249
- ): "env" | "file" | "absent" {
347
+ export function getCredentialSource(id: ProviderId): "env" | "file" | "absent" {
250
348
  return getCredential(id).source;
251
349
  }
252
350
 
@@ -254,6 +352,8 @@ export function getCredential(
254
352
  id: ProviderId,
255
353
  ): { source: "env" | "file"; value: string } | { source: "absent"; value?: undefined } {
256
354
  const descriptor = getProvider(id);
355
+ if (!isApiKeyProvider(descriptor)) return { source: "absent" };
356
+
257
357
  const envValue = process.env[descriptor.envVar];
258
358
  if (envValue && envValue.length > 0) return { source: "env", value: envValue };
259
359
 
@@ -271,10 +371,7 @@ export function getCredential(
271
371
 
272
372
  export function resolveProviderFromArgument(
273
373
  arg: string,
274
- ):
275
- | ProviderDescriptor
276
- | readonly ProviderDescriptor[]
277
- | undefined {
374
+ ): ProviderDescriptor | readonly ProviderDescriptor[] | undefined {
278
375
  const needle = arg.trim().toLowerCase();
279
376
  if (!needle) return undefined;
280
377
 
@@ -296,6 +393,8 @@ export function resolveProviderFromArgument(
296
393
  "macro",
297
394
  "news",
298
395
  "web_search",
396
+ "sentiment",
397
+ "market",
299
398
  ];
300
399
  const normalizedCategory = needle.replace("-", "_");
301
400
  if ((categories as readonly string[]).includes(normalizedCategory)) {
@@ -53,9 +53,7 @@ function parseEntry(raw: unknown): ProviderOnboardingEntry | undefined {
53
53
  return { status: "never_ask", lastPromptAt };
54
54
  }
55
55
 
56
- function parseProvidersMap(
57
- raw: unknown,
58
- ): Partial<Record<ProviderId, ProviderOnboardingEntry>> {
56
+ function parseProvidersMap(raw: unknown): Partial<Record<ProviderId, ProviderOnboardingEntry>> {
59
57
  if (!raw || typeof raw !== "object") return {};
60
58
  const result: Partial<Record<ProviderId, ProviderOnboardingEntry>> = {};
61
59
  for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
@@ -96,10 +94,7 @@ export function loadOnboardingState(path = getOnboardingPath()): OnboardingState
96
94
  return { version, welcomeShownAt, providers };
97
95
  }
98
96
 
99
- export function saveOnboardingState(
100
- state: OnboardingState,
101
- path = getOnboardingPath(),
102
- ): void {
97
+ export function saveOnboardingState(state: OnboardingState, path = getOnboardingPath()): void {
103
98
  ensureParentDir(path);
104
99
  // Strip undefined fields for cleaner on-disk output.
105
100
  const serializable: Record<string, unknown> = {
@@ -120,10 +115,7 @@ function nowIso(): string {
120
115
  return new Date().toISOString();
121
116
  }
122
117
 
123
- export function markProviderCompleted(
124
- state: OnboardingState,
125
- id: ProviderId,
126
- ): OnboardingState {
118
+ export function markProviderCompleted(state: OnboardingState, id: ProviderId): OnboardingState {
127
119
  return {
128
120
  ...state,
129
121
  providers: {
@@ -153,10 +145,7 @@ export function markProviderSnoozed(
153
145
  };
154
146
  }
155
147
 
156
- export function markProviderNeverAsk(
157
- state: OnboardingState,
158
- id: ProviderId,
159
- ): OnboardingState {
148
+ export function markProviderNeverAsk(state: OnboardingState, id: ProviderId): OnboardingState {
160
149
  return {
161
150
  ...state,
162
151
  providers: {
@@ -166,6 +155,15 @@ export function markProviderNeverAsk(
166
155
  };
167
156
  }
168
157
 
158
+ export function clearProviderOnboardingEntry(
159
+ state: OnboardingState,
160
+ id: ProviderId,
161
+ ): OnboardingState {
162
+ const providers = { ...state.providers };
163
+ delete providers[id];
164
+ return { ...state, providers };
165
+ }
166
+
169
167
  export function markWelcomeShown(state: OnboardingState): OnboardingState {
170
168
  return { ...state, welcomeShownAt: nowIso() };
171
169
  }
@@ -14,10 +14,7 @@
14
14
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
15
15
  import { ProviderCredentialError } from "../providers/provider-credential-error.js";
16
16
  import { getProvider, hasCredential, type ProviderId } from "./providers.js";
17
- import {
18
- buildCredentialRequiredTag,
19
- type CredentialRequiredReason,
20
- } from "./tool-tags.js";
17
+ import { buildCredentialRequiredTag, type CredentialRequiredReason } from "./tool-tags.js";
21
18
 
22
19
  // Metadata carried on `details` for UI / test assertion. This is NOT the
23
20
  // LLM-facing contract — that lives in `content` as the tagged line. Pi
@@ -100,11 +97,7 @@ export async function withCredentialCheck<T>(
100
97
  return await fn();
101
98
  } catch (error) {
102
99
  if (error instanceof ProviderCredentialError) {
103
- return buildCredentialRequiredResult(
104
- error.provider,
105
- error.reason,
106
- error.httpStatus,
107
- );
100
+ return buildCredentialRequiredResult(error.provider, error.reason, error.httpStatus);
108
101
  }
109
102
  throw error;
110
103
  }
@@ -22,6 +22,7 @@ import type { ProviderId } from "./providers.js";
22
22
  // -----------------------------------------------------------------------------
23
23
 
24
24
  export type CredentialRequiredReason = "missing" | "stale";
25
+ export type ExternalToolRequiredReason = "not_installed" | "session_missing" | "session_stale";
25
26
 
26
27
  export interface CredentialRequiredTagFields {
27
28
  provider: ProviderId;
@@ -48,11 +49,20 @@ export interface ConnectedTagFields {
48
49
  provider: ProviderId;
49
50
  }
50
51
 
52
+ export interface ExternalToolRequiredTagFields {
53
+ provider: ProviderId;
54
+ reason: ExternalToolRequiredReason;
55
+ installCmd: string;
56
+ loginCmd?: string;
57
+ fallback: string | null;
58
+ }
59
+
51
60
  export type ParsedTag =
52
61
  | ({ kind: "credential_required" } & CredentialRequiredTagFields)
53
62
  | ({ kind: "soft_degraded" } & SoftDegradedTagFields)
54
63
  | ({ kind: "skipped" } & SkippedTagFields)
55
- | ({ kind: "connected" } & ConnectedTagFields);
64
+ | ({ kind: "connected" } & ConnectedTagFields)
65
+ | ({ kind: "external_tool_required" } & ExternalToolRequiredTagFields);
56
66
 
57
67
  // -----------------------------------------------------------------------------
58
68
  // Builders
@@ -81,12 +91,12 @@ export function buildCredentialRequiredTag(fields: CredentialRequiredTagFields):
81
91
  }
82
92
 
83
93
  export function buildSoftDegradedTag(fields: SoftDegradedTagFields): string {
84
- return [
94
+ return `${[
85
95
  "[OPENCANDLE_SOFT_DEGRADED",
86
96
  formatField("provider", fields.provider),
87
97
  formatField("fallback", fields.fallback),
88
98
  `remediation=${quote(fields.remediation)}`,
89
- ].join(" ") + "]";
99
+ ].join(" ")}]`;
90
100
  }
91
101
 
92
102
  export function buildSkippedTag(fields: SkippedTagFields): string {
@@ -106,19 +116,32 @@ export function buildConnectedTag(fields: ConnectedTagFields): string {
106
116
  return `[OPENCANDLE_CONNECTED provider=${fields.provider}]`;
107
117
  }
108
118
 
119
+ export function buildExternalToolRequiredTag(fields: ExternalToolRequiredTagFields): string {
120
+ const parts: string[] = [
121
+ "[OPENCANDLE_EXTERNAL_TOOL_REQUIRED",
122
+ formatField("provider", fields.provider),
123
+ formatField("reason", fields.reason),
124
+ `installCmd=${quote(fields.installCmd)}`,
125
+ formatField("fallback", fields.fallback ?? "none"),
126
+ ];
127
+ if (fields.loginCmd) parts.push(`loginCmd=${quote(fields.loginCmd)}`);
128
+ return `${parts.join(" ")}]`;
129
+ }
130
+
109
131
  // -----------------------------------------------------------------------------
110
132
  // Parser
111
133
  // -----------------------------------------------------------------------------
112
134
 
113
135
  const TAG_LINE_REGEX =
114
- /\[OPENCANDLE_(CREDENTIAL_REQUIRED|SOFT_DEGRADED|SKIPPED|CONNECTED)([^\]]*)\]/;
136
+ /\[OPENCANDLE_(CREDENTIAL_REQUIRED|SOFT_DEGRADED|SKIPPED|CONNECTED|EXTERNAL_TOOL_REQUIRED)([^\]]*)\]/;
115
137
 
116
138
  function parseFields(raw: string): Record<string, string> {
117
139
  // Split on `key=value` pairs, respecting quoted values.
118
140
  const fields: Record<string, string> = {};
119
141
  const pattern = /(\w+)=("((?:[^"\\]|\\.)*)"|([^\s\]]+))/g;
120
- let match: RegExpExecArray | null;
121
- while ((match = pattern.exec(raw)) !== null) {
142
+ while (true) {
143
+ const match = pattern.exec(raw);
144
+ if (!match) break;
122
145
  const key = match[1];
123
146
  const value = match[3] !== undefined ? match[3].replace(/\\"/g, '"') : match[4];
124
147
  fields[key] = value;
@@ -147,8 +170,7 @@ export function parseToolTag(text: string): ParsedTag | undefined {
147
170
  .split(",")
148
171
  .map((s) => s.trim())
149
172
  .filter((s) => s.length > 0);
150
- const fallback =
151
- fallbackRaw === undefined || fallbackRaw === "none" ? null : fallbackRaw;
173
+ const fallback = fallbackRaw === undefined || fallbackRaw === "none" ? null : fallbackRaw;
152
174
  const parsed: ParsedTag = {
153
175
  kind: "credential_required",
154
176
  provider: provider as ProviderId,
@@ -195,6 +217,27 @@ export function parseToolTag(text: string): ParsedTag | undefined {
195
217
  return { kind: "connected", provider: provider as ProviderId };
196
218
  }
197
219
 
220
+ case "EXTERNAL_TOOL_REQUIRED": {
221
+ const { provider, reason, installCmd, loginCmd, fallback } = fields;
222
+ if (
223
+ !provider ||
224
+ (reason !== "not_installed" &&
225
+ reason !== "session_missing" &&
226
+ reason !== "session_stale") ||
227
+ !installCmd
228
+ ) {
229
+ return undefined;
230
+ }
231
+ return {
232
+ kind: "external_tool_required",
233
+ provider: provider as ProviderId,
234
+ reason,
235
+ installCmd,
236
+ loginCmd,
237
+ fallback: fallback === undefined || fallback === "none" ? null : fallback,
238
+ };
239
+ }
240
+
198
241
  default:
199
242
  return undefined;
200
243
  }
@@ -20,7 +20,7 @@
20
20
  // All validators share a 5-second timeout via `AbortSignal.timeout` so the
21
21
  // connect flow cannot hang if a provider is unreachable.
22
22
 
23
- import type { ProviderId } from "./providers.js";
23
+ import type { ApiKeyProviderId } from "./providers.js";
24
24
 
25
25
  const VALIDATION_TIMEOUT_MS = 5_000;
26
26
 
@@ -89,7 +89,7 @@ function classifyAlphaVantageBody(body: string): ValidationResult | undefined {
89
89
  if (typeof errorMessage === "string" && errorMessage.length > 0) {
90
90
  return { status: "invalid", message: errorMessage };
91
91
  }
92
- const information = parsed["Information"];
92
+ const information = parsed.Information;
93
93
  if (typeof information === "string" && /invalid api/i.test(information)) {
94
94
  return { status: "invalid", message: information };
95
95
  }
@@ -107,7 +107,7 @@ function classifyAlphaVantageBody(body: string): ValidationResult | undefined {
107
107
  * touch the config file, onboarding state, or any in-memory cache.
108
108
  */
109
109
  export async function validateCredential(
110
- providerId: ProviderId,
110
+ providerId: ApiKeyProviderId,
111
111
  key: string,
112
112
  ): Promise<ValidationResult> {
113
113
  switch (providerId) {
@@ -131,28 +131,22 @@ export async function validateCredential(
131
131
  );
132
132
 
133
133
  case "brave":
134
- return validateWithFetch(
135
- "https://api.search.brave.com/res/v1/web/search?q=test&count=1",
136
- {
137
- method: "GET",
138
- headers: {
139
- "X-Subscription-Token": key,
140
- Accept: "application/json",
141
- },
134
+ return validateWithFetch("https://api.search.brave.com/res/v1/web/search?q=test&count=1", {
135
+ method: "GET",
136
+ headers: {
137
+ "X-Subscription-Token": key,
138
+ Accept: "application/json",
142
139
  },
143
- );
140
+ });
144
141
 
145
142
  case "exa":
146
- return validateWithFetch(
147
- "https://api.exa.ai/search",
148
- {
149
- method: "POST",
150
- headers: {
151
- "Content-Type": "application/json",
152
- "x-api-key": key,
153
- },
154
- body: JSON.stringify({ query: "test", numResults: 1 }),
143
+ return validateWithFetch("https://api.exa.ai/search", {
144
+ method: "POST",
145
+ headers: {
146
+ "Content-Type": "application/json",
147
+ "x-api-key": key,
155
148
  },
156
- );
149
+ body: JSON.stringify({ query: "test", numResults: 1 }),
150
+ });
157
151
  }
158
152
  }