opencandle 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -187
- package/dist/analysts/contracts.d.ts +1 -3
- package/dist/analysts/contracts.js +1 -11
- package/dist/analysts/contracts.js.map +1 -1
- package/dist/analysts/orchestrator.d.ts +1 -3
- package/dist/analysts/orchestrator.js +1 -26
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.js +30 -7
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +12 -5
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/browser.js +3 -1
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/cache.d.ts +8 -11
- package/dist/infra/cache.js +17 -15
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/http-client.d.ts +4 -1
- package/dist/infra/http-client.js +59 -6
- package/dist/infra/http-client.js.map +1 -1
- package/dist/infra/index.d.ts +3 -3
- package/dist/infra/index.js +3 -3
- package/dist/infra/index.js.map +1 -1
- package/dist/infra/native-dependencies.js +2 -2
- package/dist/infra/native-dependencies.js.map +1 -1
- package/dist/infra/node-version.js.map +1 -1
- package/dist/infra/opencandle-paths.d.ts +0 -3
- package/dist/infra/opencandle-paths.js +4 -11
- package/dist/infra/opencandle-paths.js.map +1 -1
- package/dist/infra/rate-limiter.js +12 -9
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/alert-conditions.d.ts +34 -0
- package/dist/market-state/alert-conditions.js +23 -0
- package/dist/market-state/alert-conditions.js.map +1 -0
- package/dist/market-state/alert-runner.d.ts +55 -0
- package/dist/market-state/alert-runner.js +634 -0
- package/dist/market-state/alert-runner.js.map +1 -0
- package/dist/market-state/daily-report.d.ts +26 -0
- package/dist/market-state/daily-report.js +179 -0
- package/dist/market-state/daily-report.js.map +1 -0
- package/dist/market-state/local-automation-service.d.ts +25 -0
- package/dist/market-state/local-automation-service.js +119 -0
- package/dist/market-state/local-automation-service.js.map +1 -0
- package/dist/market-state/notification-delivery.d.ts +14 -0
- package/dist/market-state/notification-delivery.js +139 -0
- package/dist/market-state/notification-delivery.js.map +1 -0
- package/dist/market-state/resolve-for-mutation.d.ts +10 -0
- package/dist/market-state/resolve-for-mutation.js +15 -0
- package/dist/market-state/resolve-for-mutation.js.map +1 -0
- package/dist/market-state/resolve.d.ts +14 -0
- package/dist/market-state/resolve.js +89 -0
- package/dist/market-state/resolve.js.map +1 -0
- package/dist/market-state/service.d.ts +527 -0
- package/dist/market-state/service.js +1099 -0
- package/dist/market-state/service.js.map +1 -0
- package/dist/memory/index.d.ts +7 -7
- package/dist/memory/index.js +6 -6
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.js +11 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/retrieval.js +7 -4
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/sqlite.js +385 -3
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.js +1 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.js +64 -28
- package/dist/memory/tool-defaults.js.map +1 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/monitor.d.ts +2 -0
- package/dist/monitor.js +104 -0
- package/dist/monitor.js.map +1 -0
- package/dist/onboarding/connect.js +4 -6
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/credential-interceptor.js +1 -1
- package/dist/onboarding/credential-interceptor.js.map +1 -1
- package/dist/onboarding/degradation-accumulator.js +1 -3
- package/dist/onboarding/degradation-accumulator.js.map +1 -1
- package/dist/onboarding/providers.js +3 -16
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.js +1 -1
- package/dist/onboarding/tool-helpers.js.map +1 -1
- package/dist/onboarding/tool-tags.js +6 -4
- package/dist/onboarding/tool-tags.js.map +1 -1
- package/dist/onboarding/validation.js +1 -1
- package/dist/onboarding/validation.js.map +1 -1
- package/dist/pi/opencandle-extension.d.ts +8 -0
- package/dist/pi/opencandle-extension.js +412 -28
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session.d.ts +1 -1
- package/dist/pi/session.js +3 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.js +8 -3
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.js +5 -2
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +1 -1
- package/dist/prompts/context-builder.js +19 -6
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +1 -1
- package/dist/prompts/policy-cards.js +1 -1
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/prompts/sections.d.ts +1 -1
- package/dist/prompts/symbol-preflight.d.ts +20 -0
- package/dist/prompts/symbol-preflight.js +49 -0
- package/dist/prompts/symbol-preflight.js.map +1 -0
- package/dist/prompts/workflow-prompts.d.ts +1 -1
- package/dist/prompts/workflow-prompts.js +54 -16
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.d.ts +1 -1
- package/dist/providers/alpha-vantage.js +26 -7
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -1
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/errors.d.ts +5 -0
- package/dist/providers/errors.js +11 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/exa-search.d.ts +2 -2
- package/dist/providers/exa-search.js +19 -11
- package/dist/providers/exa-search.js.map +1 -1
- package/dist/providers/fear-greed.js +1 -1
- package/dist/providers/fear-greed.js.map +1 -1
- package/dist/providers/finnhub.js +3 -5
- package/dist/providers/finnhub.js.map +1 -1
- package/dist/providers/fred.js +2 -2
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +7 -6
- package/dist/providers/index.js +6 -5
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/reddit.js +2 -2
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +1 -0
- package/dist/providers/sec-edgar.js +12 -4
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/tradingview.d.ts +47 -0
- package/dist/providers/tradingview.js +275 -0
- package/dist/providers/tradingview.js.map +1 -0
- package/dist/providers/twitter.js +6 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.js +26 -12
- package/dist/providers/web-search.js.map +1 -1
- package/dist/providers/with-fallback.js +4 -2
- package/dist/providers/with-fallback.js.map +1 -1
- package/dist/providers/wrap-provider.d.ts +2 -3
- package/dist/providers/wrap-provider.js +14 -8
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +1 -1
- package/dist/providers/yahoo-finance.js +101 -17
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +6 -0
- package/dist/routing/classify-intent.js +78 -7
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.d.ts +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +234 -29
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/fund-symbols.d.ts +2 -0
- package/dist/routing/fund-symbols.js +55 -0
- package/dist/routing/fund-symbols.js.map +1 -0
- package/dist/routing/horizon.d.ts +1 -0
- package/dist/routing/horizon.js +10 -0
- package/dist/routing/horizon.js.map +1 -0
- package/dist/routing/index.d.ts +10 -10
- package/dist/routing/index.js +6 -6
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/planning.d.ts +1 -1
- package/dist/routing/planning.js +65 -34
- package/dist/routing/planning.js.map +1 -1
- package/dist/routing/route-manifest.d.ts +2 -2
- package/dist/routing/route-manifest.js +25 -4
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +7 -9
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +1 -0
- package/dist/routing/router.js +137 -22
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -1
- package/dist/routing/slot-resolver.js +2 -4
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/symbol-disambiguator.d.ts +11 -0
- package/dist/routing/symbol-disambiguator.js +52 -0
- package/dist/routing/symbol-disambiguator.js.map +1 -0
- package/dist/routing/turn-context.d.ts +1 -1
- package/dist/routing/turn-context.js +1 -1
- package/dist/routing/turn-context.js.map +1 -1
- package/dist/routing/types.d.ts +2 -0
- package/dist/runtime/answer-contracts.js +36 -8
- package/dist/runtime/answer-contracts.js.map +1 -1
- package/dist/runtime/artifact-contracts.js.map +1 -1
- package/dist/runtime/planning-evidence.js +47 -26
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +1 -9
- package/dist/runtime/prompt-step.js +0 -10
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/run-context.d.ts +5 -2
- package/dist/runtime/run-context.js +8 -1
- package/dist/runtime/run-context.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +13 -5
- package/dist/runtime/session-coordinator.js +160 -20
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/session-title.d.ts +14 -0
- package/dist/runtime/session-title.js +50 -0
- package/dist/runtime/session-title.js.map +1 -0
- package/dist/runtime/tool-defaults-wrapper.js +1 -3
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
- package/dist/runtime/validation.js.map +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-runner.d.ts +3 -3
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/runtime/workflow-runner.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +1 -1
- package/dist/sentiment/adapters/finnhub.js +6 -1
- package/dist/sentiment/adapters/finnhub.js.map +1 -1
- package/dist/sentiment/adapters/reddit.d.ts +2 -2
- package/dist/sentiment/adapters/twitter.d.ts +1 -1
- package/dist/sentiment/adapters/web.d.ts +1 -1
- package/dist/sentiment/index.d.ts +9 -11
- package/dist/sentiment/index.js +9 -20
- package/dist/sentiment/index.js.map +1 -1
- package/dist/sentiment/keywords.js +26 -4
- package/dist/sentiment/keywords.js.map +1 -1
- package/dist/sentiment/pipeline.d.ts +2 -2
- package/dist/sentiment/pipeline.js +1 -1
- package/dist/sentiment/pipeline.js.map +1 -1
- package/dist/sentiment/scorer.js +1 -1
- package/dist/sentiment/store.d.ts +1 -1
- package/dist/sentiment/store.js +1 -1
- package/dist/sentiment/store.js.map +1 -1
- package/dist/sentiment/trends.d.ts +1 -1
- package/dist/sentiment/trends.js.map +1 -1
- package/dist/sentiment/types.js.map +1 -1
- package/dist/system-prompt.js +3 -2
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +7 -7
- package/dist/tool-kit.js +4 -4
- package/dist/tool-kit.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.js +11 -6
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +18 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +23 -11
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +8 -3
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +8 -3
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.js +21 -6
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +23 -19
- package/dist/tools/index.js +51 -39
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +15 -3
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/interaction/twitter-login.js +13 -3
- package/dist/tools/interaction/twitter-login.js.map +1 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +1 -1
- package/dist/tools/macro/fred-data.js +17 -6
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +3 -1
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +3 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.d.ts +18 -0
- package/dist/tools/market/screen-stocks.js +252 -0
- package/dist/tools/market/screen-stocks.js.map +1 -0
- package/dist/tools/market/search-ticker.js +160 -8
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +2 -2
- package/dist/tools/market/stock-history.js +26 -7
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +5 -3
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +1 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +19 -6
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/alerts.d.ts +15 -0
- package/dist/tools/portfolio/alerts.js +357 -0
- package/dist/tools/portfolio/alerts.js.map +1 -0
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +33 -13
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/daily-report.d.ts +8 -0
- package/dist/tools/portfolio/daily-report.js +83 -0
- package/dist/tools/portfolio/daily-report.js.map +1 -0
- package/dist/tools/portfolio/holdings-overlap.js +10 -3
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
- package/dist/tools/portfolio/notifications.d.ts +7 -0
- package/dist/tools/portfolio/notifications.js +43 -0
- package/dist/tools/portfolio/notifications.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +12 -6
- package/dist/tools/portfolio/predictions.js +337 -87
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.js +45 -6
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +4 -3
- package/dist/tools/portfolio/tracker.js +246 -101
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +6 -4
- package/dist/tools/portfolio/watchlist.js +208 -108
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +23 -10
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +15 -13
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-trend.js +12 -2
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +12 -5
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
- package/dist/tools/sentiment/untrusted-text.js +17 -0
- package/dist/tools/sentiment/untrusted-text.js.map +1 -0
- package/dist/tools/sentiment/web-search.js +9 -13
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +15 -3
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/backtest.js +27 -20
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +23 -5
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/market.d.ts +1 -0
- package/dist/types/portfolio.d.ts +14 -4
- package/dist/workflows/compare-assets.d.ts +0 -3
- package/dist/workflows/compare-assets.js +20 -11
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -4
- package/dist/workflows/index.js +3 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/options-screener.d.ts +0 -3
- package/dist/workflows/options-screener.js +4 -11
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +0 -3
- package/dist/workflows/portfolio-builder.js +0 -8
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +1 -1
- package/gui/server/automation-heartbeat.ts +97 -0
- package/gui/server/background-quotes.ts +97 -1
- package/gui/server/chat-event-adapter.ts +32 -10
- package/gui/server/chat-run-session.ts +16 -0
- package/gui/server/invoke-tool.ts +144 -1
- package/gui/server/live-chat-event-adapter.ts +21 -6
- package/gui/server/market-state-api.ts +315 -0
- package/gui/server/model-setup.ts +149 -2
- package/gui/server/private-api-access.ts +62 -0
- package/gui/server/projector.ts +12 -7
- package/gui/server/prompt-observation.ts +4 -7
- package/gui/server/quote-snapshot-store.ts +50 -0
- package/gui/server/server.ts +200 -451
- package/gui/server/session-actions.ts +186 -1
- package/gui/server/shutdown.ts +47 -0
- package/gui/server/tool-invoke-ack.ts +49 -0
- package/gui/server/tool-metadata.ts +23 -10
- package/gui/server/websocket.ts +13 -3
- package/gui/server/writer-lock.ts +6 -2
- package/gui/server/ws-hub.ts +292 -0
- package/gui/shared/chat-events.ts +16 -1
- package/gui/shared/event-reducer.ts +24 -6
- package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
- package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
- package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +5 -1
- package/src/analysts/contracts.ts +10 -23
- package/src/analysts/orchestrator.ts +8 -43
- package/src/cli.ts +35 -12
- package/src/config.ts +17 -9
- package/src/index.ts +1 -1
- package/src/infra/browser.ts +3 -1
- package/src/infra/cache.ts +41 -30
- package/src/infra/http-client.ts +72 -6
- package/src/infra/index.ts +7 -10
- package/src/infra/native-dependencies.ts +8 -3
- package/src/infra/node-version.ts +3 -1
- package/src/infra/opencandle-paths.ts +3 -14
- package/src/infra/rate-limiter.ts +22 -19
- package/src/market-state/alert-conditions.ts +82 -0
- package/src/market-state/alert-runner.ts +863 -0
- package/src/market-state/daily-report.ts +247 -0
- package/src/market-state/local-automation-service.ts +162 -0
- package/src/market-state/notification-delivery.ts +158 -0
- package/src/market-state/resolve-for-mutation.ts +24 -0
- package/src/market-state/resolve.ts +112 -0
- package/src/market-state/service.ts +2344 -0
- package/src/memory/index.ts +7 -7
- package/src/memory/manager.ts +14 -16
- package/src/memory/retrieval.ts +8 -7
- package/src/memory/sqlite.ts +407 -6
- package/src/memory/storage.ts +5 -15
- package/src/memory/tool-defaults.ts +60 -39
- package/src/memory/types.ts +3 -3
- package/src/monitor.ts +121 -0
- package/src/onboarding/connect.ts +10 -33
- package/src/onboarding/credential-interceptor.ts +3 -15
- package/src/onboarding/degradation-accumulator.ts +1 -3
- package/src/onboarding/providers.ts +9 -40
- package/src/onboarding/state.ts +4 -15
- package/src/onboarding/tool-helpers.ts +2 -9
- package/src/onboarding/tool-tags.ts +6 -6
- package/src/onboarding/validation.ts +14 -20
- package/src/pi/opencandle-extension.ts +529 -85
- package/src/pi/session.ts +7 -5
- package/src/pi/setup.ts +61 -43
- package/src/pi/tool-adapter.ts +5 -2
- package/src/prompts/context-builder.ts +23 -12
- package/src/prompts/policy-cards.ts +2 -2
- package/src/prompts/sections.ts +1 -1
- package/src/prompts/symbol-preflight.ts +80 -0
- package/src/prompts/workflow-prompts.ts +77 -28
- package/src/providers/alpha-vantage.ts +58 -39
- package/src/providers/coingecko.ts +2 -5
- package/src/providers/errors.ts +9 -0
- package/src/providers/exa-search.ts +24 -22
- package/src/providers/fear-greed.ts +1 -1
- package/src/providers/finnhub.ts +7 -6
- package/src/providers/fred.ts +3 -3
- package/src/providers/index.ts +14 -6
- package/src/providers/reddit.ts +17 -6
- package/src/providers/sec-edgar.ts +20 -6
- package/src/providers/tradingview.ts +399 -0
- package/src/providers/twitter.ts +6 -8
- package/src/providers/web-search.ts +30 -20
- package/src/providers/with-fallback.ts +8 -7
- package/src/providers/wrap-provider.ts +15 -10
- package/src/providers/yahoo-finance.ts +140 -35
- package/src/routing/classify-intent.ts +101 -10
- package/src/routing/defaults.ts +1 -1
- package/src/routing/entity-extractor.ts +287 -38
- package/src/routing/fund-symbols.ts +58 -0
- package/src/routing/horizon.ts +7 -0
- package/src/routing/index.ts +48 -48
- package/src/routing/planning.ts +144 -53
- package/src/routing/route-manifest.ts +37 -15
- package/src/routing/router-llm-client.ts +4 -4
- package/src/routing/router-prompt.ts +15 -19
- package/src/routing/router-types.ts +2 -5
- package/src/routing/router.ts +251 -53
- package/src/routing/slot-resolver.ts +34 -11
- package/src/routing/symbol-disambiguator.ts +72 -0
- package/src/routing/turn-context.ts +6 -9
- package/src/routing/types.ts +2 -0
- package/src/runtime/answer-contracts.ts +82 -43
- package/src/runtime/artifact-contracts.ts +2 -1
- package/src/runtime/planning-evidence.ts +157 -66
- package/src/runtime/prompt-step.ts +1 -16
- package/src/runtime/run-context.ts +12 -2
- package/src/runtime/session-coordinator.ts +238 -63
- package/src/runtime/session-title.ts +60 -0
- package/src/runtime/tool-defaults-wrapper.ts +1 -3
- package/src/runtime/validation.ts +1 -4
- package/src/runtime/workflow-events.ts +7 -7
- package/src/runtime/workflow-runner.ts +5 -11
- package/src/sentiment/adapters/finnhub.ts +7 -2
- package/src/sentiment/adapters/reddit.ts +2 -2
- package/src/sentiment/adapters/twitter.ts +1 -1
- package/src/sentiment/adapters/web.ts +1 -1
- package/src/sentiment/index.ts +16 -26
- package/src/sentiment/keywords.ts +26 -4
- package/src/sentiment/pipeline.ts +15 -4
- package/src/sentiment/scorer.ts +1 -1
- package/src/sentiment/store.ts +2 -2
- package/src/sentiment/trends.ts +9 -3
- package/src/sentiment/types.ts +5 -4
- package/src/system-prompt.ts +3 -2
- package/src/tool-kit.ts +10 -9
- package/src/tools/fundamentals/company-overview.ts +19 -9
- package/src/tools/fundamentals/comps.ts +68 -55
- package/src/tools/fundamentals/dcf.ts +145 -95
- package/src/tools/fundamentals/earnings.ts +16 -6
- package/src/tools/fundamentals/financials.ts +16 -7
- package/src/tools/fundamentals/sec-filings.ts +37 -16
- package/src/tools/index.ts +51 -39
- package/src/tools/interaction/ask-user.ts +22 -10
- package/src/tools/interaction/twitter-login.ts +17 -5
- package/src/tools/macro/fear-greed.ts +1 -1
- package/src/tools/macro/fred-data.ts +58 -46
- package/src/tools/market/crypto-history.ts +8 -3
- package/src/tools/market/crypto-price.ts +6 -6
- package/src/tools/market/screen-stocks.ts +279 -0
- package/src/tools/market/search-ticker.ts +218 -17
- package/src/tools/market/stock-history.ts +37 -12
- package/src/tools/market/stock-quote.ts +10 -7
- package/src/tools/options/greeks.ts +5 -5
- package/src/tools/options/option-chain.ts +41 -17
- package/src/tools/portfolio/alerts.ts +457 -0
- package/src/tools/portfolio/correlation.ts +47 -20
- package/src/tools/portfolio/daily-report.ts +101 -0
- package/src/tools/portfolio/holdings-overlap.ts +31 -15
- package/src/tools/portfolio/notifications.ts +45 -0
- package/src/tools/portfolio/predictions.ts +406 -106
- package/src/tools/portfolio/risk-analysis.ts +46 -7
- package/src/tools/portfolio/tracker.ts +270 -109
- package/src/tools/portfolio/watchlist.ts +250 -121
- package/src/tools/sentiment/reddit-sentiment.ts +50 -24
- package/src/tools/sentiment/sentiment-summary.ts +62 -41
- package/src/tools/sentiment/sentiment-trend.ts +24 -7
- package/src/tools/sentiment/twitter-sentiment.ts +22 -15
- package/src/tools/sentiment/untrusted-text.ts +21 -0
- package/src/tools/sentiment/web-search.ts +21 -18
- package/src/tools/sentiment/web-sentiment.ts +26 -10
- package/src/tools/technical/backtest.ts +32 -22
- package/src/tools/technical/indicators.ts +39 -14
- package/src/types/index.ts +8 -3
- package/src/types/market.ts +1 -0
- package/src/types/portfolio.ts +14 -4
- package/src/types/sentiment.ts +2 -2
- package/src/workflows/compare-assets.ts +33 -21
- package/src/workflows/index.ts +3 -4
- package/src/workflows/options-screener.ts +27 -29
- package/src/workflows/portfolio-builder.ts +34 -27
- package/dist/workflows/types.d.ts +0 -4
- package/dist/workflows/types.js +0 -2
- package/dist/workflows/types.js.map +0 -1
- package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
- package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
- package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
- package/src/workflows/types.ts +0 -4
package/gui/web/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-CveNgtDg.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-2KZtKBmu.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencandle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Financial trading & investing agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/Kahtaf/OpenCandle#readme",
|
|
@@ -90,6 +90,8 @@
|
|
|
90
90
|
"docs:site:serve": "node website/serve.mjs",
|
|
91
91
|
"gui": "tsx gui/server/server.ts",
|
|
92
92
|
"gui:dev": "tsx gui/server/server.ts",
|
|
93
|
+
"lint": "biome check .",
|
|
94
|
+
"format": "biome check --write .",
|
|
93
95
|
"pretest": "npm run check:node",
|
|
94
96
|
"test": "vitest run",
|
|
95
97
|
"test:watch": "vitest",
|
|
@@ -107,6 +109,7 @@
|
|
|
107
109
|
"test:evals:product": "tsx tests/scripts/run-product-evals.ts",
|
|
108
110
|
"test:evals:competitive": "tsx tests/scripts/run-competitive-finance-eval.ts",
|
|
109
111
|
"eval:competitive:analyze": "tsx tests/scripts/analyze-competitive-finance-report.ts",
|
|
112
|
+
"review:pr": ".agents/skills/autoreview/scripts/autoreview --mode branch --prompt-file .agents/skills/autoreview/references/opencandle-review.md --parallel-tests \"npx tsc --noEmit && npx vitest run\"",
|
|
110
113
|
"version:patch": "npm version patch --no-git-tag-version",
|
|
111
114
|
"version:minor": "npm version minor --no-git-tag-version",
|
|
112
115
|
"version:major": "npm version major --no-git-tag-version",
|
|
@@ -136,6 +139,7 @@
|
|
|
136
139
|
},
|
|
137
140
|
"devDependencies": {
|
|
138
141
|
"@agentclientprotocol/claude-agent-acp": "^0.37.0",
|
|
142
|
+
"@biomejs/biome": "2.5.0",
|
|
139
143
|
"@sinclair/typebox": "^0.34.0",
|
|
140
144
|
"@types/better-sqlite3": "^7.6.13",
|
|
141
145
|
"@types/node": "^22.19.19",
|
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
import type { AnalystOutput, AnalystSignal, DebateSide, DebateOutput } from "../runtime/workflow-types.js";
|
|
2
1
|
import type { EvidenceRecord } from "../runtime/evidence.js";
|
|
2
|
+
import type {
|
|
3
|
+
AnalystOutput,
|
|
4
|
+
AnalystSignal,
|
|
5
|
+
DebateOutput,
|
|
6
|
+
DebateSide,
|
|
7
|
+
} from "../runtime/workflow-types.js";
|
|
3
8
|
|
|
4
9
|
/** All analyst roles. */
|
|
5
|
-
export type AnalystRole =
|
|
6
|
-
| "valuation"
|
|
7
|
-
| "momentum"
|
|
8
|
-
| "options"
|
|
9
|
-
| "contrarian"
|
|
10
|
-
| "risk";
|
|
11
|
-
|
|
12
|
-
/** Evidence fields expected per analyst role. */
|
|
13
|
-
export const ROLE_EXPECTED_EVIDENCE: Record<AnalystRole, string[]> = {
|
|
14
|
-
valuation: ["P/E Ratio", "Forward P/E", "EPS", "Intrinsic Value", "Revenue Growth"],
|
|
15
|
-
momentum: ["RSI", "MACD", "SMA 50", "SMA 200", "Volume Trend"],
|
|
16
|
-
options: ["Put/Call Ratio", "IV Level", "Unusual Volume", "Max Pain"],
|
|
17
|
-
contrarian: ["Fear & Greed Index", "Reddit Sentiment", "Sentiment Score"],
|
|
18
|
-
risk: ["Annualized Volatility", "Sharpe Ratio", "Max Drawdown", "VaR 95%", "Position Size"],
|
|
19
|
-
};
|
|
10
|
+
export type AnalystRole = "valuation" | "momentum" | "options" | "contrarian" | "risk";
|
|
20
11
|
|
|
21
12
|
/**
|
|
22
13
|
* Parse an LLM response into a structured AnalystOutput.
|
|
23
14
|
* Falls back to raw text if parsing fails.
|
|
24
15
|
*/
|
|
25
|
-
export function parseAnalystOutput(
|
|
26
|
-
role: string,
|
|
27
|
-
responseText: string,
|
|
28
|
-
): AnalystOutput {
|
|
16
|
+
export function parseAnalystOutput(role: string, responseText: string): AnalystOutput {
|
|
29
17
|
const signal = extractSignal(responseText);
|
|
30
18
|
const conviction = extractConviction(responseText);
|
|
31
19
|
const thesis = extractThesis(responseText);
|
|
@@ -91,9 +79,8 @@ export function tallyVotes(outputs: AnalystOutput[]): {
|
|
|
91
79
|
weightedSum += signalValue * output.conviction;
|
|
92
80
|
}
|
|
93
81
|
|
|
94
|
-
const weightedConviction =
|
|
95
|
-
? Math.round((totalWeight / outputs.length) * 10) / 10
|
|
96
|
-
: 0;
|
|
82
|
+
const weightedConviction =
|
|
83
|
+
totalWeight > 0 ? Math.round((totalWeight / outputs.length) * 10) / 10 : 0;
|
|
97
84
|
|
|
98
85
|
let verdict: AnalystSignal;
|
|
99
86
|
if (weightedSum > 0) verdict = "BUY";
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
| "contrarian"
|
|
6
|
-
| "risk";
|
|
1
|
+
import type { WorkflowDefinition } from "../runtime/prompt-step.js";
|
|
2
|
+
import { promptStep } from "../runtime/prompt-step.js";
|
|
3
|
+
|
|
4
|
+
export type AnalystRole = "valuation" | "momentum" | "options" | "contrarian" | "risk";
|
|
7
5
|
|
|
8
6
|
const SYMBOL_CAPTURE = "(\\$?[A-Za-z]{1,5}(?:[./-][A-Za-z]{1,2})?)";
|
|
9
7
|
const NORMALIZED_SYMBOL_PATTERN = /^[A-Z]{1,5}(?:[./-][A-Z]{1,2})?$/;
|
|
@@ -173,33 +171,10 @@ export interface ComprehensiveAnalysisOptions {
|
|
|
173
171
|
debate?: boolean;
|
|
174
172
|
}
|
|
175
173
|
|
|
176
|
-
export function
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const role of roles) {
|
|
182
|
-
prompts.push(ANALYST_PROMPTS[role](symbol));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (debate) {
|
|
186
|
-
prompts.push(buildBullPrompt(symbol));
|
|
187
|
-
prompts.push(buildBearPrompt(symbol));
|
|
188
|
-
prompts.push(buildRebuttalPrompt(symbol));
|
|
189
|
-
prompts.push(buildSynthesisPrompt(symbol));
|
|
190
|
-
prompts.push(VALIDATION_PROMPT_DEBATE(symbol));
|
|
191
|
-
} else {
|
|
192
|
-
prompts.push(SYNTHESIS_PROMPT_NO_DEBATE(symbol));
|
|
193
|
-
prompts.push(VALIDATION_PROMPT_NO_DEBATE(symbol));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return prompts;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
import type { WorkflowDefinition } from "../runtime/prompt-step.js";
|
|
200
|
-
import { promptStep } from "../runtime/prompt-step.js";
|
|
201
|
-
|
|
202
|
-
export function buildComprehensiveAnalysisDefinition(symbol: string, options?: ComprehensiveAnalysisOptions): WorkflowDefinition {
|
|
174
|
+
export function buildComprehensiveAnalysisDefinition(
|
|
175
|
+
symbol: string,
|
|
176
|
+
options?: ComprehensiveAnalysisOptions,
|
|
177
|
+
): WorkflowDefinition {
|
|
203
178
|
const debate = options?.debate ?? true;
|
|
204
179
|
const roles: AnalystRole[] = ["valuation", "momentum", "options", "contrarian", "risk"];
|
|
205
180
|
|
|
@@ -265,16 +240,6 @@ export function buildComprehensiveAnalysisDefinition(symbol: string, options?: C
|
|
|
265
240
|
};
|
|
266
241
|
}
|
|
267
242
|
|
|
268
|
-
export function runComprehensiveAnalysis(
|
|
269
|
-
enqueueFollowUp: (prompt: string) => void,
|
|
270
|
-
symbol: string,
|
|
271
|
-
options?: ComprehensiveAnalysisOptions,
|
|
272
|
-
): void {
|
|
273
|
-
for (const prompt of getComprehensiveAnalysisPrompts(symbol, options).slice(1)) {
|
|
274
|
-
enqueueFollowUp(prompt);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
243
|
export function isAnalysisRequest(input: string): { match: boolean; symbol?: string } {
|
|
279
244
|
const patterns = [
|
|
280
245
|
new RegExp(`^analyze\\s+${SYMBOL_CAPTURE}\\s*$`, "i"),
|
package/src/cli.ts
CHANGED
|
@@ -3,22 +3,21 @@ import "./infra/node-version.js";
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
|
-
import { parseArgs } from "node:util";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
7
|
import {
|
|
9
8
|
AuthStorage,
|
|
10
|
-
DefaultPackageManager,
|
|
11
|
-
InteractiveMode,
|
|
12
|
-
ModelRegistry,
|
|
13
|
-
SettingsManager,
|
|
14
9
|
createAgentSessionRuntime,
|
|
15
10
|
createAgentSessionServices,
|
|
11
|
+
DefaultPackageManager,
|
|
16
12
|
getAgentDir,
|
|
13
|
+
InteractiveMode,
|
|
17
14
|
initTheme,
|
|
15
|
+
ModelRegistry,
|
|
16
|
+
SettingsManager,
|
|
18
17
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { loadEnv } from "./config.js";
|
|
19
19
|
import { createOpenCandleSession } from "./pi/session.js";
|
|
20
20
|
import { continueOpenCandleSession } from "./pi/session-storage.js";
|
|
21
|
-
import { loadEnv } from "./config.js";
|
|
22
21
|
|
|
23
22
|
const require = createRequire(import.meta.url);
|
|
24
23
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -29,10 +28,7 @@ async function handlePackageCommand(
|
|
|
29
28
|
agentDir: string,
|
|
30
29
|
): Promise<boolean> {
|
|
31
30
|
const [command, ...rest] = args;
|
|
32
|
-
if (
|
|
33
|
-
!command ||
|
|
34
|
-
!["install", "remove", "uninstall", "list", "update"].includes(command)
|
|
35
|
-
) {
|
|
31
|
+
if (!command || !["install", "remove", "uninstall", "list", "update"].includes(command)) {
|
|
36
32
|
return false;
|
|
37
33
|
}
|
|
38
34
|
|
|
@@ -139,13 +135,40 @@ async function handleGuiCommand(args: string[], cwd: string): Promise<boolean> {
|
|
|
139
135
|
return true;
|
|
140
136
|
}
|
|
141
137
|
|
|
138
|
+
async function handleMonitorCommand(args: string[], cwd: string): Promise<boolean> {
|
|
139
|
+
if (args[0] !== "monitor") return false;
|
|
140
|
+
|
|
141
|
+
const tsxCli = require.resolve("tsx/cli");
|
|
142
|
+
const monitorPath = resolve(packageRoot, "src/monitor.ts");
|
|
143
|
+
const child = spawn(process.execPath, [tsxCli, monitorPath, ...args.slice(1)], {
|
|
144
|
+
cwd,
|
|
145
|
+
env: process.env,
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const exitCode = await new Promise<number>((resolveExit) => {
|
|
150
|
+
child.on("close", (code, signal) => {
|
|
151
|
+
if (signal) {
|
|
152
|
+
resolveExit(1);
|
|
153
|
+
} else {
|
|
154
|
+
resolveExit(code ?? 0);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
process.exitCode = exitCode;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
142
162
|
async function main(): Promise<void> {
|
|
143
163
|
const rawArgs = process.argv.slice(2);
|
|
144
|
-
const { positionals } = parseArgs({ allowPositionals: true, strict: false });
|
|
145
164
|
const cwd = process.cwd();
|
|
146
165
|
const agentDir = getAgentDir();
|
|
147
166
|
|
|
148
|
-
if (await handleGuiCommand(
|
|
167
|
+
if (await handleGuiCommand(rawArgs, cwd)) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (await handleMonitorCommand(rawArgs, cwd)) {
|
|
149
172
|
return;
|
|
150
173
|
}
|
|
151
174
|
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { ensureParentDir, getConfigPath } from "./infra/opencandle-paths.js";
|
|
3
3
|
import type { PlanningBehaviorMode, TaskFamily } from "./routing/planning.js";
|
|
4
4
|
|
|
@@ -22,9 +22,9 @@ export interface Config {
|
|
|
22
22
|
/** Enable adversarial bull/bear debate in comprehensive analysis. Default: true. */
|
|
23
23
|
debate?: boolean;
|
|
24
24
|
/**
|
|
25
|
-
* Intent-router mode. `"
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Intent-router mode. `"rules"` (default) uses the deterministic rule
|
|
26
|
+
* router (`classifyIntent` + `extractPreferences`). `"llm"` opts into the
|
|
27
|
+
* LLM router ahead of prompt assembly. Controlled by
|
|
28
28
|
* `OPENCANDLE_ROUTER_MODE`.
|
|
29
29
|
*/
|
|
30
30
|
routerMode: RouterMode;
|
|
@@ -126,10 +126,10 @@ const PLANNING_BEHAVIOR_MODES = [
|
|
|
126
126
|
|
|
127
127
|
function resolveRouterMode(): RouterMode {
|
|
128
128
|
const raw = process.env.OPENCANDLE_ROUTER_MODE;
|
|
129
|
-
if (raw === undefined || raw === "") return "
|
|
129
|
+
if (raw === undefined || raw === "") return "rules";
|
|
130
130
|
if (raw === "rules" || raw === "llm") return raw;
|
|
131
131
|
throw new Error(
|
|
132
|
-
`Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "
|
|
132
|
+
`Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "rules" (default) or "llm".`,
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -185,7 +185,10 @@ function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
|
|
|
185
185
|
braveApiKey: process.env.BRAVE_API_KEY ?? fileConfig.providers?.brave?.apiKey,
|
|
186
186
|
exaApiKey: process.env.EXA_API_KEY ?? fileConfig.providers?.exa?.apiKey,
|
|
187
187
|
finnhubApiKey: process.env.FINNHUB_API_KEY ?? fileConfig.providers?.finnhub?.apiKey,
|
|
188
|
-
debate:
|
|
188
|
+
debate:
|
|
189
|
+
debateEnv !== undefined
|
|
190
|
+
? debateEnv !== "false" && debateEnv !== "0"
|
|
191
|
+
: (fileConfig.debate ?? true),
|
|
189
192
|
routerMode: resolveRouterMode(),
|
|
190
193
|
toolScopeMode: resolveToolScopeMode(),
|
|
191
194
|
planningMigrationStatuses: resolvePlanningMigrationStatuses(),
|
|
@@ -193,7 +196,8 @@ function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
|
|
|
193
196
|
retentionDays: fileSentiment?.retentionDays ?? SENTIMENT_DEFAULTS.retentionDays,
|
|
194
197
|
defaultSubreddits: fileSentiment?.defaultSubreddits ?? SENTIMENT_DEFAULTS.defaultSubreddits,
|
|
195
198
|
commentsPerPost: fileSentiment?.commentsPerPost ?? SENTIMENT_DEFAULTS.commentsPerPost,
|
|
196
|
-
divergenceThreshold:
|
|
199
|
+
divergenceThreshold:
|
|
200
|
+
fileSentiment?.divergenceThreshold ?? SENTIMENT_DEFAULTS.divergenceThreshold,
|
|
197
201
|
},
|
|
198
202
|
};
|
|
199
203
|
}
|
|
@@ -222,7 +226,11 @@ export function loadFileConfig(path = getConfigPath()): OpenCandleFileConfig {
|
|
|
222
226
|
|
|
223
227
|
export function saveFileConfig(config: OpenCandleFileConfig, path = getConfigPath()): void {
|
|
224
228
|
ensureParentDir(path);
|
|
225
|
-
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`,
|
|
229
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, {
|
|
230
|
+
encoding: "utf-8",
|
|
231
|
+
mode: 0o600,
|
|
232
|
+
});
|
|
233
|
+
if (process.platform !== "win32") chmodSync(path, 0o600);
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
export function loadConfig(): Config {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createOpenCandleSession, type CreateOpenCandleSessionOptions } from "./pi/session.js";
|
|
2
1
|
export { default as openCandleExtension } from "./pi/opencandle-extension.js";
|
|
2
|
+
export { type CreateOpenCandleSessionOptions, createOpenCandleSession } from "./pi/session.js";
|
|
3
3
|
export { agentToolToPiTool, getOpenCandleToolDefinitions } from "./pi/tool-adapter.js";
|
|
4
4
|
export { registerTools } from "./tool-kit.js";
|
|
5
5
|
export { getAllTools } from "./tools/index.js";
|
package/src/infra/browser.ts
CHANGED
|
@@ -38,7 +38,9 @@ async function ensureBrowser(): Promise<Page> {
|
|
|
38
38
|
|
|
39
39
|
async function withPage<T>(fn: (p: Page) => Promise<T>): Promise<T> {
|
|
40
40
|
let resolve!: () => void;
|
|
41
|
-
const next = new Promise<void>((r) => {
|
|
41
|
+
const next = new Promise<void>((r) => {
|
|
42
|
+
resolve = r;
|
|
43
|
+
});
|
|
42
44
|
const prev = pageQueue;
|
|
43
45
|
pageQueue = next;
|
|
44
46
|
await prev;
|
package/src/infra/cache.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
1
3
|
interface CacheEntry<T> {
|
|
2
4
|
value: T;
|
|
3
5
|
expiresAt: number;
|
|
@@ -10,10 +12,26 @@ export interface StaleResult<T> {
|
|
|
10
12
|
cachedAt: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
interface StaleMetadata {
|
|
16
|
+
stale: boolean;
|
|
17
|
+
cachedAt: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const staleMetadataStorage = new AsyncLocalStorage<StaleMetadata>();
|
|
21
|
+
|
|
22
|
+
export async function runWithStaleMetadata<T>(
|
|
23
|
+
fn: () => Promise<T>,
|
|
24
|
+
): Promise<{ value: T; stale?: { cachedAt: number } }> {
|
|
25
|
+
const metadata: StaleMetadata = { stale: false, cachedAt: 0 };
|
|
26
|
+
const value = await staleMetadataStorage.run(metadata, fn);
|
|
27
|
+
return {
|
|
28
|
+
value,
|
|
29
|
+
stale: metadata.stale ? { cachedAt: metadata.cachedAt } : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
export class Cache {
|
|
14
34
|
private store = new Map<string, CacheEntry<unknown>>();
|
|
15
|
-
private lastStaleHit = false;
|
|
16
|
-
private lastStaleCachedAt = 0;
|
|
17
35
|
|
|
18
36
|
get<T>(key: string): T | undefined {
|
|
19
37
|
const entry = this.store.get(key);
|
|
@@ -36,23 +54,14 @@ export class Cache {
|
|
|
36
54
|
return undefined;
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
const metadata = staleMetadataStorage.getStore();
|
|
58
|
+
if (metadata) {
|
|
59
|
+
metadata.stale = true;
|
|
60
|
+
metadata.cachedAt = entry.cachedAt;
|
|
61
|
+
}
|
|
41
62
|
return { value: entry.value as T, stale: true, cachedAt: entry.cachedAt };
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
/**
|
|
45
|
-
* Consume the stale flag set by the last getStale() hit.
|
|
46
|
-
* Returns { stale: true, cachedAt } if the last getStale() found data,
|
|
47
|
-
* then resets the flag. Used by wrapProvider to propagate stale metadata.
|
|
48
|
-
*/
|
|
49
|
-
consumeStaleFlag(): { stale: boolean; cachedAt: number } {
|
|
50
|
-
const result = { stale: this.lastStaleHit, cachedAt: this.lastStaleCachedAt };
|
|
51
|
-
this.lastStaleHit = false;
|
|
52
|
-
this.lastStaleCachedAt = 0;
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
65
|
set<T>(key: string, value: T, ttlMs: number): void {
|
|
57
66
|
this.store.set(key, { value, expiresAt: Date.now() + ttlMs, cachedAt: Date.now() });
|
|
58
67
|
}
|
|
@@ -79,25 +88,27 @@ export const cache = new Cache();
|
|
|
79
88
|
|
|
80
89
|
// Default TTLs
|
|
81
90
|
export const TTL = {
|
|
82
|
-
QUOTE: 60_000,
|
|
83
|
-
HISTORY: 3_600_000,
|
|
91
|
+
QUOTE: 60_000, // 1 minute
|
|
92
|
+
HISTORY: 3_600_000, // 1 hour
|
|
84
93
|
FUNDAMENTALS: 86_400_000, // 24 hours
|
|
85
|
-
MACRO: 3_600_000,
|
|
86
|
-
SENTIMENT: 300_000,
|
|
94
|
+
MACRO: 3_600_000, // 1 hour
|
|
95
|
+
SENTIMENT: 300_000, // 5 minutes
|
|
87
96
|
OPTIONS_CHAIN: 120_000, // 2 minutes
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
SCREENER: 60_000, // 1 minute
|
|
98
|
+
CRUMB: 900_000, // 15 minutes
|
|
99
|
+
WEB_SEARCH: 300_000, // 5 minutes
|
|
90
100
|
FINNHUB_NEWS: 300_000, // 5 minutes
|
|
91
101
|
} as const;
|
|
92
102
|
|
|
93
103
|
// Stale limits — how long past TTL expiry a cached value is still useful as fallback
|
|
94
104
|
export const STALE_LIMIT = {
|
|
95
|
-
QUOTE: 15 * 60_000,
|
|
96
|
-
HISTORY: 24 * 3_600_000,
|
|
97
|
-
FUNDAMENTALS: 7 * 86_400_000,
|
|
98
|
-
MACRO: 24 * 3_600_000,
|
|
99
|
-
SENTIMENT: 3_600_000,
|
|
100
|
-
OPTIONS_CHAIN: 30 * 60_000,
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
QUOTE: 15 * 60_000, // 15 minutes
|
|
106
|
+
HISTORY: 24 * 3_600_000, // 24 hours
|
|
107
|
+
FUNDAMENTALS: 7 * 86_400_000, // 7 days
|
|
108
|
+
MACRO: 24 * 3_600_000, // 24 hours
|
|
109
|
+
SENTIMENT: 3_600_000, // 1 hour
|
|
110
|
+
OPTIONS_CHAIN: 30 * 60_000, // 30 minutes
|
|
111
|
+
SCREENER: 15 * 60_000, // 15 minutes
|
|
112
|
+
WEB_SEARCH: 3_600_000, // 1 hour
|
|
113
|
+
FINNHUB_NEWS: 3_600_000, // 1 hour
|
|
103
114
|
} as const;
|
package/src/infra/http-client.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface HttpClientOptions {
|
|
|
2
2
|
timeoutMs?: number;
|
|
3
3
|
maxRetries?: number;
|
|
4
4
|
retryDelayMs?: number;
|
|
5
|
+
maxRetryAfterMs?: number;
|
|
5
6
|
headers?: Record<string, string>;
|
|
6
7
|
}
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ const DEFAULT_OPTIONS: Required<HttpClientOptions> = {
|
|
|
9
10
|
timeoutMs: 10_000,
|
|
10
11
|
maxRetries: 2,
|
|
11
12
|
retryDelayMs: 1_000,
|
|
13
|
+
maxRetryAfterMs: 5_000,
|
|
12
14
|
headers: {},
|
|
13
15
|
};
|
|
14
16
|
|
|
@@ -17,52 +19,116 @@ export class HttpError extends Error {
|
|
|
17
19
|
public readonly status: number,
|
|
18
20
|
public readonly statusText: string,
|
|
19
21
|
public readonly body: string,
|
|
22
|
+
public readonly retryAfterMs?: number,
|
|
20
23
|
) {
|
|
21
24
|
super(`HTTP ${status} ${statusText}`);
|
|
22
25
|
this.name = "HttpError";
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
export async function httpGet<T>(
|
|
29
|
+
export async function httpGet<T>(url: string, options: HttpClientOptions = {}): Promise<T> {
|
|
30
|
+
return httpRequest<T>(url, { ...options, method: "GET" });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function httpPost<T>(
|
|
27
34
|
url: string,
|
|
35
|
+
body: unknown,
|
|
28
36
|
options: HttpClientOptions = {},
|
|
29
37
|
): Promise<T> {
|
|
38
|
+
return httpRequest<T>(url, {
|
|
39
|
+
...options,
|
|
40
|
+
method: "POST",
|
|
41
|
+
body: JSON.stringify(body),
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
...options.headers,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface HttpRequestOptions extends HttpClientOptions {
|
|
50
|
+
method: "GET" | "POST";
|
|
51
|
+
body?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function httpRequest<T>(url: string, options: HttpRequestOptions): Promise<T> {
|
|
30
55
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
31
56
|
let lastError: Error | undefined;
|
|
32
57
|
|
|
33
58
|
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
34
|
-
|
|
35
|
-
await sleep(opts.retryDelayMs * attempt);
|
|
36
|
-
}
|
|
59
|
+
let retryDelayMs: number | undefined;
|
|
37
60
|
|
|
38
61
|
const controller = new AbortController();
|
|
39
62
|
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
40
63
|
|
|
41
64
|
try {
|
|
42
65
|
const response = await fetch(url, {
|
|
66
|
+
method: opts.method,
|
|
43
67
|
signal: controller.signal,
|
|
44
68
|
headers: opts.headers,
|
|
69
|
+
...(opts.body !== undefined && { body: opts.body }),
|
|
45
70
|
});
|
|
46
71
|
|
|
47
72
|
if (!response.ok) {
|
|
48
73
|
const body = await response.text().catch(() => "");
|
|
49
|
-
throw new HttpError(
|
|
74
|
+
throw new HttpError(
|
|
75
|
+
response.status,
|
|
76
|
+
response.statusText,
|
|
77
|
+
body,
|
|
78
|
+
parseRetryAfterMs(response.headers?.get?.("retry-after") ?? null),
|
|
79
|
+
);
|
|
50
80
|
}
|
|
51
81
|
|
|
52
82
|
return (await response.json()) as T;
|
|
53
83
|
} catch (error) {
|
|
54
84
|
lastError = error as Error;
|
|
55
|
-
if (error
|
|
85
|
+
if (!isRetryableError(error)) {
|
|
56
86
|
throw error; // Don't retry client errors
|
|
57
87
|
}
|
|
88
|
+
if (attempt < opts.maxRetries) {
|
|
89
|
+
retryDelayMs =
|
|
90
|
+
error instanceof HttpError && error.status === 429 && error.retryAfterMs !== undefined
|
|
91
|
+
? capRetryAfterMs(error.retryAfterMs, opts.maxRetryAfterMs)
|
|
92
|
+
: opts.retryDelayMs * (attempt + 1);
|
|
93
|
+
}
|
|
58
94
|
} finally {
|
|
59
95
|
clearTimeout(timeout);
|
|
60
96
|
}
|
|
97
|
+
|
|
98
|
+
if (retryDelayMs !== undefined) {
|
|
99
|
+
await sleep(retryDelayMs);
|
|
100
|
+
}
|
|
61
101
|
}
|
|
62
102
|
|
|
63
103
|
throw lastError!;
|
|
64
104
|
}
|
|
65
105
|
|
|
106
|
+
function isRetryableError(error: unknown): boolean {
|
|
107
|
+
if (!(error instanceof HttpError)) return true;
|
|
108
|
+
if (error.status === 429) return true;
|
|
109
|
+
return error.status < 400 || error.status >= 500;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseRetryAfterMs(value: string | null): number | undefined {
|
|
113
|
+
if (!value) return undefined;
|
|
114
|
+
const trimmed = value.trim();
|
|
115
|
+
if (trimmed.length === 0) return undefined;
|
|
116
|
+
|
|
117
|
+
const seconds = Number(trimmed);
|
|
118
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
119
|
+
return seconds * 1000;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const dateMs = Date.parse(trimmed);
|
|
123
|
+
if (Number.isNaN(dateMs)) return undefined;
|
|
124
|
+
return Math.max(0, dateMs - Date.now());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function capRetryAfterMs(retryAfterMs: number, maxRetryAfterMs: number | undefined): number {
|
|
128
|
+
if (maxRetryAfterMs === undefined) return retryAfterMs;
|
|
129
|
+
return Math.min(retryAfterMs, Math.max(0, maxRetryAfterMs));
|
|
130
|
+
}
|
|
131
|
+
|
|
66
132
|
function sleep(ms: number): Promise<void> {
|
|
67
133
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
134
|
}
|
package/src/infra/index.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
export { Cache, cache, TTL } from "./cache.js";
|
|
2
|
-
export { RateLimiter, rateLimiter } from "./rate-limiter.js";
|
|
3
|
-
export { httpGet, HttpError, type HttpClientOptions } from "./http-client.js";
|
|
4
1
|
export { StealthBrowser } from "./browser.js";
|
|
2
|
+
export { Cache, cache, TTL } from "./cache.js";
|
|
3
|
+
export { type HttpClientOptions, HttpError, httpGet } from "./http-client.js";
|
|
5
4
|
export {
|
|
6
|
-
getOpenCandleHomeDir,
|
|
7
5
|
ensureOpenCandleHomeDir,
|
|
8
|
-
resolveOpenCandlePath,
|
|
9
6
|
ensureParentDir,
|
|
10
|
-
|
|
11
|
-
getPortfolioPath,
|
|
12
|
-
getPredictionsPath,
|
|
7
|
+
getBrowserProfileDir,
|
|
13
8
|
getConfigPath,
|
|
9
|
+
getLogsDir,
|
|
14
10
|
getOnboardingPath,
|
|
11
|
+
getOpenCandleHomeDir,
|
|
15
12
|
getStateDbPath,
|
|
16
|
-
|
|
17
|
-
getBrowserProfileDir,
|
|
13
|
+
resolveOpenCandlePath,
|
|
18
14
|
} from "./opencandle-paths.js";
|
|
15
|
+
export { RateLimiter, rateLimiter } from "./rate-limiter.js";
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export function getNativeDependencyErrorMessage(
|
|
1
|
+
export function getNativeDependencyErrorMessage(
|
|
2
|
+
error: unknown,
|
|
3
|
+
dependencyName: string,
|
|
4
|
+
): string | null {
|
|
2
5
|
const message = error instanceof Error ? error.message : String(error);
|
|
3
6
|
if (
|
|
4
7
|
!message.includes("NODE_MODULE_VERSION") &&
|
|
@@ -7,6 +10,8 @@ export function getNativeDependencyErrorMessage(error: unknown, dependencyName:
|
|
|
7
10
|
return null;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
return
|
|
11
|
-
|
|
13
|
+
return (
|
|
14
|
+
`${dependencyName} native binding was built for a different Node ABI than the active Node ${process.versions.node}. ` +
|
|
15
|
+
`Run \`npm rebuild ${dependencyName}\` or reinstall dependencies under the active Node with \`npm install\`.`
|
|
16
|
+
);
|
|
12
17
|
}
|
|
@@ -10,7 +10,9 @@ function isSupportedNodeVersion(version: string): boolean {
|
|
|
10
10
|
return major >= 24 && major < 27;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function getUnsupportedNodeVersionMessage(
|
|
13
|
+
export function getUnsupportedNodeVersionMessage(
|
|
14
|
+
version: string = process.versions.node,
|
|
15
|
+
): string | null {
|
|
14
16
|
if (isSupportedNodeVersion(version)) return null;
|
|
15
17
|
|
|
16
18
|
return `OpenCandle supports Node ${SUPPORTED_NODE_RANGE}. Current Node is ${version}. Use Node ${SUPPORTED_NODE_RANGE}; the repo default is Node 22.22.0 via \`nvm use\`. After switching Node versions, reinstall dependencies under the active Node with \`npm install\` or rebuild native modules with \`npm rebuild better-sqlite3\`.`;
|