kalshi-trading-bot-cli 2.1.3 → 2.1.5

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,33 @@ 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 this timestamp
28
+ --days-to-close <n> Shortcut: only markets closing in the next N days
29
+ --limit <n> Page size (default 30)
30
+ --sort-by <key> volume_24h | close_time | last_price (server-side sort)
31
+ --aggregate-by series Roll up results by series (calls series rollup)
32
+ --active-only Drop non-active markets (defensive; the live universe is active by default)
21
33
 
22
34
  Examples:
23
35
  ${p}search crypto
24
- ${p}search crypto:btc
25
- ${p}search "bitcoin price"
26
- ${p}search edge --min-edge 30 --category crypto`,
36
+ ${p}search "bitcoin price" --min-volume 10000
37
+ ${p}search edge --min-edge 30 --category crypto
38
+
39
+ Tip: ${p}similar gives semantic match (catches "Bitcoin pierce six figures" ↔ "BTC > $100k").`,
27
40
 
28
41
  portfolio: `**${p}portfolio** — Account state
29
42
 
@@ -39,12 +52,22 @@ Flags:
39
52
 
40
53
  analyze: `**${p}analyze** — Deep market analysis
41
54
 
42
- ${p}analyze <ticker> Full analysis: edge, drivers, catalysts, Kelly sizing
43
- ${p}analyze <ticker> ${ctx === 'cli' ? '--' : ''}refresh Force fresh Octagon report
44
- ${ctx === 'cli' ? `
55
+ ${p}analyze <ticker> Full analysis: edge, drivers, catalysts, Kelly sizing
56
+ ${p}analyze <ticker> ${ctx === 'cli' ? '--' : ''}refresh Force fresh Octagon report
57
+
58
+ Batch mode (one Octagon round-trip instead of N):
59
+ ${p}analyze KX-A KX-B KX-C Edge readout across 2-100 tickers
60
+ ${p}analyze --tickers KX-A,KX-B,KX-C Same, comma-separated
61
+ ${p}analyze KX-A KX-B KX-C --json For pipelines / scripting
62
+
63
+ The batch mode hits POST /kalshi/markets/edge in one call and returns
64
+ model_probability, market_probability, edge_pp, expected_return per ticker.
65
+ Use single-ticker mode when you need the full deep-analysis pipeline
66
+ (drivers, catalysts, Kelly sizing, risk gate).${ctx === 'cli' ? `
67
+
45
68
  Legacy aliases (still work):
46
- ${p}edge [--ticker X] Edge history / snapshots (default: last 24h)
47
- ${p}edge --since <date> Edges since date (e.g. 2026-03-01)` : ''}`,
69
+ ${p}edge [--ticker X] Edge history / snapshots (default: last 24h)
70
+ ${p}edge --since <date> Edges since date (e.g. 2026-03-01)` : ''}`,
48
71
 
49
72
  watch: `**${p}watch** — Live monitoring
50
73
 
@@ -125,6 +148,247 @@ ${p}init Launch the TUI with the setup wizard open
125
148
 
126
149
  ${p}help Show all commands
127
150
  ${p}help <command> Show detailed help for a command`,
151
+
152
+ scripting: `**Scripting & Parallel Use** — for agents, pipelines, and parallel invocations
153
+
154
+ The \`bunx kalshi-trading-bot-cli@latest …\` form is convenient for one-off use
155
+ but has two gotchas under scripting:
156
+
157
+ 1. Bun's install chatter ("Resolving dependencies", "Saved lockfile") leaks
158
+ into stdout before our CLI runs, corrupting JSON pipelines.
159
+ 2. Parallel \`bunx\` invocations race on the install cache and fail with
160
+ "Failed to link …: EEXIST" / "could not determine executable".
161
+ See oven-sh/bun#12917 for upstream status.
162
+
163
+ **Recommended for scripts and agents:**
164
+
165
+ bun add -g kalshi-trading-bot-cli # install once; emits no chatter on subsequent runs
166
+ parallel -j 30 'kalshi analyze {} --json' ::: TICKER1 TICKER2 …
167
+
168
+ **If you must use bunx:**
169
+
170
+ bunx --silent kalshi-trading-bot-cli@latest analyze KX-A --json
171
+ ^^^^^^^^ suppresses install chatter; keeps our stdout clean
172
+
173
+ For parallel bunx, pre-warm the cache serially before fanning out:
174
+
175
+ bunx --silent kalshi-trading-bot-cli@latest --version # one-shot, warms cache
176
+ parallel -j 30 'bunx --silent kalshi-trading-bot-cli@latest analyze {} --json' ::: …
177
+
178
+ See README → Scripting & Parallel Use for the full picture.`,
179
+
180
+ similar: `**${p}similar** — Semantic market search (Octagon-powered)
181
+
182
+ ${p}similar <ticker> Markets near this ticker by embedding distance
183
+ ${p}similar -q "free-text query" Markets matching free-text intent (server-side embed)
184
+ ${p}similar <ticker> --top-k 25 Return top-25 nearest neighbors
185
+ ${p}similar -q "..." --category crypto --min-volume 10000 --close-before 2026-08-19T00:00:00Z
186
+
187
+ Flags:
188
+ --top-k <n> Number of neighbors (default 25, max 100)
189
+ --category <name> Restrict to a Kalshi category
190
+ --min-volume <n> Floor on 24h volume
191
+ --close-before <iso> Only markets closing before this timestamp
192
+ --json JSON output
193
+
194
+ Catches matches keyword search misses — "Will Bitcoin pierce six figures" ↔ "BTC over $100k".`,
195
+
196
+ clusters: `**${p}clusters** — Browse Octagon clusters (thematic + behavioral)
197
+
198
+ ${p}clusters List thematic clusters with sample titles
199
+ ${p}clusters --label fed Filter by label substring
200
+ ${p}clusters <id> Show markets in cluster
201
+ ${p}clusters --behavioral List behavioral clusters (mean return + vol)
202
+ ${p}clusters <id> --behavioral Members of a behavioral cluster
203
+ ${p}clusters --ranked Rank clusters by historical basket return
204
+ ${p}clusters --ranked --timeframe 1y --min-return 0.20 --top-k 5
205
+
206
+ Flags:
207
+ --behavioral Use behavioral clustering (30-day return vectors)
208
+ --label <substring> Case-insensitive label filter
209
+ --ranked Score clusters by equal-weight basket return
210
+ --timeframe <1w|1m|3m|6m|1y> Window for --ranked (default 1y)
211
+ --min-return <n> Minimum total_return to include (e.g. 0.20)
212
+ --top-k <n> Basket size per cluster for --ranked (default 5)
213
+ --limit <n> Max clusters to evaluate
214
+ --json JSON output`,
215
+
216
+ peers: `**${p}peers** — Find markets in the same cluster as a ticker
217
+
218
+ ${p}peers <ticker> Thematic cluster peers (default)
219
+ ${p}peers <ticker> --behavioral Behavioral cluster peers
220
+ ${p}peers <ticker> --limit 50 Up to 50 peers (excluding anchor)
221
+ ${p}peers <ticker> --show-cluster Just show which clusters this ticker belongs to
222
+
223
+ Flags:
224
+ --behavioral Use behavioral clusters instead of thematic
225
+ --limit <n> Number of peers to return (default 50)
226
+ --show-cluster Print cluster membership only (no peer list)
227
+ --json JSON output`,
228
+
229
+ correlate: `**${p}correlate** — Pairwise correlation matrix over close-price candles
230
+
231
+ ${p}correlate <ticker1> <ticker2> [...] Pearson correlation across 2-100 tickers
232
+ ${p}correlate --tickers KX-A,KX-B,KX-C Same, comma-separated
233
+ ${p}correlate KX-A KX-B --window-days 90 90-day lookback
234
+ ${p}correlate KX-A KX-B --sides yes,no Side-aware: corr(YES_A, NO_B) flips sign
235
+ ${p}correlate KX-A KX-B --cells Per-cell detail (overlap_count + reason)
236
+
237
+ Flags:
238
+ --window-days <n> Lookback (1-730, default 30; auto interval picks 1d if >=90)
239
+ --correlation-interval <1h|1d> Override bin size
240
+ --tickers <csv> Alternative to positional args
241
+ --sides yes,no,yes Per-ticker side (same length as tickers); default all yes
242
+ --cells Include per-cell detail (overlap_count, reason)
243
+ --json JSON output (matrix + ranked_pairs + cells_detail)
244
+
245
+ Output ranks pairs ascending by correlation — most-uncorrelated first.`,
246
+
247
+ events: `**${p}events** — Octagon event rollups (event ↔ outcome ladder)
248
+
249
+ ${p}events List events sorted by total_volume
250
+ ${p}events --category Politics Filter by series_category
251
+ ${p}events --min-volume 10000 Volume floor
252
+ ${p}events --limit 25 Page size (default 50)
253
+ ${p}events KXFEDCHAIRNOM-29 Drill into one event: outcome probabilities + per-contract edge
254
+
255
+ Flags:
256
+ --category <name> Filter by series_category (case-insensitive substring)
257
+ --min-volume <n> Floor on total_volume
258
+ --limit <n> Page size (default 50)
259
+ --json JSON envelope output
260
+
261
+ Each event is a multi-market Kalshi question (e.g. "Who will Trump nominate as Fed Chair?")
262
+ with one binary sub-market per outcome (Kevin Warsh, Judy Shelton, ...).
263
+ Octagon supplies a model_probability per outcome so you can rank contracts by edge.`,
264
+
265
+ series: `**${p}series** — Series-level rollups over the Kalshi universe
266
+
267
+ ${p}series List series with 24h vol, market count, dominant category
268
+ ${p}series list --min-volume 10000 Liquidity filter
269
+ ${p}series list --category Crypto Filter by category
270
+ ${p}series KXBTCD Drill in: all sub-markets sorted by volume
271
+ ${p}series search "bitcoin" Keyword search → rolled up by series
272
+ ${p}series candles KXBTCD --timeframe 3m Series NAV = equal-weight basket of top sub-markets
273
+ ${p}series events KXIPO List events in a series (e.g. KXIPO → KXIPO-26)
274
+
275
+ Flags:
276
+ --min-volume <n> Floor on 24h volume per series
277
+ --category <name> Filter by category
278
+ --limit <n> Page size (default 50)
279
+ --timeframe <1w|1m|3m|6m|1y> Candle window (default 1y; for "series candles")
280
+ --top-k <n> Sub-markets to include in series NAV basket (default 20)
281
+ --series <prefix> Filter list by series-ticker prefix (e.g. KXBTC)
282
+ --json JSON output
283
+
284
+ A series is the Kalshi grouping above individual markets — KXBTCD is the BTC
285
+ strike ladder, with hundreds of sub-markets like KXBTCD-26DEC31-T100000.
286
+ Series list is now a single server-side call (was 25 paginated calls).`,
287
+
288
+ catalysts: `**${p}catalysts** — Upcoming Kalshi market closes grouped by week
289
+
290
+ ${p}catalysts upcoming Next 30 days
291
+ ${p}catalysts upcoming --days 7 Next week
292
+ ${p}catalysts upcoming --days 14 --min-volume 5000 --category Politics
293
+ ${p}catalysts upcoming --limit 10 Up to 10 markets per week shown
294
+
295
+ Flags:
296
+ --days <n> Lookback window (default 30)
297
+ --min-volume <n> Floor on 24h volume
298
+ --category <name> Filter by category
299
+ --limit <n> Top-N markets per week (default 8)
300
+ --json JSON output
301
+
302
+ Use for catalyst-calendar planning: see which weeks have major Kalshi
303
+ resolutions cluster up so you can position before catalyst risk.`,
304
+
305
+ themes: `**${p}themes** — Editorial narrative registry (curated theme buckets)
306
+
307
+ ${p}themes List registered editorial themes
308
+ ${p}themes import Seed from data/themes_seo.json (25 starter themes)
309
+ ${p}themes import <path> Import from a custom JSON file
310
+ ${p}themes export <path> Export current registry
311
+ ${p}themes show "Iran Escalation" Drill into one theme
312
+ ${p}themes create "My Theme" --tickers KXA,KXB --label "..." [--min-volume N]
313
+ ${p}themes delete "My Theme"
314
+ ${p}themes add-series "My Theme" KXBTCD,KXETHD
315
+ ${p}themes remove-series "My Theme" KXBTCD
316
+ ${p}themes set-search-volume "My Theme" 100000
317
+ ${p}themes report Dashboard: 25-theme grid with SEO + liquidity
318
+ ${p}themes audit Flag dead themes (high SEO + zero volume)
319
+ ${p}themes overlap Cross-theme dedupe report
320
+
321
+ Editorial themes are narrative buckets you curate (e.g. "AI Race Milestones",
322
+ "Iran Escalation") — distinct from Octagon's ML clusters. Each theme maps to a
323
+ list of Kalshi series and an optional monthly search-volume estimate.
324
+
325
+ Flags:
326
+ --label <desc> Set description on create
327
+ --min-volume <n> Set search_volume on create (poorly named — improve later)
328
+ --tickers <csv> Comma-separated series on create
329
+ --json JSON output
330
+
331
+ Legacy: ${p}search themes still lists Kalshi category labels (the pre-registry view).`,
332
+
333
+ basket: `**${p}basket** — Build, backtest, and size diversified baskets
334
+
335
+ ${p}basket build [universe filters] [-n N] [--max-per-cluster M] [--max-corr X] [--bankroll $ --kelly K --probs ...]
336
+ ${p}basket backtest --tickers KX-A,KX-B --weights 0.6,0.4 --timeframe 1y
337
+ ${p}basket candles --tickers KX-A,KX-B --timeframe 6m
338
+ ${p}basket size --bankroll 1000 --kelly 0.25 --probs KX-A:0.62,KX-B:0.55 [--side yes|no]
339
+
340
+ Validate flags:
341
+ --tickers KX-A,KX-B Validate explicit tickers (equal-stake split)
342
+ --probs KX-A:yes:170,KX-B:no:160 Per-leg ticker:side:stake
343
+ --theme <name> Resolve from editorial registry
344
+ --bankroll <usd> Used to compute max_leg_pct + warnings
345
+ --window-days <n> Correlation lookback (default 30)
346
+ --max-corr <-1..1> Soft threshold for correlation warning
347
+
348
+ Build flags (universe + diversification):
349
+ --category <name> Restrict candidate pool by category
350
+ --series <ticker> Restrict to a series
351
+ --min-volume <n> Volume floor for candidates
352
+ --close-before <iso> Only markets closing before
353
+ --label <csv> Restrict cluster labels (substring match, comma-separated)
354
+ -q "<text>" Anchor candidate pool by free-text intent (semantic)
355
+ --ticker <ticker> Anchor candidate pool by ticker (semantic)
356
+ --tickers KX-A,KX-B Explicit candidate pool (universe.market_tickers)
357
+ --theme <name> Resolve theme registry → explicit candidate pool
358
+ --auto-probs Auto-fetch model probabilities (markets/edge)
359
+ and use Kelly sizing
360
+ -n <n> Number of legs (1-20)
361
+ --max-per-cluster <n> Cap legs per thematic cluster
362
+ --max-corr <x> Pairwise correlation cap (-1 to 1)
363
+ --limit <n> Candidate pool size (2-200)
364
+ --window-days <n> Correlation window (7-365)
365
+
366
+ Sizing flags (build & size):
367
+ --bankroll <usd> Required for Kelly sizing
368
+ --kelly <fraction> Kelly multiplier 0-1 (default 0.25)
369
+ --probs KX-A:0.62,KX-B:0.55 Model probabilities per ticker (manual)
370
+ --auto-probs --tickers KX-A,KX-B Auto-fetch via POST /markets/edge
371
+ --auto-probs --theme <name> Resolve theme + auto-fetch probabilities
372
+ --side <yes|no> Default leg side for "basket size" (default yes)
373
+
374
+ Backtest/candles flags:
375
+ --tickers <csv> Tickers (required if --theme is absent)
376
+ --theme <name> Resolve from editorial registry (top market per series)
377
+ --weights <csv> Optional weights, same length as tickers
378
+ --timeframe <1w|1m|3m|6m|1y> Window/bin size
379
+
380
+ Common:
381
+ --json JSON output
382
+
383
+ Recipes:
384
+ ${p}basket build --category crypto --min-volume 10000 -n 8 --max-per-cluster 2 --max-corr 0.6
385
+ ${p}basket build --label fed,cpi,fomc,gdp,jobs -n 5 --max-per-cluster 1 --max-corr 0.4
386
+ ${p}basket build --tickers KX-A,KX-B,KX-C -n 2 --max-corr 0.5 # explicit candidate pool
387
+ ${p}basket build --theme "Iran Escalation" -n 3 --max-per-cluster 1 --auto-probs --bankroll 1000
388
+ ${p}basket backtest --tickers KX-A,KX-B,KX-C --weights 0.4,0.4,0.2 --timeframe 1y
389
+ ${p}basket size --auto-probs --theme "Iran Escalation" --bankroll 1000 --kelly 0.25
390
+ ${p}basket validate --theme "Iran Escalation" --bankroll 1000 # sanity-check before placing
391
+ ${p}basket validate --tickers KX-A,KX-B --bankroll 1000 --max-corr 0.5`,
128
392
  };
