opencandle 0.6.0 → 0.8.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 (201) hide show
  1. package/README.md +43 -23
  2. package/dist/cli-main.d.ts +1 -0
  3. package/dist/cli-main.js +200 -0
  4. package/dist/cli-main.js.map +1 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +14 -194
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +10 -0
  9. package/dist/config.js +13 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/doctor/cli-command.d.ts +1 -0
  12. package/dist/doctor/cli-command.js +69 -0
  13. package/dist/doctor/cli-command.js.map +1 -0
  14. package/dist/doctor/render.d.ts +2 -0
  15. package/dist/doctor/render.js +32 -0
  16. package/dist/doctor/render.js.map +1 -0
  17. package/dist/doctor/report.d.ts +63 -0
  18. package/dist/doctor/report.js +500 -0
  19. package/dist/doctor/report.js.map +1 -0
  20. package/dist/infra/index.d.ts +0 -1
  21. package/dist/infra/index.js +0 -1
  22. package/dist/infra/index.js.map +1 -1
  23. package/dist/infra/native-dependencies.d.ts +8 -0
  24. package/dist/infra/native-dependencies.js +49 -0
  25. package/dist/infra/native-dependencies.js.map +1 -1
  26. package/dist/infra/node-version.js +2 -5
  27. package/dist/infra/node-version.js.map +1 -1
  28. package/dist/monitor.d.ts +1 -1
  29. package/dist/monitor.js +2 -1
  30. package/dist/monitor.js.map +1 -1
  31. package/dist/onboarding/connect.d.ts +2 -2
  32. package/dist/onboarding/connect.js +10 -3
  33. package/dist/onboarding/connect.js.map +1 -1
  34. package/dist/onboarding/provider-status.d.ts +46 -0
  35. package/dist/onboarding/provider-status.js +256 -0
  36. package/dist/onboarding/provider-status.js.map +1 -0
  37. package/dist/onboarding/providers.d.ts +85 -8
  38. package/dist/onboarding/providers.js +87 -9
  39. package/dist/onboarding/providers.js.map +1 -1
  40. package/dist/onboarding/state.d.ts +1 -0
  41. package/dist/onboarding/state.js +5 -0
  42. package/dist/onboarding/state.js.map +1 -1
  43. package/dist/onboarding/tool-tags.d.ts +12 -1
  44. package/dist/onboarding/tool-tags.js +31 -1
  45. package/dist/onboarding/tool-tags.js.map +1 -1
  46. package/dist/onboarding/validation.d.ts +2 -2
  47. package/dist/onboarding/validation.js.map +1 -1
  48. package/dist/pi/opencandle-extension.js +91 -15
  49. package/dist/pi/opencandle-extension.js.map +1 -1
  50. package/dist/pi/session.d.ts +0 -1
  51. package/dist/pi/session.js +2 -1
  52. package/dist/pi/session.js.map +1 -1
  53. package/dist/pi/tool-adapter.d.ts +4 -1
  54. package/dist/pi/tool-adapter.js +5 -4
  55. package/dist/pi/tool-adapter.js.map +1 -1
  56. package/dist/prompts/context-builder.js +1 -1
  57. package/dist/prompts/policy-cards.js +9 -5
  58. package/dist/prompts/policy-cards.js.map +1 -1
  59. package/dist/prompts/workflow-prompts.js +10 -6
  60. package/dist/prompts/workflow-prompts.js.map +1 -1
  61. package/dist/providers/external-tool-command.d.ts +17 -0
  62. package/dist/providers/external-tool-command.js +114 -0
  63. package/dist/providers/external-tool-command.js.map +1 -0
  64. package/dist/providers/external-tool-error.d.ts +10 -0
  65. package/dist/providers/external-tool-error.js +21 -0
  66. package/dist/providers/external-tool-error.js.map +1 -0
  67. package/dist/providers/reddit-cli.d.ts +32 -0
  68. package/dist/providers/reddit-cli.js +172 -0
  69. package/dist/providers/reddit-cli.js.map +1 -0
  70. package/dist/providers/reddit.d.ts +1 -1
  71. package/dist/providers/reddit.js +7 -35
  72. package/dist/providers/reddit.js.map +1 -1
  73. package/dist/providers/twitter-cli.d.ts +36 -0
  74. package/dist/providers/twitter-cli.js +124 -0
  75. package/dist/providers/twitter-cli.js.map +1 -0
  76. package/dist/providers/twitter.d.ts +0 -8
  77. package/dist/providers/twitter.js +4 -54
  78. package/dist/providers/twitter.js.map +1 -1
  79. package/dist/providers/wrap-provider.js +30 -0
  80. package/dist/providers/wrap-provider.js.map +1 -1
  81. package/dist/providers/yahoo-finance.js +53 -32
  82. package/dist/providers/yahoo-finance.js.map +1 -1
  83. package/dist/routing/classify-intent.js +7 -3
  84. package/dist/routing/classify-intent.js.map +1 -1
  85. package/dist/routing/planning.d.ts +1 -1
  86. package/dist/routing/planning.js +9 -6
  87. package/dist/routing/planning.js.map +1 -1
  88. package/dist/routing/router.js +7 -3
  89. package/dist/routing/router.js.map +1 -1
  90. package/dist/runtime/answer-contracts.d.ts +1 -1
  91. package/dist/runtime/answer-contracts.js +12 -1
  92. package/dist/runtime/answer-contracts.js.map +1 -1
  93. package/dist/runtime/tool-defaults-wrapper.js +6 -2
  94. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  95. package/dist/sentiment/index.d.ts +1 -0
  96. package/dist/sentiment/index.js +1 -0
  97. package/dist/sentiment/index.js.map +1 -1
  98. package/dist/sentiment/insights.d.ts +17 -0
  99. package/dist/sentiment/insights.js +206 -0
  100. package/dist/sentiment/insights.js.map +1 -0
  101. package/dist/sentiment/pipeline.js +13 -1
  102. package/dist/sentiment/pipeline.js.map +1 -1
  103. package/dist/sentiment/scorer.d.ts +2 -0
  104. package/dist/sentiment/scorer.js +10 -1
  105. package/dist/sentiment/scorer.js.map +1 -1
  106. package/dist/sentiment/types.d.ts +2 -0
  107. package/dist/sentiment/types.js.map +1 -1
  108. package/dist/system-prompt.js +3 -7
  109. package/dist/system-prompt.js.map +1 -1
  110. package/dist/tools/index.d.ts +5 -2
  111. package/dist/tools/index.js +8 -8
  112. package/dist/tools/index.js.map +1 -1
  113. package/dist/tools/sentiment/insight-format.d.ts +2 -0
  114. package/dist/tools/sentiment/insight-format.js +36 -0
  115. package/dist/tools/sentiment/insight-format.js.map +1 -0
  116. package/dist/tools/sentiment/query-match.d.ts +3 -0
  117. package/dist/tools/sentiment/query-match.js +113 -0
  118. package/dist/tools/sentiment/query-match.js.map +1 -0
  119. package/dist/tools/sentiment/reddit-sentiment.d.ts +12 -1
  120. package/dist/tools/sentiment/reddit-sentiment.js +263 -117
  121. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  122. package/dist/tools/sentiment/sentiment-summary.d.ts +9 -1
  123. package/dist/tools/sentiment/sentiment-summary.js +217 -201
  124. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  125. package/dist/tools/sentiment/twitter-sentiment.d.ts +11 -1
  126. package/dist/tools/sentiment/twitter-sentiment.js +187 -64
  127. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  128. package/dist/tools/sentiment/web-sentiment.js +4 -0
  129. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  130. package/dist/types/sentiment.d.ts +52 -0
  131. package/gui/server/http-routes.ts +419 -0
  132. package/gui/server/invoke-tool.ts +17 -3
  133. package/gui/server/model-setup.ts +10 -3
  134. package/gui/server/projector.ts +6 -2
  135. package/gui/server/server.ts +30 -275
  136. package/gui/server/tool-metadata.ts +80 -16
  137. package/gui/server/ws-hub.ts +19 -0
  138. package/gui/web/dist/assets/CatalogOverlay-CCVKwBUB.js +1 -0
  139. package/gui/web/dist/assets/index-Dm4Aom2_.js +69 -0
  140. package/gui/web/dist/assets/{index-2KZtKBmu.css → index-UzZUg3dx.css} +1 -1
  141. package/gui/web/dist/index.html +2 -2
  142. package/package.json +17 -14
  143. package/src/cli-main.ts +233 -0
  144. package/src/cli.ts +14 -226
  145. package/src/config.ts +27 -0
  146. package/src/doctor/cli-command.ts +83 -0
  147. package/src/doctor/render.ts +37 -0
  148. package/src/doctor/report.ts +638 -0
  149. package/src/infra/index.ts +0 -1
  150. package/src/infra/native-dependencies.ts +67 -0
  151. package/src/infra/node-version.ts +2 -5
  152. package/src/monitor.ts +3 -1
  153. package/src/onboarding/connect.ts +20 -4
  154. package/src/onboarding/provider-status.ts +382 -0
  155. package/src/onboarding/providers.ts +148 -18
  156. package/src/onboarding/state.ts +9 -0
  157. package/src/onboarding/tool-tags.ts +45 -2
  158. package/src/onboarding/validation.ts +2 -2
  159. package/src/pi/opencandle-extension.ts +115 -17
  160. package/src/pi/session.ts +2 -1
  161. package/src/pi/tool-adapter.ts +14 -4
  162. package/src/prompts/context-builder.ts +1 -1
  163. package/src/prompts/policy-cards.ts +13 -5
  164. package/src/prompts/workflow-prompts.ts +13 -6
  165. package/src/providers/external-tool-command.ts +164 -0
  166. package/src/providers/external-tool-error.ts +20 -0
  167. package/src/providers/reddit-cli.ts +286 -0
  168. package/src/providers/reddit.ts +7 -63
  169. package/src/providers/twitter-cli.ts +202 -0
  170. package/src/providers/twitter.ts +4 -73
  171. package/src/providers/wrap-provider.ts +34 -0
  172. package/src/providers/yahoo-finance.ts +65 -32
  173. package/src/routing/classify-intent.ts +11 -6
  174. package/src/routing/planning.ts +16 -10
  175. package/src/routing/router.ts +11 -6
  176. package/src/runtime/answer-contracts.ts +23 -2
  177. package/src/runtime/tool-defaults-wrapper.ts +12 -2
  178. package/src/sentiment/index.ts +1 -0
  179. package/src/sentiment/insights.ts +269 -0
  180. package/src/sentiment/pipeline.ts +13 -1
  181. package/src/sentiment/scorer.ts +12 -1
  182. package/src/sentiment/types.ts +3 -0
  183. package/src/system-prompt.ts +3 -7
  184. package/src/tools/index.ts +9 -8
  185. package/src/tools/sentiment/insight-format.ts +50 -0
  186. package/src/tools/sentiment/query-match.ts +117 -0
  187. package/src/tools/sentiment/reddit-sentiment.ts +354 -141
  188. package/src/tools/sentiment/sentiment-summary.ts +283 -237
  189. package/src/tools/sentiment/twitter-sentiment.ts +262 -78
  190. package/src/tools/sentiment/web-sentiment.ts +4 -0
  191. package/src/types/sentiment.ts +59 -0
  192. package/dist/infra/browser.d.ts +0 -35
  193. package/dist/infra/browser.js +0 -105
  194. package/dist/infra/browser.js.map +0 -1
  195. package/dist/tools/interaction/twitter-login.d.ts +0 -8
  196. package/dist/tools/interaction/twitter-login.js +0 -87
  197. package/dist/tools/interaction/twitter-login.js.map +0 -1
  198. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +0 -1
  199. package/gui/web/dist/assets/index-CveNgtDg.js +0 -69
  200. package/src/infra/browser.ts +0 -113
  201. package/src/tools/interaction/twitter-login.ts +0 -105
