opencandle 0.1.1 → 0.3.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 (278) hide show
  1. package/README.md +111 -79
  2. package/dist/analysts/contracts.d.ts +31 -0
  3. package/dist/analysts/contracts.js +158 -0
  4. package/dist/analysts/contracts.js.map +1 -0
  5. package/dist/analysts/orchestrator.d.ts +11 -2
  6. package/dist/analysts/orchestrator.js +156 -9
  7. package/dist/analysts/orchestrator.js.map +1 -1
  8. package/dist/cli.js +26 -10
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +29 -5
  11. package/dist/config.js +18 -8
  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 +34 -0
  17. package/dist/infra/cache.js +44 -3
  18. package/dist/infra/cache.js.map +1 -1
  19. package/dist/infra/index.d.ts +1 -1
  20. package/dist/infra/index.js +1 -1
  21. package/dist/infra/index.js.map +1 -1
  22. package/dist/infra/opencandle-paths.d.ts +1 -0
  23. package/dist/infra/opencandle-paths.js +3 -0
  24. package/dist/infra/opencandle-paths.js.map +1 -1
  25. package/dist/infra/rate-limiter.js +7 -0
  26. package/dist/infra/rate-limiter.js.map +1 -1
  27. package/dist/memory/index.d.ts +3 -0
  28. package/dist/memory/index.js +2 -0
  29. package/dist/memory/index.js.map +1 -1
  30. package/dist/memory/manager.d.ts +19 -0
  31. package/dist/memory/manager.js +132 -0
  32. package/dist/memory/manager.js.map +1 -0
  33. package/dist/memory/sqlite.js +12 -1
  34. package/dist/memory/sqlite.js.map +1 -1
  35. package/dist/memory/types.d.ts +21 -0
  36. package/dist/memory/types.js +47 -0
  37. package/dist/memory/types.js.map +1 -0
  38. package/dist/onboarding/connect.d.ts +23 -0
  39. package/dist/onboarding/connect.js +107 -0
  40. package/dist/onboarding/connect.js.map +1 -0
  41. package/dist/onboarding/credential-interceptor.d.ts +44 -0
  42. package/dist/onboarding/credential-interceptor.js +72 -0
  43. package/dist/onboarding/credential-interceptor.js.map +1 -0
  44. package/dist/onboarding/degradation-accumulator.d.ts +21 -0
  45. package/dist/onboarding/degradation-accumulator.js +55 -0
  46. package/dist/onboarding/degradation-accumulator.js.map +1 -0
  47. package/dist/onboarding/prompt-user.d.ts +23 -0
  48. package/dist/onboarding/prompt-user.js +61 -0
  49. package/dist/onboarding/prompt-user.js.map +1 -0
  50. package/dist/onboarding/providers.d.ts +109 -0
  51. package/dist/onboarding/providers.js +236 -0
  52. package/dist/onboarding/providers.js.map +1 -0
  53. package/dist/onboarding/state.d.ts +31 -2
  54. package/dist/onboarding/state.js +141 -13
  55. package/dist/onboarding/state.js.map +1 -1
  56. package/dist/onboarding/tool-helpers.d.ts +34 -0
  57. package/dist/onboarding/tool-helpers.js +80 -0
  58. package/dist/onboarding/tool-helpers.js.map +1 -0
  59. package/dist/onboarding/tool-tags.d.ts +37 -0
  60. package/dist/onboarding/tool-tags.js +149 -0
  61. package/dist/onboarding/tool-tags.js.map +1 -0
  62. package/dist/onboarding/validation.d.ts +19 -0
  63. package/dist/onboarding/validation.js +117 -0
  64. package/dist/onboarding/validation.js.map +1 -0
  65. package/dist/pi/opencandle-extension.d.ts +5 -1
  66. package/dist/pi/opencandle-extension.js +345 -143
  67. package/dist/pi/opencandle-extension.js.map +1 -1
  68. package/dist/pi/session.d.ts +2 -0
  69. package/dist/pi/session.js +1 -1
  70. package/dist/pi/session.js.map +1 -1
  71. package/dist/pi/setup.d.ts +0 -1
  72. package/dist/pi/setup.js +66 -119
  73. package/dist/pi/setup.js.map +1 -1
  74. package/dist/prompts/context-builder.d.ts +26 -0
  75. package/dist/prompts/context-builder.js +127 -0
  76. package/dist/prompts/context-builder.js.map +1 -0
  77. package/dist/prompts/sections.d.ts +13 -0
  78. package/dist/prompts/sections.js +35 -0
  79. package/dist/prompts/sections.js.map +1 -0
  80. package/dist/providers/alpha-vantage.d.ts +3 -0
  81. package/dist/providers/alpha-vantage.js +204 -77
  82. package/dist/providers/alpha-vantage.js.map +1 -1
  83. package/dist/providers/coingecko.js +53 -37
  84. package/dist/providers/coingecko.js.map +1 -1
  85. package/dist/providers/exa-search.d.ts +39 -0
  86. package/dist/providers/exa-search.js +276 -0
  87. package/dist/providers/exa-search.js.map +1 -0
  88. package/dist/providers/fear-greed.js +23 -15
  89. package/dist/providers/fear-greed.js.map +1 -1
  90. package/dist/providers/finnhub.d.ts +17 -0
  91. package/dist/providers/finnhub.js +94 -0
  92. package/dist/providers/finnhub.js.map +1 -0
  93. package/dist/providers/fred.js +48 -28
  94. package/dist/providers/fred.js.map +1 -1
  95. package/dist/providers/index.d.ts +2 -0
  96. package/dist/providers/index.js +1 -0
  97. package/dist/providers/index.js.map +1 -1
  98. package/dist/providers/provider-credential-error.d.ts +8 -0
  99. package/dist/providers/provider-credential-error.js +22 -0
  100. package/dist/providers/provider-credential-error.js.map +1 -0
  101. package/dist/providers/reddit.d.ts +8 -0
  102. package/dist/providers/reddit.js +78 -43
  103. package/dist/providers/reddit.js.map +1 -1
  104. package/dist/providers/twitter.d.ts +20 -0
  105. package/dist/providers/twitter.js +137 -0
  106. package/dist/providers/twitter.js.map +1 -0
  107. package/dist/providers/web-search.d.ts +17 -0
  108. package/dist/providers/web-search.js +224 -0
  109. package/dist/providers/web-search.js.map +1 -0
  110. package/dist/providers/with-fallback.d.ts +15 -0
  111. package/dist/providers/with-fallback.js +32 -0
  112. package/dist/providers/with-fallback.js.map +1 -0
  113. package/dist/providers/wrap-provider.d.ts +20 -0
  114. package/dist/providers/wrap-provider.js +58 -0
  115. package/dist/providers/wrap-provider.js.map +1 -0
  116. package/dist/providers/yahoo-finance.js +77 -57
  117. package/dist/providers/yahoo-finance.js.map +1 -1
  118. package/dist/routing/classify-intent.js +22 -0
  119. package/dist/routing/classify-intent.js.map +1 -1
  120. package/dist/runtime/evidence.d.ts +35 -0
  121. package/dist/runtime/evidence.js +29 -0
  122. package/dist/runtime/evidence.js.map +1 -0
  123. package/dist/runtime/index.d.ts +16 -0
  124. package/dist/runtime/index.js +10 -0
  125. package/dist/runtime/index.js.map +1 -0
  126. package/dist/runtime/prompt-step.d.ts +41 -0
  127. package/dist/runtime/prompt-step.js +42 -0
  128. package/dist/runtime/prompt-step.js.map +1 -0
  129. package/dist/runtime/provider-ids.d.ts +14 -0
  130. package/dist/runtime/provider-ids.js +14 -0
  131. package/dist/runtime/provider-ids.js.map +1 -0
  132. package/dist/runtime/provider-tracker.d.ts +20 -0
  133. package/dist/runtime/provider-tracker.js +36 -0
  134. package/dist/runtime/provider-tracker.js.map +1 -0
  135. package/dist/runtime/run-context.d.ts +11 -0
  136. package/dist/runtime/run-context.js +14 -0
  137. package/dist/runtime/run-context.js.map +1 -0
  138. package/dist/runtime/session-coordinator.d.ts +47 -0
  139. package/dist/runtime/session-coordinator.js +171 -0
  140. package/dist/runtime/session-coordinator.js.map +1 -0
  141. package/dist/runtime/validation.d.ts +44 -0
  142. package/dist/runtime/validation.js +157 -0
  143. package/dist/runtime/validation.js.map +1 -0
  144. package/dist/runtime/workflow-events.d.ts +21 -0
  145. package/dist/runtime/workflow-events.js +31 -0
  146. package/dist/runtime/workflow-events.js.map +1 -0
  147. package/dist/runtime/workflow-runner.d.ts +36 -0
  148. package/dist/runtime/workflow-runner.js +129 -0
  149. package/dist/runtime/workflow-runner.js.map +1 -0
  150. package/dist/runtime/workflow-types.d.ts +60 -0
  151. package/dist/runtime/workflow-types.js +32 -0
  152. package/dist/runtime/workflow-types.js.map +1 -0
  153. package/dist/sentiment/adapters/finnhub.d.ts +7 -0
  154. package/dist/sentiment/adapters/finnhub.js +39 -0
  155. package/dist/sentiment/adapters/finnhub.js.map +1 -0
  156. package/dist/sentiment/adapters/reddit.d.ts +11 -0
  157. package/dist/sentiment/adapters/reddit.js +54 -0
  158. package/dist/sentiment/adapters/reddit.js.map +1 -0
  159. package/dist/sentiment/adapters/twitter.d.ts +9 -0
  160. package/dist/sentiment/adapters/twitter.js +32 -0
  161. package/dist/sentiment/adapters/twitter.js.map +1 -0
  162. package/dist/sentiment/adapters/web.d.ts +9 -0
  163. package/dist/sentiment/adapters/web.js +40 -0
  164. package/dist/sentiment/adapters/web.js.map +1 -0
  165. package/dist/sentiment/index.d.ts +16 -0
  166. package/dist/sentiment/index.js +44 -0
  167. package/dist/sentiment/index.js.map +1 -0
  168. package/dist/sentiment/keywords.d.ts +2 -0
  169. package/dist/sentiment/keywords.js +9 -0
  170. package/dist/sentiment/keywords.js.map +1 -0
  171. package/dist/sentiment/pipeline.d.ts +9 -0
  172. package/dist/sentiment/pipeline.js +57 -0
  173. package/dist/sentiment/pipeline.js.map +1 -0
  174. package/dist/sentiment/scorer.d.ts +9 -0
  175. package/dist/sentiment/scorer.js +64 -0
  176. package/dist/sentiment/scorer.js.map +1 -0
  177. package/dist/sentiment/store.d.ts +24 -0
  178. package/dist/sentiment/store.js +177 -0
  179. package/dist/sentiment/store.js.map +1 -0
  180. package/dist/sentiment/trends.d.ts +13 -0
  181. package/dist/sentiment/trends.js +73 -0
  182. package/dist/sentiment/trends.js.map +1 -0
  183. package/dist/sentiment/types.d.ts +66 -0
  184. package/dist/sentiment/types.js +54 -0
  185. package/dist/sentiment/types.js.map +1 -0
  186. package/dist/system-prompt.js +60 -2
  187. package/dist/system-prompt.js.map +1 -1
  188. package/dist/tool-kit.d.ts +9 -5
  189. package/dist/tool-kit.js +29 -6
  190. package/dist/tool-kit.js.map +1 -1
  191. package/dist/tools/fundamentals/company-overview.d.ts +3 -1
  192. package/dist/tools/fundamentals/company-overview.js +28 -17
  193. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  194. package/dist/tools/fundamentals/comps.js +47 -39
  195. package/dist/tools/fundamentals/comps.js.map +1 -1
  196. package/dist/tools/fundamentals/dcf.js +83 -65
  197. package/dist/tools/fundamentals/dcf.js.map +1 -1
  198. package/dist/tools/fundamentals/earnings.d.ts +3 -1
  199. package/dist/tools/fundamentals/earnings.js +26 -18
  200. package/dist/tools/fundamentals/earnings.js.map +1 -1
  201. package/dist/tools/fundamentals/financials.d.ts +3 -1
  202. package/dist/tools/fundamentals/financials.js +24 -16
  203. package/dist/tools/fundamentals/financials.js.map +1 -1
  204. package/dist/tools/fundamentals/sec-filings.js +9 -1
  205. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  206. package/dist/tools/index.js +10 -2
  207. package/dist/tools/index.js.map +1 -1
  208. package/dist/tools/interaction/ask-user.d.ts +3 -0
  209. package/dist/tools/interaction/ask-user.js +51 -0
  210. package/dist/tools/interaction/ask-user.js.map +1 -0
  211. package/dist/tools/interaction/twitter-login.d.ts +8 -0
  212. package/dist/tools/interaction/twitter-login.js +77 -0
  213. package/dist/tools/interaction/twitter-login.js.map +1 -0
  214. package/dist/tools/macro/fear-greed.js +9 -1
  215. package/dist/tools/macro/fear-greed.js.map +1 -1
  216. package/dist/tools/macro/fred-data.d.ts +3 -1
  217. package/dist/tools/macro/fred-data.js +27 -16
  218. package/dist/tools/macro/fred-data.js.map +1 -1
  219. package/dist/tools/market/crypto-history.js +9 -1
  220. package/dist/tools/market/crypto-history.js.map +1 -1
  221. package/dist/tools/market/crypto-price.js +9 -1
  222. package/dist/tools/market/crypto-price.js.map +1 -1
  223. package/dist/tools/market/stock-history.js +28 -1
  224. package/dist/tools/market/stock-history.js.map +1 -1
  225. package/dist/tools/market/stock-quote.js +29 -4
  226. package/dist/tools/market/stock-quote.js.map +1 -1
  227. package/dist/tools/options/option-chain.js +9 -1
  228. package/dist/tools/options/option-chain.js.map +1 -1
  229. package/dist/tools/portfolio/correlation.js +15 -3
  230. package/dist/tools/portfolio/correlation.js.map +1 -1
  231. package/dist/tools/portfolio/predictions.js +6 -5
  232. package/dist/tools/portfolio/predictions.js.map +1 -1
  233. package/dist/tools/portfolio/risk-analysis.js +9 -1
  234. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  235. package/dist/tools/portfolio/tracker.js +6 -3
  236. package/dist/tools/portfolio/tracker.js.map +1 -1
  237. package/dist/tools/portfolio/watchlist.js +6 -1
  238. package/dist/tools/portfolio/watchlist.js.map +1 -1
  239. package/dist/tools/sentiment/reddit-sentiment.d.ts +3 -1
  240. package/dist/tools/sentiment/reddit-sentiment.js +112 -19
  241. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  242. package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
  243. package/dist/tools/sentiment/sentiment-summary.js +230 -0
  244. package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
  245. package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
  246. package/dist/tools/sentiment/sentiment-trend.js +39 -0
  247. package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
  248. package/dist/tools/sentiment/twitter-sentiment.d.ts +9 -0
  249. package/dist/tools/sentiment/twitter-sentiment.js +75 -0
  250. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -0
  251. package/dist/tools/sentiment/web-search.d.ts +11 -0
  252. package/dist/tools/sentiment/web-search.js +115 -0
  253. package/dist/tools/sentiment/web-search.js.map +1 -0
  254. package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
  255. package/dist/tools/sentiment/web-sentiment.js +66 -0
  256. package/dist/tools/sentiment/web-sentiment.js.map +1 -0
  257. package/dist/tools/technical/backtest.js +9 -1
  258. package/dist/tools/technical/backtest.js.map +1 -1
  259. package/dist/tools/technical/indicators.js +9 -1
  260. package/dist/tools/technical/indicators.js.map +1 -1
  261. package/dist/types/index.d.ts +16 -1
  262. package/dist/types/sentiment.d.ts +41 -0
  263. package/dist/workflows/compare-assets.d.ts +3 -0
  264. package/dist/workflows/compare-assets.js +21 -5
  265. package/dist/workflows/compare-assets.js.map +1 -1
  266. package/dist/workflows/index.d.ts +3 -3
  267. package/dist/workflows/index.js +3 -3
  268. package/dist/workflows/index.js.map +1 -1
  269. package/dist/workflows/options-screener.d.ts +3 -0
  270. package/dist/workflows/options-screener.js +24 -7
  271. package/dist/workflows/options-screener.js.map +1 -1
  272. package/dist/workflows/portfolio-builder.d.ts +3 -0
  273. package/dist/workflows/portfolio-builder.js +30 -9
  274. package/dist/workflows/portfolio-builder.js.map +1 -1
  275. package/package.json +27 -7
  276. package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
  277. package/dist/tools/sentiment/news-sentiment.js +0 -57
  278. package/dist/tools/sentiment/news-sentiment.js.map +0 -1
