@spfunctions/cli 1.4.4 → 1.5.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 (86) hide show
  1. package/README.md +205 -48
  2. package/dist/cache.d.ts +6 -0
  3. package/dist/cache.js +31 -0
  4. package/dist/cache.test.d.ts +1 -0
  5. package/dist/cache.test.js +73 -0
  6. package/dist/client.test.d.ts +1 -0
  7. package/dist/client.test.js +89 -0
  8. package/dist/commands/agent.js +594 -106
  9. package/dist/commands/book.d.ts +17 -0
  10. package/dist/commands/book.js +220 -0
  11. package/dist/commands/dashboard.d.ts +6 -3
  12. package/dist/commands/dashboard.js +53 -22
  13. package/dist/commands/liquidity.d.ts +2 -0
  14. package/dist/commands/liquidity.js +128 -43
  15. package/dist/commands/performance.js +9 -2
  16. package/dist/commands/positions.js +50 -0
  17. package/dist/commands/scan.d.ts +1 -0
  18. package/dist/commands/scan.js +66 -15
  19. package/dist/commands/setup.d.ts +1 -0
  20. package/dist/commands/setup.js +71 -6
  21. package/dist/commands/telegram.d.ts +15 -0
  22. package/dist/commands/telegram.js +125 -0
  23. package/dist/config.d.ts +3 -0
  24. package/dist/config.js +9 -0
  25. package/dist/config.test.d.ts +1 -0
  26. package/dist/config.test.js +138 -0
  27. package/dist/index.js +107 -9
  28. package/dist/polymarket.d.ts +237 -0
  29. package/dist/polymarket.js +353 -0
  30. package/dist/polymarket.test.d.ts +1 -0
  31. package/dist/polymarket.test.js +424 -0
  32. package/dist/telegram/agent-bridge.d.ts +15 -0
  33. package/dist/telegram/agent-bridge.js +368 -0
  34. package/dist/telegram/bot.d.ts +10 -0
  35. package/dist/telegram/bot.js +297 -0
  36. package/dist/telegram/commands.d.ts +11 -0
  37. package/dist/telegram/commands.js +120 -0
  38. package/dist/telegram/format.d.ts +11 -0
  39. package/dist/telegram/format.js +51 -0
  40. package/dist/telegram/format.test.d.ts +1 -0
  41. package/dist/telegram/format.test.js +73 -0
  42. package/dist/telegram/poller.d.ts +6 -0
  43. package/dist/telegram/poller.js +32 -0
  44. package/dist/topics.d.ts +3 -0
  45. package/dist/topics.js +65 -7
  46. package/dist/topics.test.d.ts +1 -0
  47. package/dist/topics.test.js +131 -0
  48. package/dist/tui/border.d.ts +33 -0
  49. package/dist/tui/border.js +87 -0
  50. package/dist/tui/chart.d.ts +19 -0
  51. package/dist/tui/chart.js +117 -0
  52. package/dist/tui/dashboard.d.ts +9 -0
  53. package/dist/tui/dashboard.js +814 -0
  54. package/dist/tui/layout.d.ts +16 -0
  55. package/dist/tui/layout.js +41 -0
  56. package/dist/tui/screen.d.ts +33 -0
  57. package/dist/tui/screen.js +102 -0
  58. package/dist/tui/state.d.ts +40 -0
  59. package/dist/tui/state.js +36 -0
  60. package/dist/tui/widgets/commandbar.d.ts +8 -0
  61. package/dist/tui/widgets/commandbar.js +82 -0
  62. package/dist/tui/widgets/detail.d.ts +9 -0
  63. package/dist/tui/widgets/detail.js +151 -0
  64. package/dist/tui/widgets/edges.d.ts +4 -0
  65. package/dist/tui/widgets/edges.js +34 -0
  66. package/dist/tui/widgets/liquidity.d.ts +9 -0
  67. package/dist/tui/widgets/liquidity.js +142 -0
  68. package/dist/tui/widgets/orders.d.ts +4 -0
  69. package/dist/tui/widgets/orders.js +37 -0
  70. package/dist/tui/widgets/portfolio.d.ts +4 -0
  71. package/dist/tui/widgets/portfolio.js +59 -0
  72. package/dist/tui/widgets/signals.d.ts +4 -0
  73. package/dist/tui/widgets/signals.js +31 -0
  74. package/dist/tui/widgets/statusbar.d.ts +8 -0
  75. package/dist/tui/widgets/statusbar.js +72 -0
  76. package/dist/tui/widgets/thesis.d.ts +4 -0
  77. package/dist/tui/widgets/thesis.js +66 -0
  78. package/dist/tui/widgets/trade.d.ts +9 -0
  79. package/dist/tui/widgets/trade.js +117 -0
  80. package/dist/tui/widgets/upcoming.d.ts +4 -0
  81. package/dist/tui/widgets/upcoming.js +41 -0
  82. package/dist/tui/widgets/whatif.d.ts +7 -0
  83. package/dist/tui/widgets/whatif.js +113 -0
  84. package/dist/utils.test.d.ts +1 -0
  85. package/dist/utils.test.js +111 -0
  86. package/package.json +6 -2
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * sf telegram — Start Telegram bot
4
+ *
5
+ * Token is saved to ~/.sf/config.json on first use.
6
+ * --daemon: fork to background, write PID to ~/.sf/telegram.pid
7
+ * --stop: kill running daemon
8
+ * --status: check if daemon is running
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.telegramCommand = telegramCommand;
15
+ const child_process_1 = require("child_process");
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const os_1 = __importDefault(require("os"));
19
+ const config_js_1 = require("../config.js");
20
+ const PID_FILE = path_1.default.join(os_1.default.homedir(), '.sf', 'telegram.pid');
21
+ const LOG_FILE = path_1.default.join(os_1.default.homedir(), '.sf', 'telegram.log');
22
+ function readPid() {
23
+ try {
24
+ const pid = parseInt(fs_1.default.readFileSync(PID_FILE, 'utf-8').trim());
25
+ if (isNaN(pid))
26
+ return null;
27
+ try {
28
+ process.kill(pid, 0);
29
+ return pid;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /** Resolve token: --token flag > config > env var. Save to config if new. */
40
+ function resolveToken(flagToken) {
41
+ const config = (0, config_js_1.loadConfig)();
42
+ // Flag takes priority
43
+ if (flagToken) {
44
+ // Save to config for future use
45
+ const file = (0, config_js_1.loadFileConfig)();
46
+ if (file.telegramBotToken !== flagToken) {
47
+ (0, config_js_1.saveConfig)({ ...file, telegramBotToken: flagToken });
48
+ }
49
+ return flagToken;
50
+ }
51
+ return config.telegramBotToken || null;
52
+ }
53
+ async function telegramCommand(opts) {
54
+ // ── sf telegram --stop ──
55
+ if (opts.stop) {
56
+ const pid = readPid();
57
+ if (pid) {
58
+ process.kill(pid, 'SIGTERM');
59
+ try {
60
+ fs_1.default.unlinkSync(PID_FILE);
61
+ }
62
+ catch { }
63
+ console.log(` Telegram bot stopped (PID ${pid})`);
64
+ }
65
+ else {
66
+ console.log(' No running Telegram bot found.');
67
+ }
68
+ return;
69
+ }
70
+ // ── sf telegram --status ──
71
+ if (opts.status) {
72
+ const pid = readPid();
73
+ if (pid) {
74
+ console.log(` Telegram bot running (PID ${pid})`);
75
+ console.log(` Log: ${LOG_FILE}`);
76
+ }
77
+ else {
78
+ console.log(' Telegram bot not running.');
79
+ }
80
+ return;
81
+ }
82
+ // Resolve token (saves to config on first use)
83
+ const token = resolveToken(opts.token);
84
+ if (!token) {
85
+ console.log(' No Telegram bot token configured.\n');
86
+ console.log(' Setup:');
87
+ console.log(' 1. Message @BotFather on Telegram → /newbot');
88
+ console.log(' 2. Copy the token');
89
+ console.log(' 3. Run: sf telegram --token YOUR_TOKEN\n');
90
+ console.log(' The token will be saved to ~/.sf/config.json for future use.');
91
+ return;
92
+ }
93
+ // ── sf telegram --daemon ──
94
+ if (opts.daemon) {
95
+ const existing = readPid();
96
+ if (existing) {
97
+ console.log(` Bot already running (PID ${existing}). Use --stop first.`);
98
+ return;
99
+ }
100
+ // Daemon doesn't need --token since it's in config now
101
+ const args = ['telegram'];
102
+ if (opts.chatId)
103
+ args.push('--chat-id', opts.chatId);
104
+ const sfBin = process.argv[1];
105
+ fs_1.default.mkdirSync(path_1.default.dirname(PID_FILE), { recursive: true });
106
+ const logStream = fs_1.default.openSync(LOG_FILE, 'a');
107
+ const child = (0, child_process_1.spawn)(process.execPath, [sfBin, ...args], {
108
+ detached: true,
109
+ stdio: ['ignore', logStream, logStream],
110
+ env: { ...process.env },
111
+ });
112
+ child.unref();
113
+ fs_1.default.writeFileSync(PID_FILE, String(child.pid));
114
+ console.log(` Telegram bot started in background (PID ${child.pid})`);
115
+ console.log(` Log: ${LOG_FILE}`);
116
+ console.log(` Stop: sf telegram --stop`);
117
+ return;
118
+ }
119
+ // ── sf telegram (foreground) ──
120
+ const { startBot } = await import('../telegram/bot.js');
121
+ await startBot({
122
+ token,
123
+ chatId: opts.chatId ? parseInt(opts.chatId) : undefined,
124
+ });
125
+ }
package/dist/config.d.ts CHANGED
@@ -13,9 +13,12 @@ export interface SFConfig {
13
13
  openrouterKey?: string;
14
14
  kalshiKeyId?: string;
15
15
  kalshiPrivateKeyPath?: string;
16
+ polymarketWalletAddress?: string;
17
+ polymarketPrivateKeyPath?: string;
16
18
  tavilyKey?: string;
17
19
  model?: string;
18
20
  tradingEnabled?: boolean;
21
+ telegramBotToken?: string;
19
22
  configuredAt?: string;
20
23
  }
21
24
  /**
package/dist/config.js CHANGED
@@ -50,9 +50,12 @@ function loadConfig() {
50
50
  openrouterKey: process.env.OPENROUTER_API_KEY || file.openrouterKey,
51
51
  kalshiKeyId: process.env.KALSHI_API_KEY_ID || file.kalshiKeyId,
52
52
  kalshiPrivateKeyPath: process.env.KALSHI_PRIVATE_KEY_PATH || file.kalshiPrivateKeyPath,
53
+ polymarketWalletAddress: process.env.POLYMARKET_WALLET_ADDRESS || file.polymarketWalletAddress,
54
+ polymarketPrivateKeyPath: process.env.POLYMARKET_PRIVATE_KEY_PATH || file.polymarketPrivateKeyPath,
53
55
  tavilyKey: process.env.TAVILY_API_KEY || file.tavilyKey,
54
56
  model: process.env.SF_MODEL || file.model || DEFAULT_MODEL,
55
57
  tradingEnabled: file.tradingEnabled || false,
58
+ telegramBotToken: process.env.TELEGRAM_BOT_TOKEN || file.telegramBotToken,
56
59
  };
57
60
  }
58
61
  /**
@@ -100,6 +103,12 @@ function applyConfig() {
100
103
  if (!process.env.KALSHI_PRIVATE_KEY_PATH && file.kalshiPrivateKeyPath) {
101
104
  process.env.KALSHI_PRIVATE_KEY_PATH = file.kalshiPrivateKeyPath;
102
105
  }
106
+ if (!process.env.POLYMARKET_WALLET_ADDRESS && file.polymarketWalletAddress) {
107
+ process.env.POLYMARKET_WALLET_ADDRESS = file.polymarketWalletAddress;
108
+ }
109
+ if (!process.env.POLYMARKET_PRIVATE_KEY_PATH && file.polymarketPrivateKeyPath) {
110
+ process.env.POLYMARKET_PRIVATE_KEY_PATH = file.polymarketPrivateKeyPath;
111
+ }
103
112
  if (!process.env.TAVILY_API_KEY && file.tavilyKey) {
104
113
  process.env.TAVILY_API_KEY = file.tavilyKey;
105
114
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ vitest_1.vi.mock('fs', () => ({
8
+ default: {
9
+ existsSync: vitest_1.vi.fn(),
10
+ readFileSync: vitest_1.vi.fn(),
11
+ writeFileSync: vitest_1.vi.fn(),
12
+ mkdirSync: vitest_1.vi.fn(),
13
+ unlinkSync: vitest_1.vi.fn(),
14
+ },
15
+ existsSync: vitest_1.vi.fn(),
16
+ readFileSync: vitest_1.vi.fn(),
17
+ writeFileSync: vitest_1.vi.fn(),
18
+ mkdirSync: vitest_1.vi.fn(),
19
+ unlinkSync: vitest_1.vi.fn(),
20
+ }));
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const config_js_1 = require("./config.js");
23
+ (0, vitest_1.beforeEach)(() => {
24
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReset();
25
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReset();
26
+ vitest_1.vi.mocked(fs_1.default.writeFileSync).mockReset();
27
+ vitest_1.vi.mocked(fs_1.default.mkdirSync).mockReset();
28
+ vitest_1.vi.mocked(fs_1.default.unlinkSync).mockReset();
29
+ // Clear env vars
30
+ delete process.env.SF_API_KEY;
31
+ delete process.env.SF_API_URL;
32
+ delete process.env.OPENROUTER_API_KEY;
33
+ delete process.env.KALSHI_API_KEY_ID;
34
+ delete process.env.KALSHI_PRIVATE_KEY_PATH;
35
+ delete process.env.TAVILY_API_KEY;
36
+ delete process.env.SF_MODEL;
37
+ delete process.env.TELEGRAM_BOT_TOKEN;
38
+ });
39
+ (0, vitest_1.describe)('loadFileConfig', () => {
40
+ (0, vitest_1.it)('returns empty object when no config file', () => {
41
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(false);
42
+ (0, vitest_1.expect)((0, config_js_1.loadFileConfig)()).toEqual({});
43
+ });
44
+ (0, vitest_1.it)('reads and parses config file', () => {
45
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
46
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue(JSON.stringify({ apiKey: 'sf_live_test' }));
47
+ (0, vitest_1.expect)((0, config_js_1.loadFileConfig)()).toEqual({ apiKey: 'sf_live_test' });
48
+ });
49
+ (0, vitest_1.it)('returns empty object on corrupt file', () => {
50
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
51
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue('not json');
52
+ (0, vitest_1.expect)((0, config_js_1.loadFileConfig)()).toEqual({});
53
+ });
54
+ });
55
+ (0, vitest_1.describe)('loadConfig', () => {
56
+ (0, vitest_1.it)('returns defaults when no file and no env', () => {
57
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(false);
58
+ const config = (0, config_js_1.loadConfig)();
59
+ (0, vitest_1.expect)(config.apiUrl).toBe('https://simplefunctions.dev');
60
+ (0, vitest_1.expect)(config.model).toBe('anthropic/claude-sonnet-4.6');
61
+ (0, vitest_1.expect)(config.tradingEnabled).toBe(false);
62
+ });
63
+ (0, vitest_1.it)('env vars override file values', () => {
64
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
65
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue(JSON.stringify({
66
+ apiKey: 'file_key',
67
+ model: 'file_model',
68
+ }));
69
+ process.env.SF_API_KEY = 'env_key';
70
+ const config = (0, config_js_1.loadConfig)();
71
+ (0, vitest_1.expect)(config.apiKey).toBe('env_key');
72
+ (0, vitest_1.expect)(config.model).toBe('file_model');
73
+ });
74
+ (0, vitest_1.it)('file values fill gaps', () => {
75
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
76
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue(JSON.stringify({
77
+ apiKey: 'file_key',
78
+ tavilyKey: 'tavily123',
79
+ }));
80
+ const config = (0, config_js_1.loadConfig)();
81
+ (0, vitest_1.expect)(config.apiKey).toBe('file_key');
82
+ (0, vitest_1.expect)(config.tavilyKey).toBe('tavily123');
83
+ });
84
+ });
85
+ (0, vitest_1.describe)('saveConfig', () => {
86
+ (0, vitest_1.it)('writes JSON with configuredAt', () => {
87
+ (0, config_js_1.saveConfig)({ apiKey: 'test_key' });
88
+ (0, vitest_1.expect)(vitest_1.vi.mocked(fs_1.default.mkdirSync)).toHaveBeenCalled();
89
+ (0, vitest_1.expect)(vitest_1.vi.mocked(fs_1.default.writeFileSync)).toHaveBeenCalled();
90
+ const written = JSON.parse(vitest_1.vi.mocked(fs_1.default.writeFileSync).mock.calls[0][1]);
91
+ (0, vitest_1.expect)(written.apiKey).toBe('test_key');
92
+ (0, vitest_1.expect)(written.configuredAt).toBeDefined();
93
+ });
94
+ });
95
+ (0, vitest_1.describe)('applyConfig', () => {
96
+ (0, vitest_1.it)('sets env vars from file when not already set', () => {
97
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
98
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue(JSON.stringify({
99
+ apiKey: 'file_key',
100
+ openrouterKey: 'or_key',
101
+ }));
102
+ (0, config_js_1.applyConfig)();
103
+ (0, vitest_1.expect)(process.env.SF_API_KEY).toBe('file_key');
104
+ (0, vitest_1.expect)(process.env.OPENROUTER_API_KEY).toBe('or_key');
105
+ });
106
+ (0, vitest_1.it)('does not override existing env vars', () => {
107
+ process.env.SF_API_KEY = 'existing_key';
108
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
109
+ vitest_1.vi.mocked(fs_1.default.readFileSync).mockReturnValue(JSON.stringify({
110
+ apiKey: 'file_key',
111
+ }));
112
+ (0, config_js_1.applyConfig)();
113
+ (0, vitest_1.expect)(process.env.SF_API_KEY).toBe('existing_key');
114
+ });
115
+ });
116
+ (0, vitest_1.describe)('isConfigured', () => {
117
+ (0, vitest_1.it)('returns true when apiKey is set', () => {
118
+ process.env.SF_API_KEY = 'test';
119
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(false);
120
+ (0, vitest_1.expect)((0, config_js_1.isConfigured)()).toBe(true);
121
+ });
122
+ (0, vitest_1.it)('returns false when no apiKey', () => {
123
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(false);
124
+ (0, vitest_1.expect)((0, config_js_1.isConfigured)()).toBe(false);
125
+ });
126
+ });
127
+ (0, vitest_1.describe)('resetConfig', () => {
128
+ (0, vitest_1.it)('deletes config file if exists', () => {
129
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(true);
130
+ (0, config_js_1.resetConfig)();
131
+ (0, vitest_1.expect)(vitest_1.vi.mocked(fs_1.default.unlinkSync)).toHaveBeenCalled();
132
+ });
133
+ (0, vitest_1.it)('does nothing if no config file', () => {
134
+ vitest_1.vi.mocked(fs_1.default.existsSync).mockReturnValue(false);
135
+ (0, config_js_1.resetConfig)();
136
+ (0, vitest_1.expect)(vitest_1.vi.mocked(fs_1.default.unlinkSync)).not.toHaveBeenCalled();
137
+ });
138
+ });
package/dist/index.js CHANGED
@@ -52,17 +52,84 @@ const announcements_js_1 = require("./commands/announcements.js");
52
52
  const history_js_1 = require("./commands/history.js");
53
53
  const performance_js_1 = require("./commands/performance.js");
54
54
  const liquidity_js_1 = require("./commands/liquidity.js");
55
+ const book_js_1 = require("./commands/book.js");
56
+ const telegram_js_1 = require("./commands/telegram.js");
55
57
  const utils_js_1 = require("./utils.js");
56
58
  // ── Apply ~/.sf/config.json to process.env BEFORE any command ────────────────
57
59
  // This means client.ts, kalshi.ts, agent.ts keep reading process.env and just work.
58
60
  (0, config_js_1.applyConfig)();
59
61
  const program = new commander_1.Command();
62
+ const GROUPED_HELP = `
63
+ \x1b[1mSimpleFunctions CLI\x1b[22m — prediction market thesis agent
64
+
65
+ \x1b[1mUsage:\x1b[22m sf <command> [options]
66
+ sf <command> --help for detailed options
67
+
68
+ \x1b[1mSetup\x1b[22m
69
+ \x1b[36msetup\x1b[39m Interactive config wizard
70
+ \x1b[36msetup --check\x1b[39m Show config status
71
+ \x1b[36msetup --polymarket\x1b[39m Configure Polymarket wallet
72
+
73
+ \x1b[1mThesis\x1b[22m
74
+ \x1b[36mlist\x1b[39m List all theses
75
+ \x1b[36mget\x1b[39m <id> Full thesis details
76
+ \x1b[36mcontext\x1b[39m <id> [--json] Thesis snapshot \x1b[2m(primary for agents)\x1b[22m
77
+ \x1b[36mcreate\x1b[39m "thesis" Create a new thesis
78
+ \x1b[36msignal\x1b[39m <id> "content" Inject a signal
79
+ \x1b[36mevaluate\x1b[39m <id> Trigger deep evaluation
80
+ \x1b[36mpublish\x1b[39m / \x1b[36munpublish\x1b[39m <id> Manage public visibility
81
+
82
+ \x1b[1mMarkets\x1b[22m
83
+ \x1b[36mscan\x1b[39m "keywords" Search Kalshi + Polymarket
84
+ \x1b[36mscan\x1b[39m --series TICKER Browse a Kalshi series
85
+ \x1b[36medges\x1b[39m [--json] Top edges across all theses
86
+ \x1b[36mwhatif\x1b[39m <id> What-if scenario analysis
87
+ \x1b[36mliquidity\x1b[39m [topic] Orderbook liquidity scanner
88
+ \x1b[36mbook\x1b[39m <ticker> [ticker2...] Orderbook depth for specific markets
89
+ \x1b[36mexplore\x1b[39m [slug] Browse public theses
90
+ \x1b[36mforecast\x1b[39m <event> Market distribution (P50/P75/P90)
91
+
92
+ \x1b[1mPortfolio\x1b[22m
93
+ \x1b[36mpositions\x1b[39m Kalshi + Polymarket positions
94
+ \x1b[36mbalance\x1b[39m Account balance
95
+ \x1b[36morders\x1b[39m Resting orders
96
+ \x1b[36mfills\x1b[39m Recent trade fills
97
+ \x1b[36msettlements\x1b[39m Settled contracts with P&L
98
+ \x1b[36mperformance\x1b[39m P&L over time
99
+ \x1b[36mdashboard\x1b[39m Interactive TUI overview
100
+
101
+ \x1b[1mTrading\x1b[22m \x1b[2m(requires sf setup --enable-trading)\x1b[22m
102
+ \x1b[36mbuy\x1b[39m <ticker> <qty> Buy contracts
103
+ \x1b[36msell\x1b[39m <ticker> <qty> Sell contracts
104
+ \x1b[36mcancel\x1b[39m [orderId] Cancel order(s)
105
+ \x1b[36mrfq\x1b[39m <ticker> <qty> Request for quote
106
+
107
+ \x1b[1mInteractive\x1b[22m
108
+ \x1b[36magent\x1b[39m [id] Agent with natural language + tools
109
+ \x1b[36mtelegram\x1b[39m Telegram bot for monitoring
110
+
111
+ \x1b[1mInfo\x1b[22m
112
+ \x1b[36mfeed\x1b[39m Evaluation history stream
113
+ \x1b[36mmilestones\x1b[39m Upcoming Kalshi events
114
+ \x1b[36mschedule\x1b[39m Exchange status
115
+ \x1b[36mannouncements\x1b[39m Exchange announcements
116
+ \x1b[36mhistory\x1b[39m <ticker> Historical market data
117
+ `;
60
118
  program
61
119
  .name('sf')
62
120
  .description('SimpleFunctions CLI — prediction market thesis agent')
63
121
  .version('0.1.0')
64
122
  .option('--api-key <key>', 'API key (or set SF_API_KEY env var)')
65
- .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)');
123
+ .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)')
124
+ .configureHelp({
125
+ formatHelp: (cmd, helper) => {
126
+ // For subcommands, use default help
127
+ if (cmd.parent)
128
+ return helper.formatHelp(cmd, helper);
129
+ // For main program, show grouped help
130
+ return GROUPED_HELP;
131
+ },
132
+ });
66
133
  // ── Pre-action guard: check configuration ────────────────────────────────────
67
134
  const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity']);
68
135
  program.hook('preAction', (thisCommand, actionCommand) => {
@@ -90,8 +157,9 @@ program
90
157
  .option('--enable-trading', 'Enable trading (sf buy/sell/cancel)')
91
158
  .option('--disable-trading', 'Disable trading')
92
159
  .option('--kalshi', 'Reconfigure Kalshi API credentials')
160
+ .option('--polymarket', 'Reconfigure Polymarket wallet address')
93
161
  .action(async (opts) => {
94
- await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading, kalshi: opts.kalshi }));
162
+ await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading, kalshi: opts.kalshi, polymarket: opts.polymarket }));
95
163
  });
96
164
  // ── sf list ──────────────────────────────────────────────────────────────────
97
165
  program
@@ -148,9 +216,10 @@ program
148
216
  // ── sf scan [query] ───────────────────────────────────────────────────────────
149
217
  program
150
218
  .command('scan [query]')
151
- .description('Explore Kalshi markets (no auth required)')
219
+ .description('Explore Kalshi + Polymarket markets')
152
220
  .option('--series <ticker>', 'List events + markets for a series (e.g. KXWTIMAX)')
153
221
  .option('--market <ticker>', 'Get single market detail (e.g. KXWTIMAX-26DEC31-T140)')
222
+ .option('--venue <venue>', 'Filter by venue: kalshi, polymarket, or all (default: all)')
154
223
  .option('--json', 'Output raw JSON')
155
224
  .action(async (query, opts, cmd) => {
156
225
  const g = cmd.optsWithGlobals();
@@ -162,6 +231,7 @@ program
162
231
  await run(() => (0, scan_js_1.scanCommand)(q, {
163
232
  series: opts.series,
164
233
  market: opts.market,
234
+ venue: opts.venue,
165
235
  json: opts.json,
166
236
  apiKey: g.apiKey,
167
237
  apiUrl: g.apiUrl,
@@ -238,11 +308,12 @@ program
238
308
  // ── sf dashboard ──────────────────────────────────────────────────────────────
239
309
  program
240
310
  .command('dashboard')
241
- .description('Portfolio overview — theses, positions, risk, unpositioned edges')
311
+ .description('Portfolio overview — interactive TUI (default), or one-shot with --once/--json')
242
312
  .option('--json', 'JSON output')
313
+ .option('--once', 'One-time print (no interactive mode)')
243
314
  .action(async (opts, cmd) => {
244
315
  const g = cmd.optsWithGlobals();
245
- await run(() => (0, dashboard_js_1.dashboardCommand)({ json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
316
+ await run(() => (0, dashboard_js_1.dashboardCommand)({ json: opts.json, once: opts.once, apiKey: g.apiKey, apiUrl: g.apiUrl }));
246
317
  });
247
318
  // ── sf milestones ────────────────────────────────────────────────────────────
248
319
  program
@@ -400,14 +471,41 @@ program
400
471
  });
401
472
  // ── sf liquidity ─────────────────────────────────────────────────────────────
402
473
  program
403
- .command('liquidity')
404
- .description('Market liquidity scanner by topic and horizon')
405
- .option('--topic <topic>', 'Filter topic (oil, recession, fed, cpi, gas, sp500)')
474
+ .command('liquidity [topic]')
475
+ .description('Market liquidity scanner run without args to see topics')
476
+ .option('--all', 'Scan all topics')
477
+ .option('--venue <venue>', 'Filter venue: kalshi, polymarket, all (default: all)')
406
478
  .option('--horizon <horizon>', 'Filter horizon (weekly, monthly, long-term)')
407
479
  .option('--min-depth <depth>', 'Minimum bid+ask depth', parseInt)
408
480
  .option('--json', 'JSON output')
481
+ .action(async (topic, opts) => {
482
+ await run(() => (0, liquidity_js_1.liquidityCommand)({ ...opts, topic }));
483
+ });
484
+ // ── sf book ──────────────────────────────────────────────────────────────────
485
+ program
486
+ .command('book [tickers...]')
487
+ .description('Orderbook depth, spread, and liquidity for specific markets')
488
+ .option('--poly <query>', 'Search Polymarket markets by keyword')
489
+ .option('--history', 'Include 7-day price history sparkline')
490
+ .option('--json', 'JSON output')
491
+ .action(async (tickers, opts) => {
492
+ if (tickers.length === 0 && !opts.poly) {
493
+ console.error('Usage: sf book <ticker> [ticker2...] OR sf book --poly "oil price"');
494
+ process.exit(1);
495
+ }
496
+ await run(() => (0, book_js_1.bookCommand)(tickers, opts));
497
+ });
498
+ // ── sf telegram ──────────────────────────────────────────────────────────────
499
+ program
500
+ .command('telegram')
501
+ .description('Start Telegram bot for monitoring and trading')
502
+ .option('--token <token>', 'Telegram bot token (or set TELEGRAM_BOT_TOKEN)')
503
+ .option('--chat-id <id>', 'Restrict to specific chat ID')
504
+ .option('--daemon', 'Run in background')
505
+ .option('--stop', 'Stop background bot')
506
+ .option('--status', 'Check if bot is running')
409
507
  .action(async (opts) => {
410
- await run(() => (0, liquidity_js_1.liquidityCommand)(opts));
508
+ await run(() => (0, telegram_js_1.telegramCommand)(opts));
411
509
  });
412
510
  // ── sf strategies ─────────────────────────────────────────────────────────────
413
511
  (0, strategies_js_1.registerStrategies)(program);