129
393
  }
130
394
 
@@ -139,14 +403,44 @@ Quick start:
139
403
  kalshi watch --theme crypto Continuous scan across a theme
140
404
 
141
405
  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
406
+ search [theme|ticker|query] Find markets (Octagon when key set, else local)
407
+ search --sort-by volume_24h Top-N by liquidity
408
+ search --aggregate-by series Roll up results to series level
409
+ search themes (Legacy) Kalshi category labels
410
+ search edge [--min-edge N] Edge ranking (Octagon when key set, else local)
411
+ similar <ticker> Semantic neighbors (embedding distance)
412
+ similar -q "free text" Semantic search by natural-language query
413
+ clusters [--label X] Browse thematic clusters
414
+ clusters <id> List markets in a cluster
415
+ clusters --behavioral Behavioral clusters (30-day return vectors)
416
+ clusters --ranked Rank clusters by historical basket return
417
+ peers <ticker> Find markets in the same cluster
418
+ events Octagon events (event ↔ outcome ladder)
419
+ events <event_ticker> Drill into one event's outcome probabilities
420
+ series Series rollup with 24h vol, market count
421
+ series <SERIES> Sub-markets in one series
422
+ series candles <SERIES> Series NAV (basket of top sub-markets)
423
+ catalysts upcoming --days 30 Markets closing soon, grouped by week
146
424
  watch <ticker> Live price/orderbook feed