@@ -0,0 +1,64 @@
1
+ import { BULLISH_TERMS, BEARISH_TERMS } from "./keywords.js";
2
+ const TICKER_REGEX = /\$([A-Z]{1,5})\b/g;
3
+ export function keywordScore(record) {
4
+ const lower = record.text.toLowerCase();
5
+ const engagement = 1 + (record.engagement.score ?? 0);
6
+ let bullishWeight = 0;
7
+ let bearishWeight = 0;
8
+ let bullishCount = 0;
9
+ let bearishCount = 0;
10
+ for (const term of BULLISH_TERMS) {
11
+ if (lower.includes(term)) {
12
+ bullishCount++;
13
+ bullishWeight += engagement;
14
+ }
15
+ }
16
+ for (const term of BEARISH_TERMS) {
17
+ if (lower.includes(term)) {
18
+ bearishCount++;
19
+ bearishWeight += engagement;
20
+ }
21
+ }
22
+ const totalWeight = bullishWeight + bearishWeight;
23
+ const score = totalWeight === 0 ? 0 : (bullishWeight - bearishWeight) / totalWeight;
24
+ const totalMatches = bullishCount + bearishCount;
25
+ let confidence = 0;
26
+ if (totalMatches > 0) {
27
+ // Base confidence from keyword matches
28
+ confidence = Math.min(totalMatches / 5, 1) * 0.6;
29
+ // Boost for longer text
30
+ const textLength = record.text.length;
31
+ confidence += Math.min(textLength / 500, 1) * 0.3;
32
+ // Multiple matches boost
33
+ if (totalMatches >= 3)
34
+ confidence += 0.1;
35
+ // Twitter penalty
36
+ if (record.source === "twitter")
37
+ confidence -= 0.1;
38
+ // Clamp
39
+ confidence = Math.max(0, Math.min(1, confidence));
40
+ }
41
+ // Extract tickers
42
+ const tickers = [];
43
+ for (const match of record.text.matchAll(TICKER_REGEX)) {
44
+ if (!tickers.includes(match[1])) {
45
+ tickers.push(match[1]);
46
+ }
47
+ }
48
+ return { score, confidence, tickers };
49
+ }
50
+ export function scoreRecords(records) {
51
+ return records.map((record) => {
52
+ const result = keywordScore(record);
53
+ return {
54
+ ...record,
55
+ sentiment: {
56
+ score: result.score,
57
+ confidence: result.confidence,
58
+ method: "keyword",
59
+ tickers: result.tickers,
60
+ },
61
+ };
62
+ });
63
+ }
64
+ //# sourceMappingURL=scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/sentiment/scorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { SentinelRecord, SentimentSource, TrendBucket } from "./types.js";
2
+ export interface SearchOptions {
3
+ source?: SentimentSource;
4
+ since?: string;
5
+ until?: string;
6
+ }
7
+ export interface TimeSeriesOptions {
8
+ days: number;
9
+ bucketHours: number;
10
+ }
11
+ export declare class SentimentStore {
12
+ private db;
13
+ constructor(pathOrMemory: string);
14
+ private initSchema;
15
+ getSchemaVersion(): number;
16
+ insert(records: SentinelRecord[]): void;
17
+ search(query: string, options?: SearchOptions): SentinelRecord[];
18
+ getByTicker(ticker: string, options?: {
19
+ since?: string;
20
+ }): SentinelRecord[];
21
+ getTimeSeries(query: string, options: TimeSeriesOptions): TrendBucket[];
22
+ prune(retentionDays: number): number;
23
+ close(): void;
24
+ }
@@ -0,0 +1,177 @@
1
+ import Database from "better-sqlite3";
2
+ const SCHEMA_VERSION = 1;
3
+ const CREATE_SCHEMA = `
4
+ CREATE TABLE IF NOT EXISTS schema_version (
5
+ version INTEGER NOT NULL
6
+ );
7
+
8
+ CREATE TABLE IF NOT EXISTS sentinel_records (
9
+ id TEXT NOT NULL,
10
+ source TEXT NOT NULL,
11
+ source_id TEXT NOT NULL,
12
+ query TEXT NOT NULL,
13
+ title TEXT,
14
+ text TEXT NOT NULL,
15
+ author TEXT,
16
+ url TEXT NOT NULL,
17
+ published_at TEXT,
18
+ fetched_at TEXT NOT NULL,
19
+ engagement_json TEXT NOT NULL,
20
+ sentiment_score REAL NOT NULL,
21
+ sentiment_confidence REAL NOT NULL,
22
+ sentiment_method TEXT NOT NULL,
23
+ tickers_json TEXT NOT NULL,
24
+ metadata_json TEXT NOT NULL,
25
+ UNIQUE(source, source_id, fetched_at)
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_sentinel_source ON sentinel_records(source);
29
+ CREATE INDEX IF NOT EXISTS idx_sentinel_fetched_at ON sentinel_records(fetched_at);
30
+ CREATE INDEX IF NOT EXISTS idx_sentinel_query ON sentinel_records(query);
31
+
32
+ CREATE VIRTUAL TABLE IF NOT EXISTS sentinel_fts USING fts5(
33
+ text, title, author, query, source,
34
+ content=sentinel_records,
35
+ content_rowid=rowid
36
+ );
37
+ `;
38
+ const TRIGGERS = `
39
+ CREATE TRIGGER IF NOT EXISTS sentinel_ai AFTER INSERT ON sentinel_records BEGIN
40
+ INSERT INTO sentinel_fts(rowid, text, title, author, query, source)
41
+ VALUES (new.rowid, new.text, new.title, new.author, new.query, new.source);
42
+ END;
43
+
44
+ CREATE TRIGGER IF NOT EXISTS sentinel_ad AFTER DELETE ON sentinel_records BEGIN
45
+ INSERT INTO sentinel_fts(sentinel_fts, rowid, text, title, author, query, source)
46
+ VALUES ('delete', old.rowid, old.text, old.title, old.author, old.query, old.source);
47
+ END;
48
+ `;
49
+ export class SentimentStore {
50
+ db;
51
+ constructor(pathOrMemory) {
52
+ this.db = new Database(pathOrMemory);
53
+ if (pathOrMemory !== ":memory:") {
54
+ this.db.pragma("journal_mode = WAL");
55
+ }
56
+ this.initSchema();
57
+ }
58
+ initSchema() {
59
+ this.db.exec(CREATE_SCHEMA);
60
+ this.db.exec(TRIGGERS);
61
+ const row = this.db.prepare("SELECT version FROM schema_version").get();
62
+ if (!row) {
63
+ this.db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
64
+ }
65
+ }
66
+ getSchemaVersion() {
67
+ const row = this.db.prepare("SELECT version FROM schema_version").get();
68
+ return row.version;
69
+ }
70
+ insert(records) {
71
+ const stmt = this.db.prepare(`
72
+ INSERT OR IGNORE INTO sentinel_records
73
+ (id, source, source_id, query, title, text, author, url,
74
+ published_at, fetched_at, engagement_json,
75
+ sentiment_score, sentiment_confidence, sentiment_method,
76
+ tickers_json, metadata_json)
77
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
78
+ `);
79
+ const tx = this.db.transaction((recs) => {
80
+ for (const r of recs) {
81
+ stmt.run(r.id, r.source, r.sourceId, r.query, r.title, r.text, r.author, r.url, r.publishedAt, r.fetchedAt, JSON.stringify(r.engagement), r.sentiment.score, r.sentiment.confidence, r.sentiment.method, JSON.stringify(r.sentiment.tickers), JSON.stringify(r.metadata));
82
+ }
83
+ });
84
+ tx(records);
85
+ }
86
+ search(query, options) {
87
+ let sql = `
88
+ SELECT sr.* FROM sentinel_records sr
89
+ JOIN sentinel_fts fts ON sr.rowid = fts.rowid
90
+ WHERE sentinel_fts MATCH ?
91
+ `;
92
+ const params = [query];
93
+ if (options?.source) {
94
+ sql += " AND sr.source = ?";
95
+ params.push(options.source);
96
+ }
97
+ if (options?.since) {
98
+ sql += " AND sr.fetched_at >= ?";
99
+ params.push(options.since);
100
+ }
101
+ if (options?.until) {
102
+ sql += " AND sr.fetched_at <= ?";
103
+ params.push(options.until);
104
+ }
105
+ sql += " ORDER BY bm25(sentinel_fts) LIMIT 100";
106
+ const rows = this.db.prepare(sql).all(...params);
107
+ return rows.map(rowToRecord);
108
+ }
109
+ getByTicker(ticker, options) {
110
+ let sql = `SELECT * FROM sentinel_records WHERE tickers_json LIKE ?`;
111
+ const pattern = `%"${ticker}"%`;
112
+ const params = [pattern];
113
+ if (options?.since) {
114
+ sql += " AND fetched_at >= ?";
115
+ params.push(options.since);
116
+ }
117
+ sql += " ORDER BY fetched_at DESC LIMIT 100";
118
+ const rows = this.db.prepare(sql).all(...params);
119
+ return rows.map(rowToRecord);
120
+ }
121
+ getTimeSeries(query, options) {
122
+ const since = new Date();
123
+ since.setDate(since.getDate() - options.days);
124
+ const sinceStr = since.toISOString();
125
+ const bucketSeconds = options.bucketHours * 3600;
126
+ const sql = `
127
+ SELECT
128
+ (CAST(strftime('%s', fetched_at) AS INTEGER) / ?) * ? AS bucket_ts,
129
+ AVG(sentiment_score) AS avg_score,
130
+ COUNT(*) AS cnt
131
+ FROM sentinel_records
132
+ WHERE query = ? AND fetched_at >= ?
133
+ GROUP BY bucket_ts
134
+ ORDER BY bucket_ts
135
+ `;
136
+ const rows = this.db.prepare(sql).all(bucketSeconds, bucketSeconds, query, sinceStr);
137
+ return rows.map((r) => ({
138
+ timestamp: new Date(r.bucket_ts * 1000).toISOString(),
139
+ avgScore: r.avg_score,
140
+ count: r.cnt,
141
+ }));
142
+ }
143
+ prune(retentionDays) {
144
+ const cutoff = new Date();
145
+ cutoff.setDate(cutoff.getDate() - retentionDays);
146
+ const result = this.db
147
+ .prepare("DELETE FROM sentinel_records WHERE fetched_at < ?")
148
+ .run(cutoff.toISOString());
149
+ return result.changes;
150
+ }
151
+ close() {
152
+ this.db.close();
153
+ }
154
+ }
155
+ function rowToRecord(row) {
156
+ return {
157
+ id: row.id,
158
+ source: row.source,
159
+ sourceId: row.source_id,
160
+ query: row.query,
161
+ title: row.title,
162
+ text: row.text,
163
+ author: row.author,
164
+ url: row.url,
165
+ publishedAt: row.published_at,
166
+ fetchedAt: row.fetched_at,
167
+ engagement: JSON.parse(row.engagement_json),
168
+ sentiment: {
169
+ score: row.sentiment_score,
170
+ confidence: row.sentiment_confidence,
171
+ method: row.sentiment_method,
172
+ tickers: JSON.parse(row.tickers_json),
173
+ },
174
+ metadata: JSON.parse(row.metadata_json),
175
+ };
176
+ }
177
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/sentiment/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { TrendBucket, TrendResult, DivergenceResult, SentimentSource } from "./types.js";
2
+ export declare function renderSparkline(values: number[]): string;
3
+ export declare function computeTrend(buckets: TrendBucket[], source: SentimentSource | "aggregate"): TrendResult;
4
+ export interface SourceStats {
5
+ avg: number;
6
+ count: number;
7
+ }
8
+ export declare function computeDivergence(sources: {
9
+ twitter?: SourceStats;
10
+ reddit?: SourceStats;
11
+ web?: SourceStats;
12
+ finnhub?: SourceStats;
13
+ }, threshold: number): DivergenceResult;
@@ -0,0 +1,73 @@
1
+ const SPARKLINE_CHARS = "▁▂▃▄▅▆▇█";
2
+ export function renderSparkline(values) {
3
+ if (values.length === 0)
4
+ return "";
5
+ const min = Math.min(...values);
6
+ const max = Math.max(...values);
7
+ const range = max - min;
8
+ return values
9
+ .map((v) => {
10
+ if (range === 0)
11
+ return SPARKLINE_CHARS[3]; // middle block for flat
12
+ const idx = Math.round(((v - min) / range) * 7);
13
+ return SPARKLINE_CHARS[idx];
14
+ })
15
+ .join("");
16
+ }
17
+ export function computeTrend(buckets, source) {
18
+ const scores = buckets.map((b) => b.avgScore);
19
+ const totalCount = buckets.reduce((sum, b) => sum + b.count, 0);
20
+ const avgScore = totalCount === 0 ? 0 : buckets.reduce((sum, b) => sum + b.avgScore * b.count, 0) / totalCount;
21
+ const sparkline = renderSparkline(scores);
22
+ // Delta: last value minus first value
23
+ const delta = scores.length >= 2 ? scores[scores.length - 1] - scores[0] : 0;
24
+ let direction;
25
+ if (delta > 0.1)
26
+ direction = "rising";
27
+ else if (delta < -0.1)
28
+ direction = "falling";
29
+ else
30
+ direction = "stable";
31
+ return { source, sparkline, avgScore, count: totalCount, direction, delta };
32
+ }
33
+ export function computeDivergence(sources, threshold) {
34
+ const retailSources = [];
35
+ if (sources.twitter && sources.twitter.count >= 5)
36
+ retailSources.push(sources.twitter);
37
+ if (sources.reddit && sources.reddit.count >= 5)
38
+ retailSources.push(sources.reddit);
39
+ const newsSources = [];
40
+ if (sources.web && sources.web.count >= 5)
41
+ newsSources.push(sources.web);
42
+ if (sources.finnhub && sources.finnhub.count >= 5)
43
+ newsSources.push(sources.finnhub);
44
+ if (retailSources.length === 0 || newsSources.length === 0) {
45
+ return {
46
+ detected: false,
47
+ retailAvg: null,
48
+ newsAvg: null,
49
+ gap: null,
50
+ message: "Insufficient data for divergence analysis",
51
+ };
52
+ }
53
+ const retailAvg = retailSources.reduce((sum, s) => sum + s.avg, 0) / retailSources.length;
54
+ const newsAvg = newsSources.reduce((sum, s) => sum + s.avg, 0) / newsSources.length;
55
+ const gap = Math.abs(retailAvg - newsAvg);
56
+ if (gap > threshold) {
57
+ return {
58
+ detected: true,
59
+ retailAvg,
60
+ newsAvg,
61
+ gap,
62
+ message: `⚠ DIVERGENCE: Retail sentiment (${retailAvg >= 0 ? "+" : ""}${retailAvg.toFixed(2)}) vs news sentiment (${newsAvg >= 0 ? "+" : ""}${newsAvg.toFixed(2)}) — gap of ${gap.toFixed(2)}.`,
63
+ };
64
+ }
65
+ return {
66
+ detected: false,
67
+ retailAvg,
68
+ newsAvg,
69
+ gap,
70
+ message: "Sources broadly aligned",
71
+ };
72
+ }
73
+ //# sourceMappingURL=trends.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trends.js","sourceRoot":"","sources":["../../src/sentiment/trends.ts"],"names":[],"mappings":"AAEA,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC,MAAM,UAAU,eAAe,CAAC,MAAgB;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IAExB,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACpE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,OAAsB,EACtB,MAAqC;IAErC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC;IAE/G,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAE1C,sCAAsC;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,IAAI,SAA0C,CAAC;IAC/C,IAAI,KAAK,GAAG,GAAG;QAAE,SAAS,GAAG,QAAQ,CAAC;SACjC,IAAI,KAAK,GAAG,CAAC,GAAG;QAAE,SAAS,GAAG,SAAS,CAAC;;QACxC,SAAS,GAAG,QAAQ,CAAC;IAE1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9E,CAAC;AAOD,MAAM,UAAU,iBAAiB,CAC/B,OAAkG,EAClG,SAAiB;IAEjB,MAAM,aAAa,GAAkB,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;QAAE,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvF,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;QAAE,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpF,MAAM,WAAW,GAAkB,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;QAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;QAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAErF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,GAAG,EAAE,IAAI;YACT,OAAO,EAAE,2CAA2C;SACrD,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC;IAC1F,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC;IACpF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,CAAC;IAE1C,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,SAAS;YACT,OAAO;YACP,GAAG;YACH,OAAO,EAAE,mCAAmC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;SAChM,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,SAAS;QACT,OAAO;QACP,GAAG;QACH,OAAO,EAAE,yBAAyB;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,66 @@
1
+ export declare const SENTIMENT_SOURCES: readonly ["twitter", "reddit", "web", "finnhub"];
2
+ export type SentimentSource = (typeof SENTIMENT_SOURCES)[number];
3
+ export interface SentinelEngagement {
4
+ score: number;
5
+ replies: number | null;
6
+ shares: number | null;
7
+ views: number | null;
8
+ }
9
+ export interface SentinelSentiment {
10
+ score: number;
11
+ confidence: number;
12
+ method: "keyword";
13
+ tickers: string[];
14
+ }
15
+ export interface SentinelRecord {
16
+ id: string;
17
+ source: SentimentSource;
18
+ sourceId: string;
19
+ query: string;
20
+ title: string | null;
21
+ text: string;
22
+ author: string | null;
23
+ url: string;
24
+ publishedAt: string | null;
25
+ fetchedAt: string;
26
+ engagement: SentinelEngagement;
27
+ sentiment: SentinelSentiment;
28
+ metadata: Record<string, unknown>;
29
+ }
30
+ export declare function isSentinelRecord(val: unknown): val is SentinelRecord;
31
+ export interface SentimentAdapter {
32
+ source: SentimentSource;
33
+ fetch(query: string, options?: {
34
+ hours?: number;
35
+ }): Promise<SentinelRecord[]>;
36
+ }
37
+ export interface ScorerOptions {
38
+ /** Minimum confidence for a keyword score to be considered "high confidence" */
39
+ confidenceThreshold?: number;
40
+ }
41
+ export interface TrendBucket {
42
+ timestamp: string;
43
+ avgScore: number;
44
+ count: number;
45
+ }
46
+ export interface TrendResult {
47
+ source: SentimentSource | "aggregate";
48
+ sparkline: string;
49
+ avgScore: number;
50
+ count: number;
51
+ direction: "rising" | "falling" | "stable";
52
+ delta: number;
53
+ }
54
+ export interface DivergenceResult {
55
+ detected: boolean;
56
+ retailAvg: number | null;
57
+ newsAvg: number | null;
58
+ gap: number | null;
59
+ message: string;
60
+ }
61
+ export interface SentimentSummary {
62
+ fresh: SentinelRecord[];
63
+ trend: TrendResult[] | null;
64
+ divergence: DivergenceResult | null;
65
+ warnings: string[];
66
+ }
@@ -0,0 +1,54 @@
1
+ export const SENTIMENT_SOURCES = ["twitter", "reddit", "web", "finnhub"];
2
+ export function isSentinelRecord(val) {
3
+ if (val === null || typeof val !== "object")
4
+ return false;
5
+ const r = val;
6
+ if (typeof r.id !== "string")
7
+ return false;
8
+ if (typeof r.source !== "string" || !SENTIMENT_SOURCES.includes(r.source))
9
+ return false;
10
+ if (typeof r.sourceId !== "string")
11
+ return false;
12
+ if (typeof r.query !== "string")
13
+ return false;
14
+ if (r.title !== null && typeof r.title !== "string")
15
+ return false;
16
+ if (typeof r.text !== "string")
17
+ return false;
18
+ if (r.author !== null && typeof r.author !== "string")
19
+ return false;
20
+ if (typeof r.url !== "string")
21
+ return false;
22
+ if (r.publishedAt !== null && typeof r.publishedAt !== "string")
23
+ return false;
24
+ if (typeof r.fetchedAt !== "string")
25
+ return false;
26
+ const eng = r.engagement;
27
+ if (eng === null || typeof eng !== "object")
28
+ return false;
29
+ const e = eng;
30
+ if (typeof e.score !== "number")
31
+ return false;
32
+ if (e.replies !== null && typeof e.replies !== "number")
33
+ return false;
34
+ if (e.shares !== null && typeof e.shares !== "number")
35
+ return false;
36
+ if (e.views !== null && typeof e.views !== "number")
37
+ return false;
38
+ const sent = r.sentiment;
39
+ if (sent === null || typeof sent !== "object")
40
+ return false;
41
+ const s = sent;
42
+ if (typeof s.score !== "number" || s.score < -1 || s.score > 1)
43
+ return false;
44
+ if (typeof s.confidence !== "number" || s.confidence < 0 || s.confidence > 1)
45
+ return false;
46
+ if (s.method !== "keyword")
47
+ return false;
48
+ if (!Array.isArray(s.tickers))
49
+ return false;
50
+ if (r.metadata === null || typeof r.metadata !== "object")
51
+ return false;
52
+ return true;
53
+ }
54
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sentiment/types.ts"],"names":[],"mappings":"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"}
@@ -16,9 +16,10 @@ You provide data-driven analysis for stocks, crypto, macro economics, and portfo
16
16
  - **Fundamentals**: get_company_overview, get_financials, get_earnings, compute_dcf, compare_companies, get_sec_filings — company financials, valuation metrics, DCF intrinsic value, peer comparison, and SEC EDGAR filings (10-K, 10-Q, 8-K)
