@spfunctions/cli 1.7.17 → 1.7.19

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.
@@ -319,7 +319,7 @@ function createFooterBar(piTui) {
319
319
  const tokStr = this.tokens >= 1000 ? `${(this.tokens / 1000).toFixed(1)}k` : `${this.tokens}`;
320
320
  const tokens = C.zinc600(`${tokStr} tok`);
321
321
  const exchange = this.exchangeOpen === true ? C.emerald('OPEN') : this.exchangeOpen === false ? C.red('CLOSED') : C.zinc600('...');
322
- const trading = this.tradingEnabled ? C.amber('\u26A1 trading') : C.zinc600('\u26A1 read-only');
322
+ const trading = this.tradingEnabled ? C.amber('trading') : C.zinc600('read-only');
323
323
  const help = C.zinc600('/help');
324
324
  const leftText = [model, tokens, exchange, trading].join(sep);
325
325
  const lw = visibleWidth(leftText);
@@ -2207,7 +2207,7 @@ ${topEdges}
2207
2207
  }
2208
2208
  }
2209
2209
  if (event.type === 'tool_execution_start') {
2210
- const toolLine = new MutableLine(C.zinc600(` \u26A1 ${event.toolName}...`));
2210
+ const toolLine = new MutableLine(C.zinc600(` \u25B8 ${event.toolName}...`));
2211
2211
  toolStartTimes.set(event.toolCallId || event.toolName, Date.now());
2212
2212
  toolLines.set(event.toolCallId || event.toolName, toolLine);
2213
2213
  chatContainer.addChild(toolLine);
@@ -2226,7 +2226,7 @@ ${topEdges}
2226
2226
  line.setText(C.red(` \u2717 ${event.toolName} (${elapsed}s) error`));
2227
2227
  }
2228
2228
  else {
2229
- line.setText(C.zinc600(` \u26A1 ${event.toolName}`) + C.emerald(` \u2713`) + C.zinc600(` (${elapsed}s)`));
2229
+ line.setText(C.zinc600(` \u25B8 ${event.toolName} `) + C.emerald(`\u2713`) + C.zinc600(` ${elapsed}s`));
2230
2230
  }
2231
2231
  }
2232
2232
  toolStartTimes.delete(key);
@@ -4016,7 +4016,7 @@ ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice
4016
4016
  }
4017
4017
  }
4018
4018
  if (event.type === 'tool_execution_start') {
4019
- process.stderr.write(` \u26A1 ${event.toolName}...\n`);
4019
+ process.stderr.write(` \u25B8 ${event.toolName}...\n`);
4020
4020
  }
