kalshi-trading-bot-cli 2.1.2 → 2.1.4

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.
@@ -10,20 +10,32 @@ function prefix(ctx: HelpContext): string {
10
10
  function buildTopics(ctx: HelpContext): Record<string, string> {
11
11
  const p = prefix(ctx);
12
12
  return {
13
- search: `**${p}search** — Discovery
13
+ search: `**${p}search** — Discovery (Octagon-powered when OCTAGON_API_KEY is set)
14
14
 
15
- ${p}search [theme|ticker|query] Search events by theme, ticker, or free-text
15
+ ${p}search [theme|ticker|query] Full-text market search (server-side when key is set, else local index)
16
16
  ${p}search themes List all available themes and subcategories
17
- ${p}search edge Scan all markets by Octagon model edge
17
+ ${p}search edge Edge ranking from latest Octagon run (server-side) or local cache
18
18
  ${p}search edge --min-edge 30 Markets with ≥30pp edge
19
19
  ${p}search edge --limit 50 Top 50 results
20
20
  ${p}search edge --category crypto Filter by category
21
+ ${p}search edge --sort-by total_volume Sort: edge_pp | expected_return | total_volume | model_probability
22
+
23
+ Search flags (server-side path):
24
+ --category <name> Filter by category
25
+ --series <ticker> Filter to a series
26
+ --min-volume <n> Floor on 24h volume
27
+ --close-before <iso> Only markets closing before
28
+ --limit <n> Page size (default 30)
29
+ --sort-by <key> volume_24h | close_time | last_price (client-side sort)
30
+ --aggregate-by series Roll up results by series (calls series rollup)
31
+ --active-only Drop non-active markets (defensive; the live universe is active by default)
21
32
 
22
33
  Examples:
23
34
  ${p}search crypto
24
- ${p}search crypto:btc
25
- ${p}search "bitcoin price"
26
- ${p}search edge --min-edge 30 --category crypto`,
35
+ ${p}search "bitcoin price" --min-volume 10000
36
+ ${p}search edge --min-edge 30 --category crypto
37
+
38
+ Tip: ${p}similar gives semantic match (catches "Bitcoin pierce six figures" ↔ "BTC > $100k").`,
27
39
 
28
40
  portfolio: `**${p}portfolio** — Account state
29
41
 
@@ -125,6 +137,219 @@ ${p}init Launch the TUI with the setup wizard open
125
137
 
126
138
  ${p}help Show all commands
127
139
  ${p}help <command> Show detailed help for a command`,
140
+
141
+ similar: `**${p}similar** — Semantic market search (Octagon-powered)
142
+
143
+ ${p}similar <ticker> Markets near this ticker by embedding distance
144
+ ${p}similar -q "free-text query" Markets matching free-text intent (server-side embed)
145
+ ${p}similar <ticker> --top-k 25 Return top-25 nearest neighbors
146
+ ${p}similar -q "..." --category crypto --min-volume 10000 --close-before 2026-08-19T00:00:00Z
147
+
148
+ Flags:
149
+ --top-k <n> Number of neighbors (default 25, max 100)
150
+ --category <name> Restrict to a Kalshi category
151
+ --min-volume <n> Floor on 24h volume
152
+ --close-before <iso> Only markets closing before this timestamp
153
+ --json JSON output
154
+
155
+ Catches matches keyword search misses — "Will Bitcoin pierce six figures" ↔ "BTC over $100k".`,
156
+
157
+ clusters: `**${p}clusters** — Browse Octagon clusters (thematic + behavioral)
158
+
159
+ ${p}clusters List thematic clusters with sample titles
160
+ ${p}clusters --label fed Filter by label substring
161
+ ${p}clusters <id> Show markets in cluster
162
+ ${p}clusters --behavioral List behavioral clusters (mean return + vol)
163
+ ${p}clusters <id> --behavioral Members of a behavioral cluster
164
+ ${p}clusters --ranked Rank clusters by historical basket return
165
+ ${p}clusters --ranked --timeframe 1y --min-return 0.20 --top-k 5
166
+
167
+ Flags:
168
+ --behavioral Use behavioral clustering (30-day return vectors)
169
+ --label <substring> Case-insensitive label filter
170
+ --ranked Score clusters by equal-weight basket return
171
+ --timeframe <1w|1m|3m|6m|1y> Window for --ranked (default 1y)
172
+ --min-return <n> Minimum total_return to include (e.g. 0.20)
173
+ --top-k <n> Basket size per cluster for --ranked (default 5)
174
+ --limit <n> Max clusters to evaluate
175
+ --json JSON output`,
176
+
177
+ peers: `**${p}peers** — Find markets in the same cluster as a ticker
178
+
179
+ ${p}peers <ticker> Thematic cluster peers (default)
180
+ ${p}peers <ticker> --behavioral Behavioral cluster peers
181
+ ${p}peers <ticker> --limit 50 Up to 50 peers (excluding anchor)
182
+ ${p}peers <ticker> --show-cluster Just show which clusters this ticker belongs to
183
+
184
+ Flags:
185
+ --behavioral Use behavioral clusters instead of thematic
186
+ --limit <n> Number of peers to return (default 50)
187
+ --show-cluster Print cluster membership only (no peer list)
188
+ --json JSON output`,
189
+
190
+ correlate: `**${p}correlate** — Pairwise correlation matrix over close-price candles
191
+
192
+ ${p}correlate <ticker1> <ticker2> [...] Pearson correlation across 2-100 tickers
193
+ ${p}correlate --tickers KX-A,KX-B,KX-C Same, comma-separated
194
+ ${p}correlate KX-A KX-B --window-days 90 90-day lookback
195
+ ${p}correlate KX-A KX-B --sides yes,no Side-aware: corr(YES_A, NO_B) flips sign
196
+ ${p}correlate KX-A KX-B --cells Per-cell detail (overlap_count + reason)
197
+
198
+ Flags:
199
+ --window-days <n> Lookback (1-730, default 30; auto interval picks 1d if >=90)
200
+ --correlation-interval <1h|1d> Override bin size
201
+ --tickers <csv> Alternative to positional args
202
+ --sides yes,no,yes Per-ticker side (same length as tickers); default all yes
203
+ --cells Include per-cell detail (overlap_count, reason)
204
+ --json JSON output (matrix + ranked_pairs + cells_detail)
205
+
206
+ Output ranks pairs ascending by correlation — most-uncorrelated first.`,
207
+
208
+ events: `**${p}events** — Octagon event rollups (event ↔ outcome ladder)
209
+
210
+ ${p}events List events sorted by total_volume
211
+ ${p}events --category Politics Filter by series_category
212
+ ${p}events --min-volume 10000 Volume floor
213
+ ${p}events --limit 25 Page size (default 50)
214
+ ${p}events KXFEDCHAIRNOM-29 Drill into one event: outcome probabilities + per-contract edge
215
+
216
+ Flags:
217
+ --category <name> Filter by series_category (case-insensitive substring)
218
+ --min-volume <n> Floor on total_volume
219
+ --limit <n> Page size (default 50)
220
+ --json JSON envelope output
221
+
222
+ Each event is a multi-market Kalshi question (e.g. "Who will Trump nominate as Fed Chair?")
223
+ with one binary sub-market per outcome (Kevin Warsh, Judy Shelton, ...).
224
+ Octagon supplies a model_probability per outcome so you can rank contracts by edge.`,
225
+
226
+ series: `**${p}series** — Series-level rollups over the Kalshi universe
227
+
228
+ ${p}series List series with 24h vol, market count, dominant category
229
+ ${p}series list --min-volume 10000 Liquidity filter
230
+ ${p}series list --category Crypto Filter by category
231
+ ${p}series KXBTCD Drill in: all sub-markets sorted by volume
232
+ ${p}series search "bitcoin" Keyword search → rolled up by series
233
+ ${p}series candles KXBTCD --timeframe 3m Series NAV = equal-weight basket of top sub-markets
234
+ ${p}series events KXIPO List events in a series (e.g. KXIPO → KXIPO-26)
235
+
236
+ Flags:
237
+ --min-volume <n> Floor on 24h volume per series
238
+ --category <name> Filter by category
239
+ --limit <n> Page size (default 50)
240
+ --timeframe <1w|1m|3m|6m|1y> Candle window (default 1y; for "series candles")
241
+ --top-k <n> Sub-markets to include in series NAV basket (default 20)
242
+ --series <prefix> Filter list by series-ticker prefix (e.g. KXBTC)
243
+ --json JSON output
244
+
245
+ A series is the Kalshi grouping above individual markets — KXBTCD is the BTC
246
+ strike ladder, with hundreds of sub-markets like KXBTCD-26DEC31-T100000.
247
+ Series list is now a single server-side call (was 25 paginated calls).`,
248
+
249
+ catalysts: `**${p}catalysts** — Upcoming Kalshi market closes grouped by week
250
+
251
+ ${p}catalysts upcoming Next 30 days
252
+ ${p}catalysts upcoming --days 7 Next week
253
+ ${p}catalysts upcoming --days 14 --min-volume 5000 --category Politics
254
+ ${p}catalysts upcoming --limit 10 Up to 10 markets per week shown
255
+
256
+ Flags:
257
+ --days <n> Lookback window (default 30)
258
+ --min-volume <n> Floor on 24h volume
259
+ --category <name> Filter by category
260
+ --limit <n> Top-N markets per week (default 8)
261
+ --json JSON output
262
+
263
+ Use for catalyst-calendar planning: see which weeks have major Kalshi
264
+ resolutions cluster up so you can position before catalyst risk.`,
265
+
266
+ themes: `**${p}themes** — Editorial narrative registry (curated theme buckets)
267
+
268
+ ${p}themes List registered editorial themes
269
+ ${p}themes import Seed from data/themes_seo.json (25 starter themes)
270
+ ${p}themes import <path> Import from a custom JSON file
271
+ ${p}themes export <path> Export current registry
272
+ ${p}themes show "Iran Escalation" Drill into one theme
273
+ ${p}themes create "My Theme" --tickers KXA,KXB --label "..." [--min-volume N]
274
+ ${p}themes delete "My Theme"
275
+ ${p}themes add-series "My Theme" KXBTCD,KXETHD
276
+ ${p}themes remove-series "My Theme" KXBTCD
277
+ ${p}themes set-search-volume "My Theme" 100000
278
+ ${p}themes report Dashboard: 25-theme grid with SEO + liquidity
279
+ ${p}themes audit Flag dead themes (high SEO + zero volume)
280
+ ${p}themes overlap Cross-theme dedupe report
281
+
282
+ Editorial themes are narrative buckets you curate (e.g. "AI Race Milestones",
283
+ "Iran Escalation") — distinct from Octagon's ML clusters. Each theme maps to a
284
+ list of Kalshi series and an optional monthly search-volume estimate.
285
+
286
+ Flags:
287
+ --label <desc> Set description on create
288
+ --min-volume <n> Set search_volume on create (poorly named — improve later)
289
+ --tickers <csv> Comma-separated series on create
290
+ --json JSON output
291
+
292
+ Legacy: ${p}search themes still lists Kalshi category labels (the pre-registry view).`,
293
+
294
+ basket: `**${p}basket** — Build, backtest, and size diversified baskets
295
+
296
+ ${p}basket build [universe filters] [-n N] [--max-per-cluster M] [--max-corr X] [--bankroll $ --kelly K --probs ...]
297
+ ${p}basket backtest --tickers KX-A,KX-B --weights 0.6,0.4 --timeframe 1y
298
+ ${p}basket candles --tickers KX-A,KX-B --timeframe 6m
299
+ ${p}basket size --bankroll 1000 --kelly 0.25 --probs KX-A:0.62,KX-B:0.55 [--side yes|no]
300
+
301
+ Validate flags:
302
+ --tickers KX-A,KX-B Validate explicit tickers (equal-stake split)
303
+ --probs KX-A:yes:170,KX-B:no:160 Per-leg ticker:side:stake
304
+ --theme <name> Resolve from editorial registry
305
+ --bankroll <usd> Used to compute max_leg_pct + warnings
306
+ --window-days <n> Correlation lookback (default 30)
307
+ --max-corr <-1..1> Soft threshold for correlation warning
308
+
309
+ Build flags (universe + diversification):
310
+ --category <name> Restrict candidate pool by category
311
+ --series <ticker> Restrict to a series
312
+ --min-volume <n> Volume floor for candidates
313
+ --close-before <iso> Only markets closing before
314
+ --label <csv> Restrict cluster labels (substring match, comma-separated)
315
+ -q "<text>" Anchor candidate pool by free-text intent (semantic)
316
+ --ticker <ticker> Anchor candidate pool by ticker (semantic)
317
+ --tickers KX-A,KX-B Explicit candidate pool (universe.market_tickers)
318
+ --theme <name> Resolve theme registry → explicit candidate pool
319
+ --auto-probs Auto-fetch model probabilities (markets/edge)
320
+ and use Kelly sizing
321
+ -n <n> Number of legs (1-20)
322
+ --max-per-cluster <n> Cap legs per thematic cluster
323
+ --max-corr <x> Pairwise correlation cap (-1 to 1)
324
+ --limit <n> Candidate pool size (2-200)
325
+ --window-days <n> Correlation window (7-365)
326
+
327
+ Sizing flags (build & size):
328
+ --bankroll <usd> Required for Kelly sizing
329
+ --kelly <fraction> Kelly multiplier 0-1 (default 0.25)
330
+ --probs KX-A:0.62,KX-B:0.55 Model probabilities per ticker (manual)
331
+ --auto-probs --tickers KX-A,KX-B Auto-fetch via POST /markets/edge
332
+ --auto-probs --theme <name> Resolve theme + auto-fetch probabilities
333
+ --side <yes|no> Default leg side for "basket size" (default yes)
334
+
335
+ Backtest/candles flags:
336
+ --tickers <csv> Tickers (required if --theme is absent)
337
+ --theme <name> Resolve from editorial registry (top market per series)
338
+ --weights <csv> Optional weights, same length as tickers
339
+ --timeframe <1w|1m|3m|6m|1y> Window/bin size
340
+
341
+ Common:
342
+ --json JSON output
343
+
344
+ Recipes:
345
+ ${p}basket build --category crypto --min-volume 10000 -n 8 --max-per-cluster 2 --max-corr 0.6
346
+ ${p}basket build --label fed,cpi,fomc,gdp,jobs -n 5 --max-per-cluster 1 --max-corr 0.4
347
+ ${p}basket build --tickers KX-A,KX-B,KX-C -n 2 --max-corr 0.5 # explicit candidate pool
348
+ ${p}basket build --theme "Iran Escalation" -n 3 --max-per-cluster 1 --auto-probs --bankroll 1000
349
+ ${p}basket backtest --tickers KX-A,KX-B,KX-C --weights 0.4,0.4,0.2 --timeframe 1y
350
+ ${p}basket size --auto-probs --theme "Iran Escalation" --bankroll 1000 --kelly 0.25
351
+ ${p}basket validate --theme "Iran Escalation" --bankroll 1000 # sanity-check before placing
352
+ ${p}basket validate --tickers KX-A,KX-B --bankroll 1000 --max-corr 0.5`,
128
353
  };
129
354
  }
130
355
 
@@ -139,14 +364,44 @@ Quick start:
139
364
  kalshi watch --theme crypto Continuous scan across a theme
140
365
 
141
366
  Discovery:
142
- search [theme|ticker|query] Find markets by keyword or theme
143
- search --refresh <query> Force index rebuild then search
144
- search themes List all themes and subcategories
145
- search edge [--min-edge N] Scan all markets by Octagon model edge
367
+ search [theme|ticker|query] Find markets (Octagon when key set, else local)
368
+ search --sort-by volume_24h Top-N by liquidity
369
+ search --aggregate-by series Roll up results to series level
370
+ search themes (Legacy) Kalshi category labels
371
+ search edge [--min-edge N] Edge ranking (Octagon when key set, else local)
372
+ similar <ticker> Semantic neighbors (embedding distance)
373
+ similar -q "free text" Semantic search by natural-language query
374
+ clusters [--label X] Browse thematic clusters
375
+ clusters <id> List markets in a cluster
376
+ clusters --behavioral Behavioral clusters (30-day return vectors)
377
+ clusters --ranked Rank clusters by historical basket return
378
+ peers <ticker> Find markets in the same cluster
379
+ events Octagon events (event ↔ outcome ladder)
380
+ events <event_ticker> Drill into one event's outcome probabilities
381
+ series Series rollup with 24h vol, market count
382
+ series <SERIES> Sub-markets in one series
383
+ series candles <SERIES> Series NAV (basket of top sub-markets)
384
+ catalysts upcoming --days 30 Markets closing soon, grouped by week
146
385
  watch <ticker> Live price/orderbook feed
147
386
  watch --theme <theme> Continuous theme scan (Ctrl+C to stop)
148
387
  watch --refresh Force index rebuild before watching
149
388
 
389
+ Editorial themes (narrative registry):
390
+ themes List registered editorial themes
391
+ themes import Seed from data/themes_seo.json (25 starter themes)
392
+ themes show <name> Drill into one theme
393
+ themes report 25-theme dashboard with SEO + liquidity
394
+ themes audit Flag dead themes (high SEO + zero volume)
395
+ themes overlap Cross-theme dedupe report
396
+ themes create/delete/add-series/remove-series/set-search-volume/export
397
+
398
+ Portfolio construction:
399
+ correlate <t1> <t2> [...] Pairwise Pearson correlation matrix
400
+ basket build [filters] -n N Diversified basket with cluster + correlation caps
401
+ basket backtest --tickers ... NAV summary with Sharpe, max DD, win rate
402
+ basket size --bankroll $ --probs ... Fractional Kelly sizing for picked legs
403
+ basket candles --tickers ... OHLC bars for a weighted basket NAV
404
+
150
405
  Analysis & Trading:
151
406
  analyze <ticker> Full report: edge, drivers, Kelly sizing
152
407
  analyze <ticker> --refresh Force fresh Octagon report
@@ -185,14 +440,44 @@ Quick start:
185
440
  /watch --theme crypto Continuous scan across a theme
186
441
 
187
442
  Discovery:
188
- /search [theme|ticker|query] Find markets by keyword or theme
189
- /search --refresh <query> Force index rebuild then search
190
- /search themes List all themes and subcategories
191
- /search edge [--min-edge N] Scan all markets by Octagon model edge
443
+ /search [theme|ticker|query] Find markets (Octagon when key set, else local)
444
+ /search --sort-by volume_24h Top-N by liquidity
445
+ /search --aggregate-by series Roll up results to series level
446
+ /search themes (Legacy) Kalshi category labels
447
+ /search edge [--min-edge N] Edge ranking (Octagon when key set, else local)
448
+ /similar <ticker> Semantic neighbors (embedding distance)
449
+ /similar -q "free text" Semantic search by natural-language query
450
+ /clusters [--label X] Browse thematic clusters
451
+ /clusters <id> List markets in a cluster
452
+ /clusters --behavioral Behavioral clusters (30-day return vectors)
453
+ /clusters --ranked Rank clusters by historical basket return
454
+ /peers <ticker> Find markets in the same cluster
455
+ /events Octagon events (event ↔ outcome ladder)
456
+ /events <event_ticker> Drill into one event's outcome probabilities
457
+ /series Series rollup with 24h vol, market count
458
+ /series <SERIES> Sub-markets in one series
459
+ /series candles <SERIES> Series NAV (basket of top sub-markets)
460
+ /catalysts upcoming --days 30 Markets closing soon, grouped by week
192
461
  /watch <ticker> Live price/orderbook feed
193
462
  /watch --theme <theme> Continuous theme scan (Esc to stop)
194
463
  /watch --refresh Force index rebuild before watching
195
464
 
465
+ Editorial themes (narrative registry):
466
+ /themes List registered editorial themes
467
+ /themes import Seed from data/themes_seo.json (25 starter themes)
468
+ /themes show <name> Drill into one theme
469
+ /themes report 25-theme dashboard with SEO + liquidity
470
+ /themes audit Flag dead themes (high SEO + zero volume)
471
+ /themes overlap Cross-theme dedupe report
472
+ /themes create/delete/add-series/remove-series/set-search-volume/export
473
+
474
+ Portfolio construction:
475
+ /correlate <t1> <t2> [...] Pairwise Pearson correlation matrix
476
+ /basket build [filters] -n N Diversified basket with cluster + correlation caps
477
+ /basket backtest --tickers ... NAV summary with Sharpe, max DD, win rate
478
+ /basket size --bankroll $ --probs ... Fractional Kelly sizing for picked legs
479
+ /basket candles --tickers ... OHLC bars for a weighted basket NAV
480
+
196
481
  Analysis:
197
482
  /backtest Model accuracy scorecard + live edge scanner
198
483
  /analyze <ticker> Full report: edge, drivers, Kelly sizing
@@ -16,7 +16,10 @@ function defaultArgs(overrides: Partial<ParsedArgs>): ParsedArgs {
16
16
  subcommand: 'chat', positionalArgs: [], json: false,
17
17
  live: false, refresh: false, report: false, dryRun: false,
18
18
  verbose: false, performance: false, resolved: false,
19
- unresolved: false, parseErrors: [],
19
+ unresolved: false,
20
+ behavioral: false, ranked: false, showCluster: false,
21
+ activeOnly: false, cells: false, autoProbs: false,
22
+ parseErrors: [],
20
23
  ...overrides,
21
24
  };
22
25
  }