17
17
  - **Technical Analysis**: get_technical_indicators, backtest_strategy — SMA, EMA, RSI, MACD, Bollinger Bands, OBV, VWAP computed from price data, plus simple strategy backtesting
18
18
  - **Macro**: get_economic_data, get_fear_greed — FRED economic indicators and market sentiment
19
- - **Sentiment**: get_reddit_sentiment, get_reddit_discussions — retail sentiment from financial Reddit communities
19
+ - **Sentiment**: get_reddit_sentiment, get_twitter_sentiment, get_web_sentiment, get_sentiment_trend, get_sentiment_summary — retail and news sentiment from Reddit, Twitter/X, and web sources with historical trends
20
20
  - **Options**: get_option_chain — full options chain with strikes, bids/asks, volume, OI, IV, and computed Greeks (delta, gamma, theta, vega, rho)
21
21
  - **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, track_prediction — position tracking, P&L, Sharpe ratio, VaR, watchlist with price alerts, correlation matrix, and prediction tracking with accuracy scoring
22
+ - **User Interaction**: ask_user — ask clarification questions; trigger_twitter_login — open a browser for Twitter/X login
22
23
 
23
24
  ## Analytical Framework
24
25
  When analyzing a stock, follow these steps in order:
@@ -29,7 +30,7 @@ When analyzing a stock, follow these steps in order:
29
30
  5. **SYNTHESIS**: State your reasoning chain explicitly: "Because [data point] + [data point], I conclude [thesis]."
