@spfunctions/cli 1.7.16 → 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.
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ const create_js_1 = require("./commands/create.js");
29
29
  const signal_js_1 = require("./commands/signal.js");
30
30
  const evaluate_js_1 = require("./commands/evaluate.js");
31
31
  const scan_js_1 = require("./commands/scan.js");
32
+ const watch_js_1 = require("./commands/watch.js");
32
33
  const positions_js_1 = require("./commands/positions.js");
33
34
  const edges_js_1 = require("./commands/edges.js");
34
35
  const agent_js_1 = require("./commands/agent.js");
@@ -76,6 +77,7 @@ const GROUPED_HELP = `
76
77
 
77
78
  \x1b[1mSetup\x1b[22m
78
79
  \x1b[36mlogin\x1b[39m Browser login (recommended)
80
+ \x1b[36mlogout\x1b[39m Clear saved credentials
79
81
  \x1b[36msetup\x1b[39m Interactive config wizard (power users)
80
82
  \x1b[36msetup --check\x1b[39m Show config status
81
83
  \x1b[36msetup --polymarket\x1b[39m Configure Polymarket wallet
@@ -99,12 +101,18 @@ const GROUPED_HELP = `
99
101
  \x1b[36mscan\x1b[39m "keywords" Search Kalshi + Polymarket
100
102
  \x1b[36mscan\x1b[39m --series TICKER Browse a Kalshi series
101
103
  \x1b[36medges\x1b[39m [--json] Top edges across all theses
104
+ \x1b[36mwatch\x1b[39m [query] Watch for market changes (e.g. sf watch "gold")
102
105
  \x1b[36mwhatif\x1b[39m <id> What-if scenario analysis
103
106
  \x1b[36mliquidity\x1b[39m [topic] Orderbook liquidity scanner
104
107
  \x1b[36mbook\x1b[39m <ticker> [ticker2...] Orderbook depth for specific markets
105
108
  \x1b[36mexplore\x1b[39m [slug] Browse public theses
106
109
  \x1b[36mforecast\x1b[39m <event> Market distribution (P50/P75/P90)
107
110
 
111
+ \x1b[1mShare\x1b[22m \x1b[2m(any command with --share generates a short URL)\x1b[22m
112
+ \x1b[36mscan\x1b[39m "gold" --share Share scan results
113
+ \x1b[36mquery\x1b[39m "fed rate" --share Share query results
114
+ \x1b[36mcontext\x1b[39m --share Share global context snapshot
115
+
108
116
  \x1b[1mPortfolio\x1b[22m
109
117
  \x1b[36mpositions\x1b[39m Kalshi + Polymarket positions
110
118
  \x1b[36mbalance\x1b[39m Account balance
@@ -142,7 +150,7 @@ const GROUPED_HELP = `
142
150
  program
143
151
  .name('sf')
144
152
  .description('SimpleFunctions CLI — prediction market thesis agent')
145
- .version('0.1.0')
153
+ .version('1.7.17')
146
154
  .option('--api-key <key>', 'API key (or set SF_API_KEY env var)')
147
155
  .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)')
148
156
  .configureHelp({
@@ -240,7 +248,7 @@ async function interactiveEntry() {
240
248
  console.log();
241
249
  }
242
250
  // ── Pre-action guard: check configuration ────────────────────────────────────
243
- 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']);
244
252
  program.hook('preAction', (thisCommand, actionCommand) => {
245
253
  const cmdName = actionCommand.name();
246
254
  if (NO_CONFIG_COMMANDS.has(cmdName))
@@ -252,7 +260,7 @@ program.hook('preAction', (thisCommand, actionCommand) => {
252
260
  if (!(0, config_js_1.isConfigured)()) {
253
261
  const isJson = process.argv.includes('--json');
254
262
  if (isJson) {
255
- 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' }));
256
264
  }
257
265
  else {
258
266
  console.log();
@@ -261,7 +269,8 @@ program.hook('preAction', (thisCommand, actionCommand) => {
261
269
  console.log(' 2. \x1b[36msf setup\x1b[39m Interactive wizard (2 min)');
262
270
  console.log(' 3. \x1b[36msf --api-key KEY\x1b[39m Pass inline');
263
271
  console.log();
264
- 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');
265
274
  }
266
275
  console.log();
267
276
  process.exit(1);
@@ -285,9 +294,27 @@ program
285
294
  program
286
295
  .command('login')
287
296
  .description('Browser-based login (recommended — no API keys needed)')
288
- .action(async (_opts, cmd) => {
297
+ .option('--force', 'Re-login even if already configured')
298
+ .action(async (opts, cmd) => {
289
299
  const g = cmd.optsWithGlobals();
290
- 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
+ });
291
318
  });
292
319
  // ── sf list ──────────────────────────────────────────────────────────────────
293
320
  program
@@ -312,9 +339,10 @@ program
312
339
  .command('context [id]')
313
340
  .description('Context snapshot. With ID: thesis-specific. Without: global market snapshot (no auth)')
314
341
  .option('--json', 'Output raw JSON')
342
+ .option('--share', 'Share output via short URL')
315
343
  .action(async (id, opts, cmd) => {
316
344
  const g = cmd.optsWithGlobals();
317
- await run(() => (0, context_js_1.contextCommand)(id, { json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
345
+ await run(() => (0, context_js_1.contextCommand)(id, { json: opts.json, share: opts.share, apiKey: g.apiKey, apiUrl: g.apiUrl }));
318
346
  });
319
347
  // ── sf create <thesis> ────────────────────────────────────────────────────────
320
348
  program
@@ -376,6 +404,7 @@ program
376
404
  .option('--market <ticker>', 'Get single market detail (e.g. KXWTIMAX-26DEC31-T140)')
377
405
  .option('--venue <venue>', 'Filter by venue: kalshi, polymarket, or all (default: all)')
378
406
  .option('--json', 'Output raw JSON')
407
+ .option('--share', 'Share output via short URL')
379
408
  .action(async (query, opts, cmd) => {
380
409
  const g = cmd.optsWithGlobals();
381
410
  const q = query || '';
@@ -388,15 +417,26 @@ program
388
417
  market: opts.market,
389
418
  venue: opts.venue,
390
419
  json: opts.json,
420
+ share: opts.share,
391
421
  apiKey: g.apiKey,
392
422
  apiUrl: g.apiUrl,
393
423
  }));
394
424
  });
425
+ // ── sf watch [query] ─────────────────────────────────────────────────────────
426
+ program
427
+ .command('watch [query]')
428
+ .description('Watch for market changes (e.g. sf watch "gold")')
429
+ .option('--interval <seconds>', 'Poll interval in seconds (default: 60)', '60')
430
+ .option('--json', 'JSON change events (for piping)')
431
+ .action(async (query, opts) => {
432
+ await run(() => (0, watch_js_1.watchCommand)(query, { interval: opts.interval, json: opts.json }));
433
+ });
395
434
  // ── sf edges ──────────────────────────────────────────────────────────────────
396
435
  program
397
436
  .command('edges')
398
437
  .description('Top edges across all theses — what to trade now')
399
438
  .option('--json', 'JSON output for agents')
439
+ .option('--share', 'Share output via short URL')
400
440
  .option('--limit <n>', 'Max edges to show', '20')
401
441
  .option('--thesis <id>', 'Filter to a single thesis')
402
442
  .option('--min-edge <cents>', 'Minimum absolute edge size in cents')
@@ -406,6 +446,7 @@ program
406
446
  const g = cmd.optsWithGlobals();
407
447
  await run(() => (0, edges_js_1.edgesCommand)({
408
448
  json: opts.json,
449
+ share: opts.share,
409
450
  limit: opts.limit,
410
451
  thesis: opts.thesis,
411
452
  minEdge: opts.minEdge,
@@ -465,8 +506,9 @@ program
465
506
  .command('explore [slug]')
466
507
  .description('Browse public theses (no auth required)')
467
508
  .option('--json', 'JSON output')
509
+ .option('--share', 'Share output via short URL')
468
510
  .action(async (slug, opts) => {
469
- await run(() => (0, explore_js_1.exploreCommand)(slug, { json: opts.json }));
511
+ await run(() => (0, explore_js_1.exploreCommand)(slug, { json: opts.json, share: opts.share }));
470
512
  });
471
513
  // ── sf dashboard ──────────────────────────────────────────────────────────────
472
514
  program
@@ -702,17 +744,19 @@ program
702
744
  .command('markets')
703
745
  .description('Traditional market snapshot — SPY, VIX, Treasury, Gold, Oil (no auth)')
704
746
  .option('--json', 'JSON output')
747
+ .option('--share', 'Share output via short URL')
705
748
  .action(async (opts) => {
706
- await run(() => (0, markets_js_1.marketsCommand)({ json: opts.json }));
749
+ await run(() => (0, markets_js_1.marketsCommand)({ json: opts.json, share: opts.share }));
707
750
  });
708
751
  // ── sf query "question" ──────────────────────────────────────────────────────
709
752
  program
710
753
  .command('query <question>')
711
754
  .description('LLM-enhanced prediction market knowledge search (no auth required)')
712
755
  .option('--json', 'JSON output')
756
+ .option('--share', 'Share output via short URL')
713
757
  .option('--limit <n>', 'Max results per category (default 10)', '10')
714
758
  .action(async (question, opts) => {
715
- await run(() => (0, query_js_1.queryCommand)(question, { json: opts.json, limit: opts.limit }));
759
+ await run(() => (0, query_js_1.queryCommand)(question, { json: opts.json, share: opts.share, limit: opts.limit }));
716
760
  });
717
761
  // ── sf telegram ──────────────────────────────────────────────────────────────
718
762
  program
@@ -0,0 +1,4 @@
1
+ /**
2
+ * --share helper: POST CLI output to /api/share and print the URL.
3
+ */
4
+ export declare function shareOutput(command: string, args: string, data: unknown): Promise<void>;
package/dist/share.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * --share helper: POST CLI output to /api/share and print the URL.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shareOutput = shareOutput;
7
+ const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
8
+ async function shareOutput(command, args, data) {
9
+ try {
10
+ const res = await fetch(`${SF_API_URL}/api/share`, {
11
+ method: 'POST',
12
+ headers: { 'Content-Type': 'application/json' },
13
+ body: JSON.stringify({ command, args, data }),
14
+ });
15
+ if (!res.ok) {
16
+ console.error(`Share failed: ${res.status}`);
17
+ return;
18
+ }
19
+ const { url } = await res.json();
20
+ console.log();
21
+ console.log(` \x1b[36m${url}\x1b[39m`);
22
+ console.log();
23
+ }
24
+ catch (err) {
25
+ console.error(`Share failed: ${err.message}`);
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Skill loader — reads .md files from the skills directory,
3
+ * parses frontmatter, and makes them available as slash commands.
4
+ */
5
+ export interface Skill {
6
+ name: string;
7
+ trigger: string;
8
+ description: string;
9
+ author: string;
10
+ version: string;
11
+ category: string;
12
+ tags: string[];
13
+ toolsUsed: string[];
14
+ estimatedTime: string;
15
+ auto?: string;
16
+ prompt: string;
17
+ raw: string;
18
+ }
19
+ export declare function loadSkills(): Skill[];
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Skill loader — reads .md files from the skills directory,
4
+ * parses frontmatter, and makes them available as slash commands.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.loadSkills = loadSkills;
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ function loadSkills() {
11
+ const skills = [];
12
+ // Built-in skills from cli/src/skills/
13
+ // __dirname works in CJS; resolve to src/skills regardless of whether running from dist/ or src/
14
+ const thisDir = __dirname;
15
+ const skillsDir = thisDir.includes('dist')
16
+ ? (0, path_1.join)(thisDir, '../../src/skills') // running from dist/skills/
17
+ : thisDir; // running from src/skills/
18
+ let files = [];
19
+ try {
20
+ files = (0, fs_1.readdirSync)(skillsDir).filter(f => f.endsWith('.md'));
21
+ }
22
+ catch {
23
+ return skills;
24
+ }
25
+ for (const file of files) {
26
+ try {
27
+ const raw = (0, fs_1.readFileSync)((0, path_1.join)(skillsDir, file), 'utf-8');
28
+ const skill = parseSkillFile(raw);
29
+ if (skill)
30
+ skills.push(skill);
31
+ }
32
+ catch { }
33
+ }
34
+ // User skills from ~/.sf/skills/ (future extensibility)
35
+ try {
36
+ const home = process.env.HOME || process.env.USERPROFILE || '';
37
+ const userDir = (0, path_1.join)(home, '.sf', 'skills');
38
+ const userFiles = (0, fs_1.readdirSync)(userDir).filter(f => f.endsWith('.md'));
39
+ for (const file of userFiles) {
40
+ try {
41
+ const raw = (0, fs_1.readFileSync)((0, path_1.join)(userDir, file), 'utf-8');
42
+ const skill = parseSkillFile(raw);
43
+ if (skill)
44
+ skills.push(skill);
45
+ }
46
+ catch { }
47
+ }
48
+ }
49
+ catch { }
50
+ return skills;
51
+ }
52
+ function parseSkillFile(raw) {
53
+ // Parse YAML frontmatter between --- delimiters
54
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
55
+ if (!match)
56
+ return null;
57
+ const frontmatter = match[1];
58
+ const body = match[2].trim();
59
+ const meta = {};
60
+ for (const line of frontmatter.split('\n')) {
61
+ const m = line.match(/^(\w[\w_]*)\s*:\s*(.+)$/);
62
+ if (m)
63
+ meta[m[1]] = m[2].trim();
64
+ }
65
+ if (!meta.name || !meta.trigger)
66
+ return null;
67
+ // Extract the "Instructions" section as the prompt (everything after ## Instructions)
68
+ const instructionsMatch = body.match(/## Instructions\n([\s\S]*)$/i);
69
+ const prompt = instructionsMatch ? instructionsMatch[1].trim() : body;
70
+ // Parse array fields: [a, b, c]
71
+ const parseArray = (s) => (s || '').replace(/[\[\]]/g, '').split(',').map(x => x.trim()).filter(Boolean);
72
+ return {
73
+ name: meta.name,
74
+ trigger: meta.trigger,
75
+ description: meta.description || '',
76
+ author: meta.author || 'unknown',
77
+ version: meta.version || '1.0.0',
78
+ category: meta.category || 'general',
79
+ tags: parseArray(meta.tags),
80
+ toolsUsed: parseArray(meta.tools_used),
81
+ estimatedTime: meta.estimated_time || '',
82
+ auto: meta.auto,
83
+ prompt,
84
+ raw,
85
+ };
86
+ }