4021
4021
  if (event.type === 'tool_execution_end') {
4022
4022
  const status = event.isError ? '\u2717' : '\u2713';
@@ -6,7 +6,7 @@ const utils_js_1 = require("../utils.js");
6
6
  async function balanceCommand(opts) {
7
7
  const result = await (0, kalshi_js_1.getBalance)();
8
8
  if (!result)
9
- throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
9
+ throw new Error('Kalshi not configured. Run: sf setup --kalshi');
10
10
  if (opts.json) {
11
11
  console.log(JSON.stringify(result, null, 2));
12
12
  return;
@@ -152,7 +152,7 @@ async function bookCommand(tickers, opts) {
152
152
  }
153
153
  }
154
154
  if (results.length === 0) {
155
- console.log(`${utils_js_1.c.dim}No markets found.${utils_js_1.c.reset}`);
155
+ console.log(`\n ${utils_js_1.c.dim}No markets found. Check the ticker and try again.${utils_js_1.c.reset}\n`);
156
156
  return;
157
157
  }
158
158
  // ── JSON output ──
@@ -28,23 +28,28 @@ async function edgesCommand(opts) {
28
28
  const liqRank = { a: 4, high: 4, b: 3, medium: 3, c: 2, low: 2, d: 1 };
29
29
  // ── Step 1: Fetch theses (or single thesis) ────────────────────────────────
30
30
  let theses;
31
+ const log = opts.json ? (() => { }) : (msg) => console.log(msg);
31
32
  if (opts.thesis) {
32
33
  // Single thesis mode — skip listing, fetch context directly
33
- console.log(`${utils_js_1.c.dim}Fetching edges for thesis ${opts.thesis}...${utils_js_1.c.reset}`);
34
+ log(`${utils_js_1.c.dim}Fetching edges for thesis ${opts.thesis}...${utils_js_1.c.reset}`);
34
35
  theses = [{ id: opts.thesis }];
35
36
  }
36
37
  else {
37
- console.log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
38
+ log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
38
39
  const data = await client.listTheses();
39
40
  const rawTheses = data.theses || data;
40
41
  theses = (Array.isArray(rawTheses) ? rawTheses : []).filter((t) => t.status === 'active');
41
42
  }
42
43
  if (theses.length === 0) {
43
- console.log(`${utils_js_1.c.yellow}No active theses found.${utils_js_1.c.reset} Create one: sf create "your thesis"`);
44
+ if (opts.json) {
45
+ console.log(JSON.stringify({ edges: [], totalEdges: 0 }));
46
+ return;
47
+ }
48
+ (0, utils_js_1.emptyState)('active theses', 'Create one: sf create "your market thesis"');
44
49
  return;
45
50
  }
46
51
  // ── Step 2: Fetch context for each thesis (parallel) ───────────────────────
47
- console.log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} ${theses.length === 1 ? 'thesis' : 'theses'}...${utils_js_1.c.reset}`);
52
+ log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} ${theses.length === 1 ? 'thesis' : 'theses'}...${utils_js_1.c.reset}`);
48
53
  const allEdges = [];
49
54
  const contextPromises = theses.map(async (t) => {
50
55
  try {
@@ -75,7 +80,11 @@ async function edgesCommand(opts) {
75
80
  }
76
81
  }
77
82
  if (allEdges.length === 0) {
78
- console.log(`${utils_js_1.c.yellow}No edges found across ${theses.length} theses.${utils_js_1.c.reset}`);
83
+ if (opts.json) {
84
+ console.log(JSON.stringify({ edges: [], totalEdges: 0, thesesScanned: theses.length }));
85
+ return;
86
+ }
87
+ (0, utils_js_1.emptyState)('edges', `Scanned ${theses.length} theses. Edges appear when thesis price diverges from market price.`);
79
88
  return;
80
89
  }
81
90
  // ── Step 3: Dedupe by marketId — keep highest absolute edge ────────────────
@@ -100,7 +109,7 @@ async function edgesCommand(opts) {
100
109
  // ── Step 4: Fetch positions (optional) ─────────────────────────────────────
101
110
  let positions = null;
102
111
  if ((0, kalshi_js_1.isKalshiConfigured)()) {
103
- console.log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
112
+ log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
104
113
  positions = await (0, kalshi_js_1.getPositions)();
105
114
  if (positions) {
106
115
  // Enrich with live prices
@@ -12,6 +12,7 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.exploreCommand = exploreCommand;
14
14
  const share_js_1 = require("../share.js");
15
+ const utils_js_1 = require("../utils.js");
15
16
  const BASE_URL = 'https://simplefunctions.dev';
16
17
  async function exploreCommand(slug, opts) {
17
18
  if (!slug) {
@@ -32,7 +33,7 @@ async function exploreCommand(slug, opts) {
32
33
  }
33
34
  console.log('\n Public Theses\n');
34
35
  if (theses.length === 0) {
35
- console.log(' No public theses yet.\n');
36
+ console.log(` ${utils_js_1.c.dim}No public theses yet. Publish yours: sf publish <id>${utils_js_1.c.reset}\n`);
36
37
  return;
37
38
  }
38
39
  for (const t of theses) {
@@ -6,7 +6,7 @@ const utils_js_1 = require("../utils.js");
6
6
  async function fillsCommand(opts) {
7
7
  const result = await (0, kalshi_js_1.getFills)({ ticker: opts.ticker, limit: 50 });
8
8
  if (!result)
9
- throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
9
+ throw new Error('Kalshi not configured. Run: sf setup --kalshi');
10
10
  if (opts.json) {
11
11
  console.log(JSON.stringify(result.fills, null, 2));
12
12
  return;
@@ -11,7 +11,7 @@ async function listCommand(opts) {
11
11
  return;
12
12
  }
13
13
  if (theses.length === 0) {
14
- console.log(`${utils_js_1.c.dim}No theses found.${utils_js_1.c.reset}`);
14
+ (0, utils_js_1.emptyState)('theses', 'Create one: sf create "your market thesis"');
15
15
  return;
16
16
  }
17
17
  console.log(`\n${utils_js_1.c.bold}` +
@@ -6,4 +6,5 @@
6
6
  */
7
7
  export declare function loginCommand(opts: {
8
8
  apiUrl?: string;
9
+ force?: boolean;
9
10
  }): Promise<void>;
@@ -13,11 +13,11 @@ const utils_js_1 = require("../utils.js");
13
13
  const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
14
14
  async function loginCommand(opts) {
15
15
  const apiUrl = opts.apiUrl || (0, config_js_1.loadConfig)().apiUrl || SF_API_URL;
16
- // Only block if the CONFIG FILE has an API key (not env vars)
16
+ // If already configured, re-login replaces the existing key
17
17
  const fileConfig = (0, config_js_1.loadFileConfig)();
18
- if (fileConfig.apiKey) {
19
- console.log(`\n ${utils_js_1.c.dim}Already configured with API key ${fileConfig.apiKey.slice(0, 12)}...${utils_js_1.c.reset}`);
20
- console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf setup --reset${utils_js_1.c.dim} first to reconfigure.${utils_js_1.c.reset}\n`);
18
+ if (fileConfig.apiKey && !opts.force) {
19
+ console.log(`\n ${utils_js_1.c.dim}Already logged in (${fileConfig.apiKey.slice(0, 12)}...).${utils_js_1.c.reset}`);
20
+ console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf login --force${utils_js_1.c.dim} to re-authenticate, or ${utils_js_1.c.cyan}sf logout${utils_js_1.c.dim} to clear.${utils_js_1.c.reset}\n`);
21
21
  return;
22
22
  }
23
23
  // Generate session token
@@ -180,10 +180,10 @@ async function positionsCommand(opts) {
180
180
  console.log('');
181
181
  }
182
182
  else if ((0, kalshi_js_1.isKalshiConfigured)()) {
183
- console.log(`\n${utils_js_1.c.dim}No open positions on Kalshi.${utils_js_1.c.reset}\n`);
183
+ console.log(`\n ${utils_js_1.c.dim}No open Kalshi positions.${utils_js_1.c.reset}\n`);
184
184
  }
185
185
  else {
186
- console.log(`\n${utils_js_1.c.yellow}Kalshi not configured.${utils_js_1.c.reset} Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH to see positions.\n`);
186
+ console.log(`\n ${utils_js_1.c.dim}Kalshi not configured. Run: ${utils_js_1.c.cyan}sf setup --kalshi${utils_js_1.c.reset}\n`);
187
187
  }
188
188
  // C) Polymarket positions
189
189
  if (polyPositions.length > 0) {
@@ -194,7 +194,7 @@ async function keywordScan(query, json, venue = 'all', share) {
194
194
  console.log('');
195
195
  // Conversion hook
196
196
  if (!json) {
197
- console.log(`${utils_js_1.c.dim}Want 24/7 monitoring + edge detection? ${utils_js_1.c.reset}${utils_js_1.c.cyan}sf create${utils_js_1.c.reset}${utils_js_1.c.dim} "your thesis"${utils_js_1.c.reset}`);
197
+ console.log(`${utils_js_1.c.dim}Want 24/7 monitoring + edge detection? ${utils_js_1.c.reset}${utils_js_1.c.cyan}sf create${utils_js_1.c.reset}${utils_js_1.c.dim} "${query.slice(0, 50)}"${utils_js_1.c.reset}`);
198
198
  console.log('');
199
199
  }
200
200
  }
package/dist/config.js CHANGED
@@ -126,11 +126,11 @@ function isConfigured() {
126
126
  function requireTrading() {
127
127
  const config = loadConfig();
128
128
  if (!config.tradingEnabled) {
129
- console.error('\n ⚠️ Trading is disabled. Run: sf setup --enable-trading\n');
129
+ console.error('\n Trading is disabled. Run: sf setup --enable-trading\n');
130
130
  process.exit(1);
131
131
  }
132
132
  if (!config.kalshiKeyId && !process.env.KALSHI_API_KEY_ID) {
133
- console.error('\n ⚠️ Kalshi API key not configured. Run: sf setup\n');
133
+ console.error('\n Kalshi API key not configured. Run: sf setup --kalshi\n');
134
134
  process.exit(1);
135
135
  }
136
136
  }
package/dist/index.js CHANGED
@@ -77,6 +77,7 @@ const GROUPED_HELP = `
77
77
 
78
78
  \x1b[1mSetup\x1b[22m
79
79
  \x1b[36mlogin\x1b[39m Browser login (recommended)
80
+ \x1b[36mlogout\x1b[39m Clear saved credentials
80
81
  \x1b[36msetup\x1b[39m Interactive config wizard (power users)
81
82
  \x1b[36msetup --check\x1b[39m Show config status
82
83
  \x1b[36msetup --polymarket\x1b[39m Configure Polymarket wallet
@@ -149,7 +150,7 @@ const GROUPED_HELP = `
149
150
  program
150
151
  .name('sf')
151
152
  .description('SimpleFunctions CLI — prediction market thesis agent')
152
- .version('0.1.0')
153
+ .version('1.7.17')
153
154
  .option('--api-key <key>', 'API key (or set SF_API_KEY env var)')
154
155
  .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)')
155
156
  .configureHelp({
@@ -247,7 +248,7 @@ async function interactiveEntry() {
247
248
  console.log();
248
249
  }
249
250
  // ── Pre-action guard: check configuration ────────────────────────────────────
250
- const NO_CONFIG_COMMANDS = new Set(['setup', 'login', 'help', 'scan', 'explore', 'query', 'context', 'markets', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity', 'book', 'prompt', 'sf']);
251
+ const NO_CONFIG_COMMANDS = new Set(['setup', 'login', 'logout', 'help', 'scan', 'explore', 'query', 'context', 'markets', 'watch', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity', 'book', 'prompt', 'sf']);
251
252
  program.hook('preAction', (thisCommand, actionCommand) => {
252
253
  const cmdName = actionCommand.name();
253
254
  if (NO_CONFIG_COMMANDS.has(cmdName))
@@ -259,7 +260,7 @@ program.hook('preAction', (thisCommand, actionCommand) => {
259
260
  if (!(0, config_js_1.isConfigured)()) {
260
261
  const isJson = process.argv.includes('--json');
261
262
  if (isJson) {
262
- console.log(JSON.stringify({ error: 'API key required. Run sf setup or pass --api-key.', code: 'NOT_CONFIGURED' }));
263
+ console.log(JSON.stringify({ error: 'API key required.', code: 'NOT_CONFIGURED', keyUrl: 'https://simplefunctions.dev/dashboard/keys', cli: 'sf login' }));
263
264
  }
264
265
  else {
265
266
  console.log();
@@ -268,7 +269,8 @@ program.hook('preAction', (thisCommand, actionCommand) => {
268
269
  console.log(' 2. \x1b[36msf setup\x1b[39m Interactive wizard (2 min)');
269
270
  console.log(' 3. \x1b[36msf --api-key KEY\x1b[39m Pass inline');
270
271
  console.log();
271
- console.log(' \x1b[2mDon\'t have a key? Try \x1b[36msf scan "oil"\x1b[22m\x1b[2m — works without login.\x1b[22m');
272
+ console.log(' \x1b[2mGet a key at: \x1b[36mhttps://simplefunctions.dev/dashboard/keys\x1b[22m');
273
+ console.log(' \x1b[2mNo key? Try \x1b[36msf scan "oil"\x1b[22m\x1b[2m — works without login.\x1b[22m');
272
274
  }
273
275
  console.log();
274
276
  process.exit(1);
@@ -292,9 +294,27 @@ program
292
294
  program
293
295
  .command('login')
294
296
  .description('Browser-based login (recommended — no API keys needed)')
295
- .action(async (_opts, cmd) => {
297
+ .option('--force', 'Re-login even if already configured')
298
+ .action(async (opts, cmd) => {
296
299
  const g = cmd.optsWithGlobals();
297
- await run(() => (0, login_js_1.loginCommand)({ apiUrl: g.apiUrl }));
300
+ await run(() => (0, login_js_1.loginCommand)({ apiUrl: g.apiUrl, force: opts.force }));
301
+ });
302
+ // ── sf logout ────────────────────────────────────────────────────────────────
303
+ program
304
+ .command('logout')
305
+ .description('Clear saved credentials')
306
+ .action(async () => {
307
+ await run(async () => {
308
+ const { resetConfig, loadFileConfig } = await import('./config.js');
309
+ const cfg = loadFileConfig();
310
+ if (!cfg.apiKey) {
311
+ console.log(`\n ${utils_js_1.c.dim}Not logged in.${utils_js_1.c.reset}\n`);
312
+ return;
313
+ }
314
+ resetConfig();
315
+ console.log(`\n Logged out. Credentials removed from ~/.sf/config.json`);
316
+ console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf login${utils_js_1.c.dim} to re-authenticate.${utils_js_1.c.reset}\n`);
317
+ });
298
318
  });
299
319
  // ── sf list ──────────────────────────────────────────────────────────────────
300
320
  program
package/dist/utils.d.ts CHANGED
@@ -34,6 +34,16 @@ export declare function rpad(s: string, width: number): string;
34
34
  export declare function hr(width?: number): void;
35
35
  /** Print an error and exit */
36
36
  export declare function die(msg: string): never;
37
+ /**
38
+ * Print an error with an actionable suggestion.
39
+ * Use instead of bare console.error for user-facing errors.
40
+ */
41
+ export declare function errorWithHint(message: string, hint?: string): void;
42
+ /**
43
+ * Print an empty state message with a next-action CTA.
44
+ * Use when a command returns zero results.
45
+ */
46
+ export declare function emptyState(thing: string, nextAction?: string): void;
37
47
  /** Print a section header */
38
48
  export declare function header(title: string): void;
39
49
  /** Truncate a string with ellipsis */
package/dist/utils.js CHANGED
@@ -15,6 +15,8 @@ exports.pad = pad;
15
15
  exports.rpad = rpad;
16
16
  exports.hr = hr;
17
17
  exports.die = die;
18
+ exports.errorWithHint = errorWithHint;
19
+ exports.emptyState = emptyState;
18
20
  exports.header = header;
19
21
  exports.trunc = trunc;
20
22
  exports.shortId = shortId;
@@ -108,6 +110,26 @@ function die(msg) {
108
110
  console.error(`${exports.c.red}Error:${exports.c.reset} ${msg}`);
109
111
  process.exit(1);
110
112
  }
113
+ /**
114
+ * Print an error with an actionable suggestion.
115
+ * Use instead of bare console.error for user-facing errors.
116
+ */
117
+ function errorWithHint(message, hint) {
118
+ console.error(`\n ${exports.c.red}Error:${exports.c.reset} ${message}`);
119
+ if (hint)
120
+ console.error(` ${exports.c.dim}${hint}${exports.c.reset}`);
121
+ console.error();
122
+ }
123
+ /**
124
+ * Print an empty state message with a next-action CTA.
125
+ * Use when a command returns zero results.
126
+ */
127
+ function emptyState(thing, nextAction) {
128
+ console.log(`\n ${exports.c.dim}No ${thing} found.${exports.c.reset}`);
129
+ if (nextAction)
130
+ console.log(` ${exports.c.dim}${nextAction}${exports.c.reset}`);
131
+ console.log();
132
+ }
111
133
  /** Print a section header */
112
134
  function header(title) {
113
135
  console.log(`\n${exports.c.bold}${exports.c.cyan}${title}${exports.c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.7.17",
3
+ "version": "1.7.19",
4
+ "mcpName": "io.github.spfunctions/simplefunctions",
4
5
  "description": "Prediction market intelligence CLI. Causal thesis model, 24/7 Kalshi/Polymarket scan, live orderbook, edge detection. Interactive agent mode with tool calling.",
5
6
  "bin": {
6
7
  "sf": "./dist/index.js"