@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.
- package/README.md +205 -48
- package/dist/cache.d.ts +6 -0
- package/dist/cache.js +31 -0
- package/dist/cache.test.d.ts +1 -0
- package/dist/cache.test.js +73 -0
- package/dist/client.test.d.ts +1 -0
- package/dist/client.test.js +89 -0
- package/dist/commands/agent.js +594 -106
- package/dist/commands/book.d.ts +17 -0
- package/dist/commands/book.js +220 -0
- package/dist/commands/dashboard.d.ts +6 -3
- package/dist/commands/dashboard.js +53 -22
- package/dist/commands/liquidity.d.ts +2 -0
- package/dist/commands/liquidity.js +128 -43
- package/dist/commands/performance.js +9 -2
- package/dist/commands/positions.js +50 -0
- package/dist/commands/scan.d.ts +1 -0
- package/dist/commands/scan.js +66 -15
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +71 -6
- package/dist/commands/telegram.d.ts +15 -0
- package/dist/commands/telegram.js +125 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +9 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +138 -0
- package/dist/index.js +107 -9
- package/dist/polymarket.d.ts +237 -0
- package/dist/polymarket.js +353 -0
- package/dist/polymarket.test.d.ts +1 -0
- package/dist/polymarket.test.js +424 -0
- package/dist/telegram/agent-bridge.d.ts +15 -0
- package/dist/telegram/agent-bridge.js +368 -0
- package/dist/telegram/bot.d.ts +10 -0
- package/dist/telegram/bot.js +297 -0
- package/dist/telegram/commands.d.ts +11 -0
- package/dist/telegram/commands.js +120 -0
- package/dist/telegram/format.d.ts +11 -0
- package/dist/telegram/format.js +51 -0
- package/dist/telegram/format.test.d.ts +1 -0
- package/dist/telegram/format.test.js +73 -0
- package/dist/telegram/poller.d.ts +6 -0
- package/dist/telegram/poller.js +32 -0
- package/dist/topics.d.ts +3 -0
- package/dist/topics.js +65 -7
- package/dist/topics.test.d.ts +1 -0
- package/dist/topics.test.js +131 -0
- package/dist/tui/border.d.ts +33 -0
- package/dist/tui/border.js +87 -0
- package/dist/tui/chart.d.ts +19 -0
- package/dist/tui/chart.js +117 -0
- package/dist/tui/dashboard.d.ts +9 -0
- package/dist/tui/dashboard.js +814 -0
- package/dist/tui/layout.d.ts +16 -0
- package/dist/tui/layout.js +41 -0
- package/dist/tui/screen.d.ts +33 -0
- package/dist/tui/screen.js +102 -0
- package/dist/tui/state.d.ts +40 -0
- package/dist/tui/state.js +36 -0
- package/dist/tui/widgets/commandbar.d.ts +8 -0
- package/dist/tui/widgets/commandbar.js +82 -0
- package/dist/tui/widgets/detail.d.ts +9 -0
- package/dist/tui/widgets/detail.js +151 -0
- package/dist/tui/widgets/edges.d.ts +4 -0
- package/dist/tui/widgets/edges.js +34 -0
- package/dist/tui/widgets/liquidity.d.ts +9 -0
- package/dist/tui/widgets/liquidity.js +142 -0
- package/dist/tui/widgets/orders.d.ts +4 -0
- package/dist/tui/widgets/orders.js +37 -0
- package/dist/tui/widgets/portfolio.d.ts +4 -0
- package/dist/tui/widgets/portfolio.js +59 -0
- package/dist/tui/widgets/signals.d.ts +4 -0
- package/dist/tui/widgets/signals.js +31 -0
- package/dist/tui/widgets/statusbar.d.ts +8 -0
- package/dist/tui/widgets/statusbar.js +72 -0
- package/dist/tui/widgets/thesis.d.ts +4 -0
- package/dist/tui/widgets/thesis.js +66 -0
- package/dist/tui/widgets/trade.d.ts +9 -0
- package/dist/tui/widgets/trade.js +117 -0
- package/dist/tui/widgets/upcoming.d.ts +4 -0
- package/dist/tui/widgets/upcoming.js +41 -0
- package/dist/tui/widgets/whatif.d.ts +7 -0
- package/dist/tui/widgets/whatif.js +113 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +111 -0
- 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
|
|
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 —
|
|
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
|
|
405
|
-
.option('--
|
|
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,
|
|
508
|
+
await run(() => (0, telegram_js_1.telegramCommand)(opts));
|
|
411
509
|
});
|
|
412
510
|
// ── sf strategies ─────────────────────────────────────────────────────────────
|
|
413
511
|
(0, strategies_js_1.registerStrategies)(program);
|