30
31
 
31
32
  ## Guidelines
32
- - Always fetch data with tools before stating prices, ratios, or metrics. Never guess financial numbers.
33
+ - Always fetch data with tools before stating prices, ratios, or metrics. Never guess financial numbers. Every substantive response should be backed by at least one tool call — if you find yourself writing a response with zero tool calls, stop and think about what data would make it better.
33
34
  - For options analysis, use get_option_chain to see the full chain with Greeks. Pay attention to put/call ratio, unusual volume, and IV levels.
34
35
  - Present numerical data in tables when comparing multiple securities.
35
36
  - Include data timestamps so users know how fresh the information is.
@@ -39,6 +40,63 @@ When analyzing a stock, follow these steps in order:
39
40
  - Reuse prior tool outputs when they already answer the question. Do not re-fetch the same symbol and parameters unless you need a missing field or fresher timestamp.
40
41
  - If one provider is missing data, continue with the remaining tools and clearly label unavailable metrics instead of aborting the entire response.
41
42
 
43
+ ## Handling skipped data sources
44
+ Tool results may include a tagged line beginning with \`[OPENCANDLE_SKIPPED ...]\`, \`[OPENCANDLE_CREDENTIAL_REQUIRED ...]\`, or \`[OPENCANDLE_SOFT_DEGRADED ...]\`. These signal that a data source was either skipped at the user's request, not configured, or fell back to a keyless alternative (e.g. Brave → DuckDuckGo, Exa → keyless MCP). When you see one or more of these in your tool results:
45
+ 1. Continue the analysis using whatever other data you have. Do NOT apologize, do NOT treat it as an error, do NOT suggest the user fix something they already declined.
46
+ 2. At the end of your final answer, add a \`**Data gaps**\` section listing each affected provider as one bullet, quoting the \`remediation\` string verbatim (e.g. \`run /connect financials to unlock\`). Aggregate \`[OPENCANDLE_SKIPPED ...]\` and \`[OPENCANDLE_SOFT_DEGRADED ...]\` tags together — both are "you didn't get the keyed source" signals from the user's perspective.
47
+ 3. For \`[OPENCANDLE_SOFT_DEGRADED ...]\` tags, briefly name the fallback that was used so the user understands where the data actually came from (e.g. "Web search used DuckDuckGo instead of Brave").
48
+ 4. EXCEPTION: if the \`remediation\` string contains the literal text \`(silenced)\`, the user has explicitly asked not to be pestered about this provider. Still describe the omission but OMIT the \`/connect\` remediation text for that bullet. Something like "Finnhub news was omitted (silenced)" is enough.
49
+ 5. A \`[OPENCANDLE_CONNECTED ...]\` tag means a credential was JUST saved mid-turn. Acknowledge it briefly ("Alpha Vantage just connected") and tell the user to re-run the previous request to fetch the data. Pi does not currently support re-dispatching the original tool call automatically.
50
+
51
+ ## When to Ask for Clarification
52
+ Use the ask_user tool BEFORE proceeding when:
53
+ - The request is broad or vague (e.g., "analyze the market" without specifying which asset or sector)
54
+ - Required information is missing: a ticker symbol for asset analysis, a budget for portfolio construction, or a time horizon for recommendations
55
+ - Multiple valid analysis approaches exist and the user has not indicated a preference (e.g., fundamental vs. technical, short-term vs. long-term)
56
+ - Risk tolerance is unclear for portfolio or options recommendations
57
+
58
+ Do NOT ask clarifying questions when:
59
+ - The request is clear and specific (e.g., "get AAPL quote", "analyze BTC")
60
+ - You can reasonably infer the intent from context or prior conversation
61
+ - A reasonable default exists and can be disclosed in the Assumptions block instead
62
+ - The user explicitly asks you to use your judgment
63
+
64
+ Keep questions concise and offer specific options when possible. Prefer select-type questions over open-ended text input to minimize user effort.
65
+
66
+ ## Twitter Authentication
67
+ get_twitter_sentiment requires a one-time Twitter/X login. When the tool returns [LOGIN_NEEDED]:
68
+ 1. Use ask_user (confirm) to ask: "Twitter sentiment requires a one-time login. A browser will open — want to proceed?"
69
+ 2. If confirmed, call trigger_twitter_login. It opens a browser, waits for the user to log in, and returns success/failure.
70
+ 3. On success, retry get_twitter_sentiment with the original query.
71
+ If the user declines, skip Twitter sentiment and continue with other available data sources.
72
+
73
+ ## After Clarification: Fetch Data Immediately
74
+ CRITICAL: After ask_user answers come back, your NEXT action MUST be tool calls — not a text response. You are a data agent, not a chatbot. Never respond with generic investment categories or tell the user to come back with tickers. YOU pick the relevant assets and indicators based on what you learned, then fetch the data.
75
+
76
+ Playbooks by scenario (use these as starting points, adapt as needed):
77
+
78
+ **"Where should I put $X" / general investment advice:**
79
+ 1. Fetch get_fear_greed — is the market fearful or greedy right now?
80
+ 2. Fetch get_economic_data for key macro indicators (Fed funds rate, CPI, unemployment)
81
+ 3. Fetch get_stock_quote for benchmark ETFs relevant to their goal (e.g., SPY, QQQ, VTI for growth; BND, SCHD for income; GLD, BTC for alternatives)
82
+ 4. Fetch get_technical_indicators on those ETFs to assess current momentum and overbought/oversold conditions
83
+ 5. Synthesize: "Given current market conditions [data], here's how I'd think about allocating $X across [specific assets] and why"
84
+
85
+ **"Build me a portfolio" / allocation request:**
86
+ 1. Pick 5-8 candidate assets matching their stated goal and risk level
87
+ 2. Fetch get_stock_quote and get_company_overview for each
88
+ 3. Fetch analyze_correlation to check diversification
89
+ 4. Present a concrete allocation with percentages, backed by the data you fetched
90
+
91
+ **"What's happening in the market" / market outlook:**
92
+ 1. Fetch get_stock_quote for SPY, QQQ, IWM, DIA (major indices)
93
+ 2. Fetch get_fear_greed
94
+ 3. Fetch get_economic_data for 2-3 key FRED series
95
+ 4. Fetch get_reddit_sentiment for current retail mood
96
+ 5. Synthesize a market snapshot with data points
97
+
98
+ If you are about to write a response that contains zero tool call results, STOP. Go fetch data first.
99
+
42
100
  ## Assumption Disclosure
43
101
  Workflow prompts include a pre-rendered "Assumptions" block with correct source attribution (user-specified, saved preference, or default). Start your response with that block exactly as written. Do NOT independently relabel any value's source anywhere in your response. The assumptions block is the single authoritative provenance representation.
44
102
  ${memorySection}
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,aAAsB;IACtD,MAAM,aAAa,GAAG,aAAa;QACjC,CAAC,CAAC;;;;EAIJ,aAAa,EAAE;QACb,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCP,aAAa;;;mNAGoM,CAAC;AACpN,CAAC"}
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,aAAsB;IACtD,MAAM,aAAa,GAAG,aAAa;QACjC,CAAC,CAAC;;;;EAIJ,aAAa,EAAE;QACb,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6FP,aAAa;;;mNAGoM,CAAC;AACpN,CAAC"}
@@ -8,12 +8,16 @@ export { agentToolToPiTool } from "./pi/tool-adapter.js";
8
8
  export { Type } from "@sinclair/typebox";
9
9
  export type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
10
  export type { AgentTool } from "@mariozechner/pi-agent-core";
11
- export declare function getThirdPartyToolDescriptions(): ReadonlyArray<{
11
+ export declare function getAddonToolDescriptions(): ReadonlyArray<{
12
12
  name: string;
13
13
  description: string;
14
14
  }>;
15
- export interface RegisterToolsOptions {
16
- namespace?: string;
17
- description?: string;
15
+ export interface ToolConfig<TParams extends TSchema, TDetails = unknown> {
16
+ name: string;
17
+ label: string;
18
+ description: string;
19
+ parameters: TParams;
20
+ execute: AgentTool<TParams, TDetails>["execute"];
18
21
  }
19
- export declare function registerOpenCandleTools<TParams extends TSchema>(pi: ExtensionAPI, tools: AgentTool<TParams>[], options?: RegisterToolsOptions): void;
22
+ export declare function createTool<TParams extends TSchema, TDetails = unknown>(config: ToolConfig<TParams, TDetails>): AgentTool<TParams, TDetails>;
23
+ export declare function registerTools<TParams extends TSchema>(pi: ExtensionAPI, tools: AgentTool<TParams>[]): void;