147
425
  watch --theme <theme> Continuous theme scan (Ctrl+C to stop)
148
426
  watch --refresh Force index rebuild before watching
149
427
 
428
+ Editorial themes (narrative registry):
429
+ themes List registered editorial themes
430
+ themes import Seed from data/themes_seo.json (25 starter themes)
431
+ themes show <name> Drill into one theme
432
+ themes report 25-theme dashboard with SEO + liquidity
433
+ themes audit Flag dead themes (high SEO + zero volume)
434
+ themes overlap Cross-theme dedupe report
435
+ themes create/delete/add-series/remove-series/set-search-volume/export
436
+
437
+ Portfolio construction:
438
+ correlate <t1> <t2> [...] Pairwise Pearson correlation matrix
439
+ basket build [filters] -n N Diversified basket with cluster + correlation caps
440
+ basket backtest --tickers ... NAV summary with Sharpe, max DD, win rate
441
+ basket size --bankroll $ --probs ... Fractional Kelly sizing for picked legs
442
+ basket candles --tickers ... OHLC bars for a weighted basket NAV
443
+
150
444
  Analysis & Trading:
151
445
  analyze <ticker> Full report: edge, drivers, Kelly sizing
152
446
  analyze <ticker> --refresh Force fresh Octagon report
@@ -185,14 +479,44 @@ Quick start:
185
479
  /watch --theme crypto Continuous scan across a theme
