kalshi-trading-bot-cli 2.1.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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/assets/kalshi-flow-light.png +0 -0
  4. package/assets/screenshot.png +0 -0
  5. package/env.example +43 -0
  6. package/kalshi-flow-light.png +0 -0
  7. package/package.json +66 -0
  8. package/src/agent/agent.ts +249 -0
  9. package/src/agent/channels.ts +53 -0
  10. package/src/agent/index.ts +29 -0
  11. package/src/agent/prompts.ts +171 -0
  12. package/src/agent/run-context.ts +23 -0
  13. package/src/agent/scratchpad.ts +465 -0
  14. package/src/agent/token-counter.ts +33 -0
  15. package/src/agent/tool-executor.ts +166 -0
  16. package/src/agent/types.ts +221 -0
  17. package/src/audit/index.ts +25 -0
  18. package/src/audit/reader.ts +43 -0
  19. package/src/audit/trail.ts +29 -0
  20. package/src/audit/types.ts +133 -0
  21. package/src/backtest/discovery.ts +170 -0
  22. package/src/backtest/fetcher.ts +247 -0
  23. package/src/backtest/metrics.ts +165 -0
  24. package/src/backtest/renderer.ts +196 -0
  25. package/src/backtest/types.ts +45 -0
  26. package/src/cli.ts +943 -0
  27. package/src/commands/alerts.ts +48 -0
  28. package/src/commands/analyze.ts +662 -0
  29. package/src/commands/backtest.ts +276 -0
  30. package/src/commands/clear-cache.ts +24 -0
  31. package/src/commands/config.ts +107 -0
  32. package/src/commands/dispatch.ts +473 -0
  33. package/src/commands/edge.ts +62 -0
  34. package/src/commands/formatters.ts +339 -0
  35. package/src/commands/help.ts +263 -0
  36. package/src/commands/helpers.ts +48 -0
  37. package/src/commands/index.ts +287 -0
  38. package/src/commands/json.ts +43 -0
  39. package/src/commands/parse-args.ts +229 -0
  40. package/src/commands/portfolio.ts +236 -0
  41. package/src/commands/review.ts +176 -0
  42. package/src/commands/scan-formatters.ts +98 -0
  43. package/src/commands/scan.ts +38 -0
  44. package/src/commands/search-edge.ts +139 -0
  45. package/src/commands/status.ts +70 -0
  46. package/src/commands/themes.ts +117 -0
  47. package/src/commands/watch.ts +295 -0
  48. package/src/components/answer-box.ts +57 -0
  49. package/src/components/approval-prompt.ts +34 -0
  50. package/src/components/browse-list.ts +134 -0
  51. package/src/components/chat-log.ts +291 -0
  52. package/src/components/custom-editor.ts +18 -0
  53. package/src/components/debug-panel.ts +52 -0
  54. package/src/components/index.ts +17 -0
  55. package/src/components/intro.ts +92 -0
  56. package/src/components/select-list.ts +155 -0
  57. package/src/components/tool-event.ts +127 -0
  58. package/src/components/user-query.ts +18 -0
  59. package/src/components/working-indicator.ts +87 -0
  60. package/src/controllers/agent-runner.ts +283 -0
  61. package/src/controllers/browse.ts +1013 -0
  62. package/src/controllers/index.ts +7 -0
  63. package/src/controllers/input-history.ts +76 -0
  64. package/src/controllers/model-selection.ts +244 -0
  65. package/src/db/alerts.ts +77 -0
  66. package/src/db/edge.ts +105 -0
  67. package/src/db/event-index.ts +323 -0
  68. package/src/db/events.ts +41 -0
  69. package/src/db/index.ts +60 -0
  70. package/src/db/octagon-cache.ts +118 -0
  71. package/src/db/positions.ts +71 -0
  72. package/src/db/risk.ts +51 -0
  73. package/src/db/schema.ts +227 -0
  74. package/src/db/themes.ts +34 -0
  75. package/src/db/trades.ts +50 -0
  76. package/src/eval/brier.ts +90 -0
  77. package/src/eval/index.ts +4 -0
  78. package/src/eval/performance.ts +87 -0
  79. package/src/gateway/access-control.ts +253 -0
  80. package/src/gateway/agent-runner.ts +75 -0
  81. package/src/gateway/alerts/formatter.ts +90 -0
  82. package/src/gateway/alerts/index.ts +4 -0
  83. package/src/gateway/alerts/router.ts +32 -0
  84. package/src/gateway/alerts/terminal.ts +16 -0
  85. package/src/gateway/alerts/types.ts +13 -0
  86. package/src/gateway/channels/index.ts +9 -0
  87. package/src/gateway/channels/manager.ts +153 -0
  88. package/src/gateway/channels/types.ts +48 -0
  89. package/src/gateway/channels/whatsapp/README.md +234 -0
  90. package/src/gateway/channels/whatsapp/auth-store.ts +140 -0
  91. package/src/gateway/channels/whatsapp/dedupe.ts +60 -0
  92. package/src/gateway/channels/whatsapp/error.ts +122 -0
  93. package/src/gateway/channels/whatsapp/inbound.ts +326 -0
  94. package/src/gateway/channels/whatsapp/index.ts +5 -0
  95. package/src/gateway/channels/whatsapp/lid.ts +56 -0
  96. package/src/gateway/channels/whatsapp/logger.ts +25 -0
  97. package/src/gateway/channels/whatsapp/login.ts +94 -0
  98. package/src/gateway/channels/whatsapp/outbound.ts +119 -0
  99. package/src/gateway/channels/whatsapp/plugin.ts +54 -0
  100. package/src/gateway/channels/whatsapp/reconnect.ts +40 -0
  101. package/src/gateway/channels/whatsapp/runtime.ts +122 -0
  102. package/src/gateway/channels/whatsapp/session.ts +89 -0
  103. package/src/gateway/channels/whatsapp/types.ts +32 -0
  104. package/src/gateway/commands/handler.ts +64 -0
  105. package/src/gateway/commands/index.ts +7 -0
  106. package/src/gateway/commands/parser.ts +29 -0
  107. package/src/gateway/commands/wa-formatters.ts +92 -0
  108. package/src/gateway/config.ts +244 -0
  109. package/src/gateway/extension-points.ts +17 -0
  110. package/src/gateway/gateway.ts +301 -0
  111. package/src/gateway/group/history-buffer.ts +75 -0
  112. package/src/gateway/group/index.ts +8 -0
  113. package/src/gateway/group/member-tracker.ts +60 -0
  114. package/src/gateway/group/mention-detection.ts +42 -0
  115. package/src/gateway/heartbeat/index.ts +8 -0
  116. package/src/gateway/heartbeat/prompt.ts +73 -0
  117. package/src/gateway/heartbeat/runner.ts +200 -0
  118. package/src/gateway/heartbeat/suppression.ts +74 -0
  119. package/src/gateway/index.ts +138 -0
  120. package/src/gateway/routing/resolve-route.ts +119 -0
  121. package/src/gateway/sessions/store.ts +65 -0
  122. package/src/gateway/types.ts +11 -0
  123. package/src/gateway/utils.ts +82 -0
  124. package/src/index.tsx +30 -0
  125. package/src/model/llm.ts +247 -0
  126. package/src/providers.ts +94 -0
  127. package/src/risk/circuit-breaker.ts +113 -0
  128. package/src/risk/correlation.ts +40 -0
  129. package/src/risk/gate.ts +125 -0
  130. package/src/risk/index.ts +10 -0
  131. package/src/risk/kelly.ts +230 -0
  132. package/src/scan/alerter.ts +64 -0
  133. package/src/scan/edge-computer.ts +164 -0
  134. package/src/scan/invoker.ts +199 -0
  135. package/src/scan/loop.ts +184 -0
  136. package/src/scan/octagon-client.ts +627 -0
  137. package/src/scan/octagon-events-api.ts +105 -0
  138. package/src/scan/octagon-prefetch.ts +172 -0
  139. package/src/scan/theme-resolver.ts +179 -0
  140. package/src/scan/types.ts +62 -0
  141. package/src/scan/watchdog.ts +126 -0
  142. package/src/setup/wizard.ts +659 -0
  143. package/src/theme.ts +67 -0
  144. package/src/tools/fetch/cache.ts +95 -0
  145. package/src/tools/fetch/external-content.ts +200 -0
  146. package/src/tools/fetch/index.ts +1 -0
  147. package/src/tools/fetch/web-fetch-utils.ts +122 -0
  148. package/src/tools/fetch/web-fetch.ts +419 -0
  149. package/src/tools/index.ts +10 -0
  150. package/src/tools/kalshi/api.ts +251 -0
  151. package/src/tools/kalshi/dlq.ts +35 -0
  152. package/src/tools/kalshi/events.ts +84 -0
  153. package/src/tools/kalshi/exchange.ts +24 -0
  154. package/src/tools/kalshi/historical.ts +89 -0
  155. package/src/tools/kalshi/index.ts +11 -0
  156. package/src/tools/kalshi/kalshi-search.ts +437 -0
  157. package/src/tools/kalshi/kalshi-trade.ts +102 -0
  158. package/src/tools/kalshi/markets.ts +76 -0
  159. package/src/tools/kalshi/portfolio.ts +100 -0
  160. package/src/tools/kalshi/search-index.ts +198 -0
  161. package/src/tools/kalshi/series.ts +16 -0
  162. package/src/tools/kalshi/trading.ts +115 -0
  163. package/src/tools/kalshi/types.ts +199 -0
  164. package/src/tools/registry.ts +160 -0
  165. package/src/tools/search/index.ts +25 -0
  166. package/src/tools/search/tavily.ts +35 -0
  167. package/src/tools/types.ts +53 -0
  168. package/src/tools/v2/edge-query.ts +135 -0
  169. package/src/tools/v2/octagon-report.ts +112 -0
  170. package/src/tools/v2/portfolio-query.ts +79 -0
  171. package/src/tools/v2/portfolio-review.ts +59 -0
  172. package/src/tools/v2/risk-status.ts +94 -0
  173. package/src/tools/v2/scan.ts +78 -0
  174. package/src/types/qrcode-terminal.d.ts +7 -0
  175. package/src/types/whiskeysockets-baileys.d.ts +41 -0
  176. package/src/types.ts +22 -0
  177. package/src/utils/ai-message.ts +26 -0
  178. package/src/utils/bot-config.ts +219 -0
  179. package/src/utils/cache.ts +195 -0
  180. package/src/utils/config.ts +113 -0
  181. package/src/utils/env.ts +111 -0
  182. package/src/utils/errors.ts +313 -0
  183. package/src/utils/history-context.ts +32 -0
  184. package/src/utils/in-memory-chat-history.ts +268 -0
  185. package/src/utils/index.ts +28 -0
  186. package/src/utils/input-key-handlers.ts +64 -0
  187. package/src/utils/logger.ts +67 -0
  188. package/src/utils/long-term-chat-history.ts +138 -0
  189. package/src/utils/markdown-table.ts +227 -0
  190. package/src/utils/model.ts +70 -0
  191. package/src/utils/ollama.ts +37 -0
  192. package/src/utils/paths.ts +12 -0
  193. package/src/utils/progress-channel.ts +84 -0
  194. package/src/utils/telemetry.ts +103 -0
  195. package/src/utils/text-navigation.ts +81 -0
  196. package/src/utils/thinking-verbs.ts +18 -0
  197. package/src/utils/tokens.ts +36 -0
  198. package/src/utils/tool-description.ts +61 -0