@@ -0,0 +1,114 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { delimiter, isAbsolute, join, sep } from "node:path";
5
+ const DEFAULT_TIMEOUT_MS = 20_000;
6
+ const DEFAULT_MAX_OUTPUT_CHARS = 2_000_000;
7
+ export function getExternalToolCommandCandidates(command, options = {}) {
8
+ if (isPathLikeCommand(command))
9
+ return [command];
10
+ const env = options.env ?? process.env;
11
+ const platform = options.platform ?? process.platform;
12
+ const home = options.homeDir ?? homedir();
13
+ const candidates = [command];
14
+ const binDirs = [
15
+ env.OPENCANDLE_EXTERNAL_TOOL_BIN_DIR,
16
+ env.UV_TOOL_BIN_DIR,
17
+ env.XDG_BIN_HOME,
18
+ home ? join(home, ".local", "bin") : undefined,
19
+ ];
20
+ for (const dir of binDirs) {
21
+ if (!dir)
22
+ continue;
23
+ for (const executable of commandNames(command, platform)) {
24
+ candidates.push(join(dir, executable));
25
+ }
26
+ }
27
+ return [...new Set(candidates)];
28
+ }
29
+ export async function runExternalToolCommand(command, args, options = {}) {
30
+ const uvToolBinDirs = await resolveUvToolBinDirs(options);
31
+ const candidates = [
32
+ ...getExternalToolCommandCandidates(command),
33
+ ...uvToolBinDirs.flatMap((dir) => commandNames(command, process.platform).map((executable) => join(dir, executable))),
34
+ ].filter((candidate, index, all) => all.indexOf(candidate) === index);
35
+ const existingCandidates = candidates.filter((candidate) => candidate === command || existsSync(candidate));
36
+ return runCandidate(existingCandidates.length > 0 ? existingCandidates : [command], args, options);
37
+ }
38
+ async function resolveUvToolBinDirs(options) {
39
+ try {
40
+ const result = await spawnCommand("uv", ["tool", "dir", "--bin"], {
41
+ timeoutMs: Math.min(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 2_000),
42
+ maxOutputChars: 4_000,
43
+ });
44
+ if (result.code !== 0)
45
+ return [];
46
+ const binDir = result.stdout.trim();
47
+ return binDir ? [binDir] : [];
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
53
+ function runCandidate(candidates, args, options) {
54
+ const [command, ...fallbacks] = candidates;
55
+ return spawnCommand(command, args, options).catch((err) => {
56
+ if (err.code === "ENOENT" && fallbacks.length > 0) {
57
+ return runCandidate(fallbacks, args, options);
58
+ }
59
+ throw err;
60
+ });
61
+ }
62
+ function spawnCommand(command, args, options) {
63
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
64
+ const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
65
+ return new Promise((resolve, reject) => {
66
+ const child = spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
67
+ let stdout = "";
68
+ let stderr = "";
69
+ let settled = false;
70
+ const timeout = setTimeout(() => {
71
+ if (settled)
72
+ return;
73
+ settled = true;
74
+ child.kill("SIGTERM");
75
+ reject(new Error(`${command} timed out after ${timeoutMs}ms`));
76
+ }, timeoutMs);
77
+ child.stdout.on("data", (chunk) => {
78
+ stdout = (stdout + chunk.toString("utf8")).slice(0, maxOutputChars);
79
+ });
80
+ child.stderr.on("data", (chunk) => {
81
+ stderr = (stderr + chunk.toString("utf8")).slice(0, maxOutputChars);
82
+ });
83
+ child.on("error", (err) => {
84
+ if (settled)
85
+ return;
86
+ settled = true;
87
+ clearTimeout(timeout);
88
+ reject(err);
89
+ });
90
+ child.on("close", (code) => {
91
+ if (settled)
92
+ return;
93
+ settled = true;
94
+ clearTimeout(timeout);
95
+ resolve({ code, stdout, stderr });
96
+ });
97
+ });
98
+ }
99
+ function isPathLikeCommand(command) {
100
+ return (isAbsolute(command) || command.includes("/") || command.includes("\\") || command.includes(sep));
101
+ }
102
+ function commandNames(command, platform) {
103
+ if (platform !== "win32")
104
+ return [command];
105
+ const names = [command];
106
+ const pathExt = process.env.PATHEXT?.split(delimiter) ?? [".EXE", ".CMD", ".BAT"];
107
+ for (const ext of pathExt) {
108
+ if (!command.toLowerCase().endsWith(ext.toLowerCase())) {
109
+ names.push(`${command}${ext.toLowerCase()}`);
110
+ }
111
+ }
112
+ return names;
113
+ }
114
+ //# sourceMappingURL=external-tool-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-tool-command.js","sourceRoot":"","sources":["../../src/providers/external-tool-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare class ExternalToolError extends Error {
2
+ readonly toolName: string;
3
+ readonly code?: string | undefined;
4
+ constructor(toolName: string, message: string, code?: string | undefined);
5
+ }
6
+ export declare class ExternalToolNotInstalled extends Error {
7
+ readonly toolName: string;
8
+ readonly installCmd: string;
9
+ constructor(toolName: string, installCmd: string);
10
+ }
@@ -0,0 +1,21 @@
1
+ export class ExternalToolError extends Error {
2
+ toolName;
3
+ code;
4
+ constructor(toolName, message, code) {
5
+ super(message);
6
+ this.toolName = toolName;
7
+ this.code = code;
8
+ this.name = "ExternalToolError";
9
+ }
10
+ }
11
+ export class ExternalToolNotInstalled extends Error {
12
+ toolName;
13
+ installCmd;
14
+ constructor(toolName, installCmd) {
15
+ super(`${toolName} is not installed. Install it with: ${installCmd}`);
16
+ this.toolName = toolName;
17
+ this.installCmd = installCmd;
18
+ this.name = "ExternalToolNotInstalled";
19
+ }
20
+ }
21
+ //# sourceMappingURL=external-tool-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-tool-error.js","sourceRoot":"","sources":["../../src/providers/external-tool-error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAExB;IAEA;IAHlB,YACkB,QAAgB,EAChC,OAAe,EACC,IAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,SAAI,GAAJ,IAAI,CAAS;QAG7B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAE/B;IACA;IAFlB,YACkB,QAAgB,EAChB,UAAkB;QAElC,KAAK,CAAC,GAAG,QAAQ,uCAAuC,UAAU,EAAE,CAAC,CAAC;QAHtD,aAAQ,GAAR,QAAQ,CAAQ;QAChB,eAAU,GAAV,UAAU,CAAQ;QAGlC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ import { type ExternalToolCommandResult } from "./external-tool-command.js";
2
+ import type { RedditComment } from "./reddit.js";
3
+ export interface RdtPost {
4
+ readonly id: string;
5
+ readonly title: string;
6
+ readonly selftext: string;
7
+ readonly author: string;
8
+ readonly score: number;
9
+ readonly comments: number;
10
+ readonly subreddit?: string;
11
+ readonly url: string;
12
+ readonly permalink?: string;
13
+ readonly created: string;
14
+ }
15
+ type RdtCommandRunner = (command: string, args: readonly string[]) => Promise<ExternalToolCommandResult>;
16
+ export declare function setRdtCommandRunnerForTests(runner: RdtCommandRunner): void;
17
+ export declare function resetRdtCommandRunnerForTests(): void;
18
+ export declare function searchRedditPosts(query: string, opts: {
19
+ subreddit?: string;
20
+ limit: number;
21
+ }): Promise<RdtPost[]>;
22
+ export declare function listSubredditPosts(subreddit: string, opts: {
23
+ limit: number;
24
+ }): Promise<RdtPost[]>;
25
+ export declare function readRedditPost(postId: string, opts: {
26
+ limit: number;
27
+ }): Promise<{
28
+ post: RdtPost;
29
+ comments: RedditComment[];
30
+ }>;
31
+ export declare function redactSensitiveOutput(input: string): string;
32
+ export {};
@@ -0,0 +1,172 @@
1
+ import { runExternalToolCommand } from "./external-tool-command.js";
2
+ import { ExternalToolError, ExternalToolNotInstalled } from "./external-tool-error.js";
3
+ const RDT_BINARY = "rdt";
4
+ const RDT_INSTALL_CMD = "uv tool install rdt-cli";
5
+ const COMMAND_TIMEOUT_MS = 20_000;
6
+ const MAX_OUTPUT_CHARS = 2_000_000;
7
+ let commandRunner = runCommand;
8
+ export function setRdtCommandRunnerForTests(runner) {
9
+ commandRunner = runner;
10
+ }
11
+ export function resetRdtCommandRunnerForTests() {
12
+ commandRunner = runCommand;
13
+ }
14
+ export async function searchRedditPosts(query, opts) {
15
+ const args = ["search", query];
16
+ if (opts.subreddit) {
17
+ args.push("--subreddit", opts.subreddit);
18
+ }
19
+ args.push("--json", "--compact", "-n", String(opts.limit));
20
+ const data = await runRdt(args);
21
+ if (!Array.isArray(data)) {
22
+ throw new ExternalToolError(RDT_BINARY, "rdt-cli returned invalid search data");
23
+ }
24
+ return data.map(adaptPost).filter((post) => post.id.length > 0);
25
+ }
26
+ export async function listSubredditPosts(subreddit, opts) {
27
+ const data = await runRdt([
28
+ "sub",
29
+ subreddit,
30
+ "--json",
31
+ "--compact",
32
+ "-n",
33
+ String(opts.limit),
34
+ ]);
35
+ if (!Array.isArray(data)) {
36
+ throw new ExternalToolError(RDT_BINARY, "rdt-cli returned invalid subreddit data");
37
+ }
38
+ return data.map((raw) => adaptPost({ ...raw, subreddit })).filter((post) => post.id.length > 0);
39
+ }
40
+ export async function readRedditPost(postId, opts) {
41
+ const data = await runRdt([
42
+ "read",
43
+ postId,
44
+ "--json",
45
+ "-n",
46
+ String(opts.limit),
47
+ ]);
48
+ if (!Array.isArray(data)) {
49
+ throw new ExternalToolError(RDT_BINARY, "rdt-cli returned invalid read data");
50
+ }
51
+ const rawPost = data[0]?.data?.children?.find((child) => child.kind === "t3")?.data;
52
+ if (!rawPost) {
53
+ throw new ExternalToolError(RDT_BINARY, "rdt-cli read output did not include a post");
54
+ }
55
+ const post = adaptPost(rawPost);
56
+ const commentListing = data[1]?.data?.children ?? [];
57
+ const comments = commentListing
58
+ .filter((child) => child.kind === "t1")
59
+ .map((child) => adaptComment(child.data, post.permalink))
60
+ .filter((comment) => comment !== null)
61
+ .sort((a, b) => b.score - a.score)
62
+ .slice(0, opts.limit);
63
+ return { post, comments };
64
+ }
65
+ async function runRdt(args) {
66
+ let result;
67
+ try {
68
+ result = await commandRunner(RDT_BINARY, args);
69
+ }
70
+ catch (err) {
71
+ const nodeError = err;
72
+ if (nodeError.code === "ENOENT") {
73
+ throw new ExternalToolNotInstalled(RDT_BINARY, RDT_INSTALL_CMD);
74
+ }
75
+ throw new ExternalToolError(RDT_BINARY, redactSensitiveOutput(err instanceof Error ? err.message : String(err)));
76
+ }
77
+ if (result.code !== 0) {
78
+ const envelopeError = parseCliErrorEnvelope(result.stdout);
79
+ if (envelopeError) {
80
+ throw new ExternalToolError(RDT_BINARY, redactSensitiveOutput(envelopeError.message), envelopeError.code);
81
+ }
82
+ throw new ExternalToolError(RDT_BINARY, redactSensitiveOutput(result.stderr.trim() || `rdt-cli exited with code ${result.code}`));
83
+ }
84
+ let envelope;
85
+ try {
86
+ envelope = JSON.parse(result.stdout);
87
+ }
88
+ catch {
89
+ throw new ExternalToolError(RDT_BINARY, `rdt-cli returned non-JSON output: ${redactSensitiveOutput(result.stdout.slice(0, 200))}`);
90
+ }
91
+ if (!envelope.ok) {
92
+ throw new ExternalToolError(RDT_BINARY, redactSensitiveOutput(envelope.error?.message ?? "rdt-cli returned an error"), envelope.error?.code);
93
+ }
94
+ if (envelope.data === undefined) {
95
+ throw new ExternalToolError(RDT_BINARY, "rdt-cli returned no data");
96
+ }
97
+ return envelope.data;
98
+ }
99
+ function parseCliErrorEnvelope(stdout) {
100
+ try {
101
+ const parsed = JSON.parse(stdout);
102
+ if (parsed.ok !== false || typeof parsed.error?.message !== "string")
103
+ return null;
104
+ return {
105
+ code: typeof parsed.error.code === "string" ? parsed.error.code : undefined,
106
+ message: parsed.error.message,
107
+ };
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ function adaptPost(raw) {
114
+ const permalink = stringValue(raw.permalink);
115
+ const url = stringValue(raw.url) || (permalink ? `https://reddit.com${permalink}` : "");
116
+ return {
117
+ id: stringValue(raw.id),
118
+ title: stringValue(raw.title),
119
+ selftext: stringValue(raw.selftext),
120
+ author: stringValue(raw.author) || "unknown",
121
+ score: numberValue(raw.score),
122
+ comments: numberValue(raw.num_comments),
123
+ subreddit: stringValue(raw.subreddit) || undefined,
124
+ url,
125
+ permalink: permalink || undefined,
126
+ created: normalizeCreatedAt(raw.created_utc),
127
+ };
128
+ }
129
+ function adaptComment(raw, postPermalink) {
130
+ if (!raw || typeof raw.body !== "string" || raw.body.length === 0)
131
+ return null;
132
+ const permalink = stringValue(raw.permalink) || postPermalink || "";
133
+ return {
134
+ id: stringValue(raw.id),
135
+ body: raw.body,
136
+ author: stringValue(raw.author) || "unknown",
137
+ score: numberValue(raw.score),
138
+ permalink: permalink ? `https://reddit.com${permalink}` : "",
139
+ };
140
+ }
141
+ function normalizeCreatedAt(value) {
142
+ if (typeof value === "number" && Number.isFinite(value)) {
143
+ const millis = value > 1_000_000_000_000 ? value : value * 1000;
144
+ return new Date(millis).toISOString();
145
+ }
146
+ if (typeof value === "string" && value.length > 0) {
147
+ const millis = Date.parse(value);
148
+ if (!Number.isNaN(millis))
149
+ return new Date(millis).toISOString();
150
+ }
151
+ return new Date(0).toISOString();
152
+ }
153
+ function stringValue(value) {
154
+ return typeof value === "string" ? value : "";
155
+ }
156
+ function numberValue(value) {
157
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
158
+ }
159
+ export function redactSensitiveOutput(input) {
160
+ return input
161
+ .slice(0, MAX_OUTPUT_CHARS)
162
+ .replace(/\b([a-z0-9_]*(?:cookie|session|token)[a-z0-9_]*)\b\s*[:=]\s*[^;\s,)]+/gi, "$1=[redacted]")
163
+ .replace(/\b([a-z0-9_]*(?:cookie|session|token)[a-z0-9_]*)=([^;\s,)]+)/gi, "$1=[redacted]")
164
+ .replace(/(?:~|\/Users\/[^/\s]+|\/home\/[^/\s]+)?\/\.config\/rdt-cli\/credential\.json/g, "[redacted-credential-path]");
165
+ }
166
+ function runCommand(command, args) {
167
+ return runExternalToolCommand(command, args, {
168
+ timeoutMs: COMMAND_TIMEOUT_MS,
169
+ maxOutputChars: MAX_OUTPUT_CHARS,
170
+ });
171
+ }
172
+ //# sourceMappingURL=reddit-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reddit-cli.js","sourceRoot":"","sources":["../../src/providers/reddit-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGvF,MAAM,UAAU,GAAG,KAAK,CAAC;AACzB,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAClD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AA8DnC,IAAI,aAAa,GAAqB,UAAU,CAAC;AAEjD,MAAM,UAAU,2BAA2B,CAAC,MAAwB;IAClE,aAAa,GAAG,MAAM,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C,aAAa,GAAG,UAAU,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAa,EACb,IAA2C;IAE3C,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAe,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,sCAAsC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB,EACjB,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAe;QACtC,KAAK;QACL,SAAS;QACT,QAAQ;QACR,WAAW;QACX,IAAI;QACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;KACnB,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,yCAAyC,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAgD;QACvE,MAAM;QACN,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;KACnB,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,oCAAoC,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;IACpF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,4CAA4C,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAqB,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;IACrD,MAAM,QAAQ,GAAG,cAAc;SAC5B,MAAM,CAAC,CAAC,KAAK,EAA2C,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;SAC/E,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;SACxD,MAAM,CAAC,CAAC,OAAO,EAA4B,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC;SAC/D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAExB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,MAAM,CAAI,IAAuB;IAC9C,IAAI,MAAiC,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,GAA4B,CAAC;QAC/C,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChC,MAAM,IAAI,wBAAwB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,iBAAiB,CACzB,UAAU,EACV,qBAAqB,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CACxE,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,aAAa,GAAG,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,IAAI,iBAAiB,CACzB,UAAU,EACV,qBAAqB,CAAC,aAAa,CAAC,OAAO,CAAC,EAC5C,aAAa,CAAC,IAAI,CACnB,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,iBAAiB,CACzB,UAAU,EACV,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,4BAA4B,MAAM,CAAC,IAAI,EAAE,CAAC,CACzF,CAAC;IACJ,CAAC;IAED,IAAI,QAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAmB,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CACzB,UAAU,EACV,qCAAqC,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,iBAAiB,CACzB,UAAU,EACV,qBAAqB,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,2BAA2B,CAAC,EAC7E,QAAQ,CAAC,KAAK,EAAE,IAAI,CACrB,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,0BAA0B,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAc;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAG/B,CAAC;QACF,IAAI,MAAM,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClF,OAAO;YACL,IAAI,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YAC3E,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;SAC9B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,GAAe;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxF,OAAO;QACL,EAAE,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;QAC7B,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QAC5C,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;QAC7B,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC;QACvC,SAAS,EAAE,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS;QAClD,GAAG;QACH,SAAS,EAAE,SAAS,IAAI,SAAS;QACjC,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,WAAW,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,GAA8B,EAC9B,aAAsB;IAEtB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/E,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,aAAa,IAAI,EAAE,CAAC;IACpE,OAAO;QACL,EAAE,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QAC5C,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;QAC7B,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE;KAC7D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;QAChE,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,KAAK;SACT,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;SAC1B,OAAO,CACN,yEAAyE,EACzE,eAAe,CAChB;SACA,OAAO,CAAC,gEAAgE,EAAE,eAAe,CAAC;SAC1F,OAAO,CACN,+EAA+E,EAC/E,4BAA4B,CAC7B,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,IAAuB;IAC1D,OAAO,sBAAsB,CAAC,OAAO,EAAE,IAAI,EAAE;QAC3C,SAAS,EAAE,kBAAkB;QAC7B,cAAc,EAAE,gBAAgB;KACjC,CAAC,CAAC;AACL,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { RedditSentimentResult } from "../types/sentiment.js";
2
- export declare function getSubredditPosts(subreddit: string, limit?: number): Promise<RedditSentimentResult>;
2
+ export declare function getSubredditPosts(subreddit: string, limit?: number, query?: string): Promise<RedditSentimentResult>;
3
3
  export interface RedditComment {
4
4
  id: string;
5
5
  body: string;
@@ -1,29 +1,17 @@
1
1
  import { cache, STALE_LIMIT, TTL } from "../infra/cache.js";
2
- import { httpGet } from "../infra/http-client.js";
3
2
  import { rateLimiter } from "../infra/rate-limiter.js";
4
3
  import { BEARISH_TERMS, BULLISH_TERMS } from "../sentiment/keywords.js";
5
- const REDDIT_HEADERS = { "User-Agent": "OpenCandle/1.0 (financial analysis agent)" };
6
- export async function getSubredditPosts(subreddit, limit = 25) {
7
- const cacheKey = `reddit:${subreddit}:${limit}`;
4
+ import { listSubredditPosts, readRedditPost, searchRedditPosts } from "./reddit-cli.js";
5
+ export async function getSubredditPosts(subreddit, limit = 25, query) {
6
+ const cacheKey = `reddit:${subreddit}:${query ?? "hot"}:${limit}`;
8
7
  const cached = cache.get(cacheKey);
9
8
  if (cached)
10
9
  return cached;
11
10
  try {
12
11
  await rateLimiter.acquire("reddit");
13
- const url = `https://www.reddit.com/r/${encodeURIComponent(subreddit)}/hot.json?limit=${limit}`;
14
- const data = await httpGet(url, {
15
- headers: REDDIT_HEADERS,
16
- });
17
- const posts = data.data.children.map((child) => ({
18
- id: child.data.id,
19
- title: child.data.title,
20
- selftext: child.data.selftext ?? "",
21
- author: child.data.author ?? "unknown",
22
- score: child.data.score,
23
- comments: child.data.num_comments,
24
- url: `https://reddit.com${child.data.permalink}`,
25
- created: new Date(child.data.created_utc * 1000).toISOString(),
26
- }));
12
+ const posts = query
13
+ ? await searchRedditPosts(query, { subreddit, limit })
14
+ : await listSubredditPosts(subreddit, { limit });
27
15
  // Extract ticker-like mentions ($AAPL, $TSLA, etc.)
28
16
  const tickerRegex = /\$([A-Z]{1,5})\b/g;
29
17
  const mentionCounts = new Map();
@@ -65,23 +53,7 @@ export async function getPostComments(subreddit, postId, limit = 5) {
65
53
  if (cached)
66
54
  return cached;
67
55
  await rateLimiter.acquire("reddit_comments");
68
- const url = `https://www.reddit.com/r/${encodeURIComponent(subreddit)}/comments/${postId}.json`;
69
- const data = await httpGet(url, {
70
- headers: REDDIT_HEADERS,
71
- });
72
- // Comments are in the second listing element
73
- const commentListing = data[1]?.data?.children ?? [];
74
- const comments = commentListing
75
- .filter((c) => c.kind === "t1" && c.data.body)
76
- .sort((a, b) => (b.data.score ?? 0) - (a.data.score ?? 0))
77
- .slice(0, limit)
78
- .map((c) => ({
79
- id: c.data.id,
80
- body: c.data.body,
81
- author: c.data.author ?? "unknown",
82
- score: c.data.score ?? 0,
83
- permalink: `https://reddit.com${c.data.permalink ?? ""}`,
84
- }));
56
+ const { comments } = await readRedditPost(postId, { limit });
85
57
  cache.set(cacheKey, comments, COMMENT_TTL);
86
58
  return comments;
87
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reddit.js","sourceRoot":"","sources":["../../src/providers/reddit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reddit.js","sourceRoot":"","sources":["../../src/providers/reddit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAExF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,QAAgB,EAAE,EAClB,KAAc;IAEd,MAAM,QAAQ,GAAG,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;IAClE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAwB,QAAQ,CAAC,CAAC;IAC1D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK;YACjB,CAAC,CAAC,MAAM,iBAAiB,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YACtD,CAAC,CAAC,MAAM,kBAAkB,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAEnD,oDAAoD;QACpD,MAAM,WAAW,GAAG,mBAAmB,CAAC;QACxC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;QAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxB,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QACD,MAAM,WAAW,GAAG,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC;aAC7C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3B,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;aACZ,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAE7B,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QAExC,MAAM,MAAM,GAA0B;YACpC,SAAS;YACT,SAAS,EAAE,KAAK,CAAC,MAAM;YACvB,KAAK;YACL,WAAW;YACX,cAAc,EAAE,SAAS,CAAC,KAAK;YAC/B,YAAY,EAAE,SAAS,CAAC,OAAO;YAC/B,YAAY,EAAE,SAAS,CAAC,OAAO;YAC/B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAwB,QAAQ,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;QACrF,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC;QAC9B,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAYD,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAEjD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAiB,EACjB,MAAc,EACd,QAAgB,CAAC;IAEjB,MAAM,QAAQ,GAAG,mBAAmB,SAAS,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;IACnE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAkB,QAAQ,CAAC,CAAC;IACpD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAE7D,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,2DAA2D;AAE3D,MAAM,UAAU,cAAc,CAAC,KAA+B;IAK5D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACvC,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACjE,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACnE,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC;IAChC,OAAO;QACL,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK;QACpD,OAAO;QACP,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { TwitterTweet } from "../types/sentiment.js";
2
+ import { type ExternalToolCommandResult } from "./external-tool-command.js";
3
+ export interface RawTweet {
4
+ readonly id?: string;
5
+ readonly text?: string;
6
+ readonly author?: {
7
+ readonly username?: string;
8
+ readonly screenName?: string;
9
+ readonly name?: string;
10
+ };
11
+ readonly username?: string;
12
+ readonly url?: string;
13
+ readonly permanentUrl?: string;
14
+ readonly createdAt?: string | number;
15
+ readonly created_at?: string | number;
16
+ readonly likeCount?: number;
17
+ readonly likes?: number;
18
+ readonly retweetCount?: number;
19
+ readonly retweets?: number;
20
+ readonly replyCount?: number;
21
+ readonly replies?: number;
22
+ readonly viewCount?: number | null;
23
+ readonly views?: number | null;
24
+ readonly metrics?: {
25
+ readonly likes?: number;
26
+ readonly retweets?: number;
27
+ readonly replies?: number;
28
+ readonly views?: number | null;
29
+ };
30
+ }
31
+ type TwitterCliCommandRunner = (command: string, args: readonly string[]) => Promise<ExternalToolCommandResult>;
32
+ export declare function setTwitterCliCommandRunnerForTests(runner: TwitterCliCommandRunner): void;
33
+ export declare function resetTwitterCliCommandRunnerForTests(): void;
34
+ export declare function searchTweets(query: string, max?: number): Promise<TwitterTweet[]>;
35
+ export declare function redactSensitiveOutput(input: string): string;
36
+ export {};
@@ -0,0 +1,124 @@
1
+ import { runExternalToolCommand } from "./external-tool-command.js";
2
+ import { ExternalToolError, ExternalToolNotInstalled } from "./external-tool-error.js";
3
+ const TWITTER_CLI_BINARY = "twitter";
4
+ const TWITTER_CLI_TOOL_NAME = "twitter-cli";
5
+ const TWITTER_CLI_INSTALL_CMD = "uv tool install twitter-cli";
6
+ const COMMAND_TIMEOUT_MS = 20_000;
7
+ const MAX_OUTPUT_CHARS = 2_000_000;
8
+ let commandRunner = runCommand;
9
+ export function setTwitterCliCommandRunnerForTests(runner) {
10
+ commandRunner = runner;
11
+ }
12
+ export function resetTwitterCliCommandRunnerForTests() {
13
+ commandRunner = runCommand;
14
+ }
15
+ export async function searchTweets(query, max = 20) {
16
+ const envelope = await runTwitterCli([
17
+ "search",
18
+ query,
19
+ "--max",
20
+ String(max),
21
+ "-t",
22
+ "Latest",
23
+ "--json",
24
+ ]);
25
+ if (!envelope.ok) {
26
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, redactSensitiveOutput(envelope.error?.message ?? "twitter-cli returned an error"), envelope.error?.code);
27
+ }
28
+ if (!Array.isArray(envelope.data)) {
29
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, "twitter-cli returned invalid tweet data");
30
+ }
31
+ return envelope.data.map(adaptRawTweet);
32
+ }
33
+ async function runTwitterCli(args) {
34
+ let result;
35
+ try {
36
+ result = await commandRunner(TWITTER_CLI_BINARY, args);
37
+ }
38
+ catch (err) {
39
+ const nodeError = err;
40
+ if (nodeError.code === "ENOENT") {
41
+ throw new ExternalToolNotInstalled(TWITTER_CLI_TOOL_NAME, TWITTER_CLI_INSTALL_CMD);
42
+ }
43
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, redactSensitiveOutput(err instanceof Error ? err.message : String(err)));
44
+ }
45
+ if (result.code !== 0) {
46
+ const envelopeError = parseCliErrorEnvelope(result.stdout);
47
+ if (envelopeError) {
48
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, redactSensitiveOutput(envelopeError.message), envelopeError.code);
49
+ }
50
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, redactSensitiveOutput(result.stderr.trim() || `twitter-cli exited with code ${result.code}`));
51
+ }
52
+ try {
53
+ return JSON.parse(result.stdout);
54
+ }
55
+ catch {
56
+ throw new ExternalToolError(TWITTER_CLI_TOOL_NAME, `twitter-cli returned non-JSON output: ${redactSensitiveOutput(result.stdout.slice(0, 200))}`);
57
+ }
58
+ }
59
+ function parseCliErrorEnvelope(stdout) {
60
+ try {
61
+ const parsed = JSON.parse(stdout);
62
+ if (parsed.ok !== false || typeof parsed.error?.message !== "string")
63
+ return null;
64
+ return {
65
+ code: typeof parsed.error.code === "string" ? parsed.error.code : undefined,
66
+ message: parsed.error.message,
67
+ };
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function adaptRawTweet(raw) {
74
+ return {
75
+ id: stringValue(raw.id),
76
+ text: stringValue(raw.text).slice(0, 280),
77
+ author: stringValue(raw.author?.username) ||
78
+ stringValue(raw.author?.screenName) ||
79
+ stringValue(raw.username) ||
80
+ "unknown",
81
+ likes: numberValue(raw.metrics?.likes ?? raw.likeCount ?? raw.likes),
82
+ retweets: numberValue(raw.metrics?.retweets ?? raw.retweetCount ?? raw.retweets),
83
+ replies: numberValue(raw.metrics?.replies ?? raw.replyCount ?? raw.replies),
84
+ views: nullableNumberValue(raw.metrics?.views ?? raw.viewCount ?? raw.views),
85
+ url: stringValue(raw.url ?? raw.permanentUrl),
86
+ created: normalizeCreatedAt(raw.createdAt ?? raw.created_at),
87
+ };
88
+ }
89
+ function normalizeCreatedAt(value) {
90
+ if (typeof value === "number") {
91
+ const millis = value > 1_000_000_000_000 ? value : value * 1000;
92
+ return new Date(millis).toISOString();
93
+ }
94
+ if (typeof value === "string" && value.length > 0) {
95
+ const millis = Date.parse(value);
96
+ if (!Number.isNaN(millis))
97
+ return new Date(millis).toISOString();
98
+ }
99
+ return new Date(0).toISOString();
100
+ }
101
+ function stringValue(value) {
102
+ return typeof value === "string" ? value : "";
103
+ }
104
+ function numberValue(value) {
105
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
106
+ }
107
+ function nullableNumberValue(value) {
108
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
109
+ }
110
+ export function redactSensitiveOutput(input) {
111
+ const xCookieNames = "auth_token|ct0|twid|kdt|guest_id|guest_id_ads|guest_id_marketing|personalization_id";
112
+ return input
113
+ .slice(0, MAX_OUTPUT_CHARS)
114
+ .replace(/\b(cookie|set-cookie)\s*:\s*[^\r\n]+/gi, "$1: [redacted]")
115
+ .replace(new RegExp(`\\b(${xCookieNames})\\b\\s*[:=]\\s*[^;\\s,)]+`, "gi"), "$1=[redacted]")
116
+ .replace(new RegExp(`\\b(${xCookieNames})=([^;\\s,)]+)`, "gi"), "$1=[redacted]");
117
+ }
118
+ function runCommand(command, args) {
119
+ return runExternalToolCommand(command, args, {
120
+ timeoutMs: COMMAND_TIMEOUT_MS,
121
+ maxOutputChars: MAX_OUTPUT_CHARS,
122
+ });
123
+ }
124
+ //# sourceMappingURL=twitter-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twitter-cli.js","sourceRoot":"","sources":["../../src/providers/twitter-cli.ts"],"names":[],"mappings":"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"}