186
480
 
187
481
  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
482
+ /search [theme|ticker|query] Find markets (Octagon when key set, else local)
483
+ /search --sort-by volume_24h Top-N by liquidity
484
+ /search --aggregate-by series Roll up results to series level
485
+ /search themes (Legacy) Kalshi category labels
486
+ /search edge [--min-edge N] Edge ranking (Octagon when key set, else local)
487
+ /similar <ticker> Semantic neighbors (embedding distance)
488
+ /similar -q "free text" Semantic search by natural-language query
489
+ /clusters [--label X] Browse thematic clusters
490
+ /clusters <id> List markets in a cluster
491
+ /clusters --behavioral Behavioral clusters (30-day return vectors)
492
+ /clusters --ranked Rank clusters by historical basket return
493
+ /peers <ticker> Find markets in the same cluster
494
+ /events Octagon events (event ↔ outcome ladder)
495
+ /events <event_ticker> Drill into one event's outcome probabilities
496
+ /series Series rollup with 24h vol, market count
497
+ /series <SERIES> Sub-markets in one series
498
+ /series candles <SERIES> Series NAV (basket of top sub-markets)
499
+ /catalysts upcoming --days 30 Markets closing soon, grouped by week
192
500
  /watch <ticker> Live price/orderbook feed
193
501
  /watch --theme <theme> Continuous theme scan (Esc to stop)
194
502
  /watch --refresh Force index rebuild before watching
195
503
 
504
+ Editorial themes (narrative registry):
505
+ /themes List registered editorial themes
506
+ /themes import Seed from data/themes_seo.json (25 starter themes)
507
+ /themes show <name> Drill into one theme
508
+ /themes report 25-theme dashboard with SEO + liquidity
509
+ /themes audit Flag dead themes (high SEO + zero volume)
510
+ /themes overlap Cross-theme dedupe report
511
+ /themes create/delete/add-series/remove-series/set-search-volume/export
512
+
513
+ Portfolio construction:
514
+ /correlate <t1> <t2> [...] Pairwise Pearson correlation matrix
515
+ /basket build [filters] -n N Diversified basket with cluster + correlation caps
516
+ /basket backtest --tickers ... NAV summary with Sharpe, max DD, win rate
517
+ /basket size --bankroll $ --probs ... Fractional Kelly sizing for picked legs
518
+ /basket candles --tickers ... OHLC bars for a weighted basket NAV
519
+
196
520
  Analysis:
197
521
  /backtest Model accuracy scorecard + live edge scanner
198
522
  /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;