@@ -0,0 +1,103 @@
1
+ import { loadConfig, saveConfig } from './config.js';
2
+
3
+ const CLIENT_KEY = 'client-TbH2tfgwg2CYu87Y932Wj2CdNTfyy7303HFszn0YZny';
4
+ const LOG_EVENT_URL = 'https://events.statsigapi.net/v1/log_event';
5
+
6
+ let userId: string | null = null;
7
+ let disabled = false;
8
+ let eventQueue: StatsigEvent[] = [];
9
+
10
+ interface StatsigEvent {
11
+ eventName: string;
12
+ value?: string;
13
+ metadata?: Record<string, string | number | boolean>;
14
+ time: number;
15
+ user: { userID: string };
16
+ }
17
+
18
+ function isEnabled(): boolean {
19
+ const val = process.env.TELEMETRY_ENABLED;
20
+ if (val === 'false' || val === '0') return false;
21
+ return true;
22
+ }
23
+
24
+ function getOrCreateAnonymousId(): string {
25
+ const config = loadConfig();
26
+ if (config.anonymousId) return config.anonymousId;
27
+ const id = crypto.randomUUID();
28
+ saveConfig({ ...config, anonymousId: id });
29
+ return id;
30
+ }
31
+
32
+ export async function initTelemetry(): Promise<void> {
33
+ if (disabled || userId) return;
34
+ if (!isEnabled()) {
35
+ disabled = true;
36
+ return;
37
+ }
38
+
39
+ try {
40
+ userId = getOrCreateAnonymousId();
41
+
42
+ // Intercept process.exit to flush telemetry before exiting.
43
+ // dispatch.ts calls process.exit() in many code paths, which would
44
+ // kill the process before events are sent.
45
+ const originalExit = process.exit;
46
+ process.exit = (async (code?: number) => {
47
+ await shutdownTelemetry();
48
+ originalExit(code as any);
49
+ }) as never;
50
+ } catch {
51
+ disabled = true;
52
+ }
53
+ }
54
+
55
+ export function trackEvent(
56
+ name: string,
57
+ metadata?: Record<string, string | number | boolean>,
58
+ ): void {
59
+ if (!userId || disabled) return;
60
+ try {
61
+ eventQueue.push({
62
+ eventName: name,
63
+ metadata,
64
+ time: Date.now(),
65
+ user: { userID: userId },
66
+ });
67
+ } catch {}
68
+ }
69
+
70
+ async function flushEvents(): Promise<void> {
71
+ if (eventQueue.length === 0 || !userId) return;
72
+ const events = eventQueue;
73
+ eventQueue = [];
74
+ try {
75
+ await fetch(LOG_EVENT_URL, {
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json',
79
+ 'statsig-api-key': CLIENT_KEY,
80
+ 'statsig-sdk-type': 'js-mono',
81
+ 'statsig-sdk-version': '1.0.0',
82
+ },
83
+ body: JSON.stringify({
84
+ events,
85
+ statsigMetadata: {
86
+ sdkType: 'js-mono',
87
+ sdkVersion: '1.0.0',
88
+ },
89
+ }),
90
+ });
91
+ } catch {}
92
+ }
93
+
94
+ export async function shutdownTelemetry(): Promise<void> {
95
+ if (disabled) return;
96
+ try {
97
+ await Promise.race([
98
+ flushEvents(),
99
+ new Promise((resolve) => setTimeout(resolve, 3000)),
100
+ ]);
101
+ } catch {}
102
+ userId = null;
103
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Find the start position of the previous word from cursor position.
3
+ * Used for Option+Left (Mac) / Ctrl+Left (Windows) navigation.
4
+ */
5
+ export function findPrevWordStart(text: string, pos: number): number {
6
+ if (pos <= 0) return 0;
7
+ let i = pos - 1;
8
+ // Skip non-word chars
9
+ while (i > 0 && !/\w/.test(text[i])) i--;
10
+ // Move to word start
11
+ while (i > 0 && /\w/.test(text[i - 1])) i--;
12
+ return i;
13
+ }
14
+
15
+ /**
16
+ * Find the end position of the next word from cursor position.
17
+ * Used for Option+Right (Mac) / Ctrl+Right (Windows) navigation.
18
+ */
19
+ export function findNextWordEnd(text: string, pos: number): number {
20
+ const len = text.length;
21
+ if (pos >= len) return len;
22
+ let i = pos;
23
+ // Skip non-word chars
24
+ while (i < len && !/\w/.test(text[i])) i++;
25
+ // Move to word end
26
+ while (i < len && /\w/.test(text[i])) i++;
27
+ return i;
28
+ }
29
+
30
+ // ============================================================================
31
+ // Multi-line cursor navigation utilities
32
+ // ============================================================================
33
+
34
+ /**
35
+ * Get the line number (0-indexed) and column from a cursor position.
36
+ */
37
+ export function getLineAndColumn(text: string, pos: number): { line: number; column: number } {
38
+ const beforeCursor = text.slice(0, pos);
39
+ const lines = beforeCursor.split('\n');
40
+ return {
41
+ line: lines.length - 1,
42
+ column: lines[lines.length - 1].length,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Get cursor position from line number and column.
48
+ * Clamps column to the actual line length if it exceeds.
49
+ */
50
+ export function getCursorPosition(text: string, line: number, column: number): number {
51
+ const lines = text.split('\n');
52
+ let pos = 0;
53
+ for (let i = 0; i < line && i < lines.length; i++) {
54
+ pos += lines[i].length + 1; // +1 for newline
55
+ }
56
+ const targetLine = lines[line] || '';
57
+ return pos + Math.min(column, targetLine.length);
58
+ }
59
+
60
+ /**
61
+ * Get the start position of the line containing the cursor.
62
+ */
63
+ export function getLineStart(text: string, pos: number): number {
64
+ const lastNewline = text.lastIndexOf('\n', pos - 1);
65
+ return lastNewline + 1; // -1 + 1 = 0 if no newline found
66
+ }
67
+
68
+ /**
69
+ * Get the end position of the line containing the cursor (before the newline).
70
+ */
71
+ export function getLineEnd(text: string, pos: number): number {
72
+ const nextNewline = text.indexOf('\n', pos);
73
+ return nextNewline === -1 ? text.length : nextNewline;
74
+ }
75
+
76
+ /**
77
+ * Get the total number of lines in the text.
78
+ */
79
+ export function getLineCount(text: string): number {
80
+ return text.split('\n').length;
81
+ }
@@ -0,0 +1,18 @@
1
+ export const THINKING_VERBS = [
2
+ 'Analyzing', 'Assessing', 'Brainstorming', 'Brewing',
3
+ 'Calculating', 'Calibrating', 'Catalyzing', 'Cerebrating',
4
+ 'Churning', 'Cogitating', 'Constructing', 'Crafting',
5
+ 'Crunching', 'Deliberating', 'Distilling', 'Drafting',
6
+ 'Engineering', 'Evaluating', 'Experimenting', 'Finessing',
7
+ 'Formulating', 'Forging', 'Hatching', 'Hypothesizing',
8
+ 'Ideating', 'Inventing', 'Marinating', 'Modeling',
9
+ 'Mulling', 'Musing', 'Observing', 'Percolating',
10
+ 'Pondering', 'Processing', 'Puzzling', 'Reviewing',
11
+ 'Riffing', 'Ruminating', 'Sculpting', 'Simmering',
12
+ 'Sketching', 'Synthesizing', 'Tinkering', 'Triangulating',
13
+ 'Verifying', 'Whittling', 'Wrangling',
14
+ ] as const;
15
+
16
+ export function getRandomThinkingVerb(): string {
17
+ return THINKING_VERBS[Math.floor(Math.random() * THINKING_VERBS.length)];
18
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Token estimation utilities for context management.
3
+ * Used to prevent exceeding LLM context window limits.
4
+ */
5
+
6
+ /**
7
+ * Rough token estimation based on character count.
8
+ * JSON is denser than prose, so we use ~3.5 chars per token.
9
+ * This is conservative - better to underestimate available space.
10
+ */
11
+ export function estimateTokens(text: string): number {
12
+ return Math.ceil(text.length / 3.5);
13
+ }
14
+
15
+ /**
16
+ * Maximum token budget for context data in final answer generation.
17
+ * Conservative limit that leaves room for system prompt, query, and response.
18
+ */
19
+ export const TOKEN_BUDGET = 150_000;
20
+
21
+ // ============================================================================
22
+ // Anthropic-style Context Management Constants
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Token threshold at which context clearing is triggered.
27
+ * Matches Anthropic's default of 100k tokens.
28
+ * When estimated context exceeds this, oldest tool results are cleared.
29
+ */
30
+ export const CONTEXT_THRESHOLD = 100_000;
31
+
32
+ /**
33
+ * Number of most recent tool results to keep when clearing.
34
+ * Anthropic's default is 3, but we use 5 for slightly more context.
35
+ */
36
+ export const KEEP_TOOL_USES = 5;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Generate a deterministic human-readable description of a tool call.
3
+ * Used for context compaction during the agent loop.
4
+ *
5
+ * Examples:
6
+ * - "AAPL income statements (annual) - 5 periods"
7
+ * - '"bitcoin price" tavily search'
8
+ */
9
+ export function getToolDescription(toolName: string, args: Record<string, unknown>): string {
10
+ const parts: string[] = [];
11
+ const usedKeys = new Set<string>();
12
+
13
+ // Add ticker if present (most common identifier)
14
+ if (args.ticker) {
15
+ parts.push(String(args.ticker).toUpperCase());
16
+ usedKeys.add('ticker');
17
+ }
18
+
19
+ // Add search query if present
20
+ if (args.query) {
21
+ parts.push(`"${args.query}"`);
22
+ usedKeys.add('query');
23
+ }
24
+
25
+ // Format tool name: get_income_statements -> income statements
26
+ const formattedToolName = toolName
27
+ .replace(/^get_/, '')
28
+ .replace(/^search_/, '')
29
+ .replace(/_/g, ' ');
30
+ parts.push(formattedToolName);
31
+
32
+ // Add period qualifier if present
33
+ if (args.period) {
34
+ parts.push(`(${args.period})`);
35
+ usedKeys.add('period');
36
+ }
37
+
38
+ // Add limit if present
39
+ if (args.limit && typeof args.limit === 'number') {
40
+ parts.push(`- ${args.limit} periods`);
41
+ usedKeys.add('limit');
42
+ }
43
+
44
+ // Add date range if present
45
+ if (args.start_date && args.end_date) {
46
+ parts.push(`from ${args.start_date} to ${args.end_date}`);
47
+ usedKeys.add('start_date');
48
+ usedKeys.add('end_date');
49
+ }
50
+
51
+ // Append any remaining args not explicitly handled
52
+ const remainingArgs = Object.entries(args)
53
+ .filter(([key]) => !usedKeys.has(key))
54
+ .map(([key, value]) => `${key}=${value}`);
55
+
56
+ if (remainingArgs.length > 0) {
57
+ parts.push(`[${remainingArgs.join(', ')}]`);
58
+ }
59
+
60
+ return parts.join(' ');
61
+ }