@@ -27,6 +30,16 @@ import { reviewPortfolio, formatReviewHuman } from './review.js';
27
30
  import { buildHelp, validateTradeArgs } from './help.js';
28
31
  import { fetchMarketQuote } from './helpers.js';
29
32
  import { trackEvent } from '../utils/telemetry.js';
33
+ import { parseArgs } from './parse-args.js';
34
+ import { handleSimilar, formatSimilarHuman } from './similar.js';
35
+ import { handleClusters, formatClustersHuman } from './clusters.js';
36
+ import { handlePeers, formatPeersHuman } from './peers.js';
37
+ import { handleCorrelate, formatCorrelationHuman } from './correlate.js';
38
+ import { handleBasket, formatBasketHuman } from './basket.js';
39
+ import { handleEvents, formatEventsHuman } from './events.js';
40
+ import { handleSeries, formatSeriesHuman } from './series.js';
41
+ import { handleEditorialThemes, formatEditorialThemesHuman } from './editorial-themes.js';
42
+ import { handleCatalysts, formatCatalystsHuman } from './catalysts.js';
30
43
 
31
44
  export interface CommandResult {
32
45
  output: string;
@@ -49,7 +62,28 @@ export async function handleSlashCommand(input: string): Promise<CommandResult |
49
62
  const parts = trimmed.slice(1).trim().split(/\s+/);
50
63
  const command = parts[0]?.toLowerCase();
51
64
  const args = parts.slice(1);
52
- trackEvent('slash_command', { command: command ?? '' });
65
+ // Enrich Octagon-Kalshi commands with subview/mode flags so analytics can
66
+ // distinguish e.g. "basket build" vs "basket backtest", or thematic vs
67
+ // behavioral clusters. Outer command name is always tracked.
68
+ const slashMeta: Record<string, string | boolean> = { command: command ?? '' };
69
+ if (command === 'basket') {
70
+ const sub = args[0]?.toLowerCase();
71
+ if (sub === 'build' || sub === 'backtest' || sub === 'size' || sub === 'candles') {
72
+ slashMeta.subview = sub;
73
+ }
74
+ slashMeta.kelly_sizing = args.includes('--bankroll');
75
+ } else if (command === 'clusters') {
76
+ slashMeta.behavioral = args.includes('--behavioral');
77
+ slashMeta.ranked = args.includes('--ranked');
78
+ } else if (command === 'peers') {
79
+ slashMeta.behavioral = args.includes('--behavioral');
80
+ slashMeta.show_cluster = args.includes('--show-cluster');
81
+ } else if (command === 'similar') {
82
+ slashMeta.anchor = args.includes('-q') || args.includes('--query') ? 'query' : 'ticker';
83
+ } else if (command === 'search') {
84
+ slashMeta.remote = !!process.env.OCTAGON_API_KEY;
85
+ }
86
+ trackEvent('slash_command', slashMeta);
53
87
 
54
88
  switch (command) {
55
89
  case 'help': {
@@ -79,11 +113,24 @@ export async function handleSlashCommand(input: string): Promise<CommandResult |
79
113
  case 'cancel':
80
114
  return handleCancel(args[0]);
81
115
 
82
- // ─── /search themes (inline) ─────────────────────────────────────
83
- // Note: /search <non-themes> is handled in cli.ts via browseController
116
+ // ─── /themes (editorial registry) ────────────────────────────────
117
+ // The bare /themes call now hits the editorial-themes registry. Legacy
118
+ // "Kalshi category labels" is still reachable via /search themes.
84
119
  case 'themes': {
85
- const resp = await handleThemes(defaultArgs({ subcommand: 'themes' }));
86
- return { output: formatThemesHuman(resp.data) };
120
+ const parsed = parseArgs(['themes', ...args]);
121
+ const sub = parsed.positionalArgs[0]?.toLowerCase();
122
+ const isAsync = sub === 'report' || sub === 'audit';
123
+ if (!isAsync) {
124
+ const resp = await handleEditorialThemes(parsed);
125
+ return { output: resp.ok ? formatEditorialThemesHuman(resp.data) : (resp.error?.message ?? 'themes failed') };
126
+ }
127
+ return {
128
+ output: `Building themes ${sub} (this pulls the full Kalshi universe)...`,
129
+ asyncFollowUp: async () => {
130
+ const resp = await handleEditorialThemes(parsed);
131
+ return resp.ok ? formatEditorialThemesHuman(resp.data) : (resp.error?.message ?? 'themes failed');
132
+ },
133
+ };
87
134
  }
88
135
 
89
136
  // ─── /analyze ────────────────────────────────────────────────────
@@ -126,6 +173,90 @@ export async function handleSlashCommand(input: string): Promise<CommandResult |
126
173
  };
127
174
  }
128
175
 
176
+ // ─── Octagon Kalshi search/clusters/basket ───────────────────────
177
+ case 'similar': {
178
+ const parsed = parseArgs(['similar', ...args]);
179
+ return {
180
+ output: 'Querying Octagon for similar markets...',
181
+ asyncFollowUp: async () => {
182
+ const resp = await handleSimilar(parsed);
183
+ return resp.ok ? formatSimilarHuman(resp.data) : (resp.error?.message ?? 'similar failed');
184
+ },
185
+ };
186
+ }
187
+ case 'clusters': {
188
+ const parsed = parseArgs(['clusters', ...args]);
189
+ return {
190
+ output: 'Querying Octagon for clusters...',
191
+ asyncFollowUp: async () => {
192
+ const resp = await handleClusters(parsed);
193
+ return resp.ok ? formatClustersHuman(resp.data) : (resp.error?.message ?? 'clusters failed');
194
+ },
195
+ };
196
+ }
197
+ case 'peers': {
198
+ const parsed = parseArgs(['peers', ...args]);
199
+ return {
200
+ output: 'Querying Octagon for cluster peers...',
201
+ asyncFollowUp: async () => {
202
+ const resp = await handlePeers(parsed);
203
+ return resp.ok ? formatPeersHuman(resp.data) : (resp.error?.message ?? 'peers failed');
204
+ },
205
+ };
206
+ }
207
+ case 'correlate': {
208
+ const parsed = parseArgs(['correlate', ...args]);
209
+ return {
210
+ output: 'Computing correlation matrix...',
211
+ asyncFollowUp: async () => {
212
+ const resp = await handleCorrelate(parsed);
213
+ return resp.ok ? formatCorrelationHuman(resp.data) : (resp.error?.message ?? 'correlate failed');
214
+ },
215
+ };
216
+ }
217
+ case 'basket': {
218
+ const parsed = parseArgs(['basket', ...args]);
219
+ const sub = parsed.positionalArgs[0] ?? '';
220
+ return {
221
+ output: `Running basket ${sub || '(no subcommand)'}...`,
222
+ asyncFollowUp: async () => {
223
+ const resp = await handleBasket(parsed);
224
+ return resp.ok ? formatBasketHuman(resp.data) : (resp.error?.message ?? 'basket failed');
225
+ },
226
+ };
227
+ }
228
+ case 'events': {
229
+ const parsed = parseArgs(['events', ...args]);
230
+ return {
231
+ output: 'Querying Octagon events...',
232
+ asyncFollowUp: async () => {
233
+ const resp = await handleEvents(parsed);
234
+ return resp.ok ? formatEventsHuman(resp.data) : (resp.error?.message ?? 'events failed');
235
+ },
236
+ };
237
+ }
238
+ case 'series': {
239
+ const parsed = parseArgs(['series', ...args]);
240
+ const sub = parsed.positionalArgs[0]?.toLowerCase();
241
+ return {
242
+ output: sub === 'candles' ? 'Building series NAV...' : 'Rolling up Kalshi series...',
243
+ asyncFollowUp: async () => {
244
+ const resp = await handleSeries(parsed);
245
+ return resp.ok ? formatSeriesHuman(resp.data) : (resp.error?.message ?? 'series failed');
246
+ },
247
+ };
248
+ }
249
+ case 'catalysts': {
250
+ const parsed = parseArgs(['catalysts', ...args]);
251
+ return {
252
+ output: 'Loading upcoming catalysts...',
253
+ asyncFollowUp: async () => {
254
+ const resp = await handleCatalysts(parsed);
255
+ return resp.ok ? formatCatalystsHuman(resp.data) : (resp.error?.message ?? 'catalysts failed');
256
+ },
257
+ };
258
+ }
259
+
129
260
  case 'config':
130
261
  // Fall through to agent — better handled by the LLM
131
262
  return null;