
Feeds
- 15 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
Feeds is a Claude Code skill that provides real-time and historical market data from 8 prediction-market platforms with search, prices, orderbooks, and live subscriptions.
About
Feeds is a market-data skill that aggregates real-time and historical data from eight prediction-market platforms including Polymarket, Kalshi, Manifold, and Drift. A developer uses /feed commands or the createFeedManager TypeScript API to search markets, get prices and orderbooks, subscribe to live updates, fetch news, and run edge and Kelly analysis. Read-only access needs no credentials for several platforms.
- Real-time and historical market data from 8 prediction-market platforms
- Searches markets, gets prices and orderbooks, and subscribes to live price/trade updates
- Adds news retrieval, edge analysis vs external models, and Kelly-fraction calculation
Feeds by the numbers
- 15 all-time installs (skills.sh)
- Ranked #745 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
feeds capabilities & compatibility
Read-only access needs no credentials for several platforms; API keys are optional for platforms that support trading.
- Capabilities
- market search · price feed · orderbook · realtime subscription · news retrieval · edge analysis
- Use cases
- data analysis · trading · web search
- Pricing
- Freemium
What feeds says it does
Real-time and historical market data from Polymarket, Kalshi, Manifold, Metaculus, PredictIt, Drift, Betfair, and Smarkets.
Subscribe to price updates
npx skills add https://github.com/alsk1992/cloddsbot --skill feedsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Aggregate real-time and historical prices, orderbooks, and news from 8 prediction-market platforms into one interface.
Who is it for?
Aggregating and subscribing to market data across Polymarket, Kalshi, Manifold, and more.
Skip if: Trading execution (use the execution or drift skills) or non-prediction-market data.
When should I use this skill?
You want unified real-time prices, orderbooks, or news across prediction-market platforms.
What you get
A single interface to search, price, subscribe, and analyze markets across eight platforms.
By the numbers
- 8 platforms: Polymarket, Kalshi, Manifold, Metaculus, PredictIt, Drift, Betfair, Smarkets
- 5 platforms support trading, 3 read-only
Files
Market Feeds - Complete API Reference
Real-time and historical market data from Polymarket, Kalshi, Manifold, Metaculus, PredictIt, Drift, Betfair, and Smarkets.
Supported Platforms
| Platform | Feed Type | Trading | Data |
|---|---|---|---|
| Polymarket | WebSocket + RTDS | Yes | Prices, orderbook, trades |
| Kalshi | WebSocket | Yes | Prices, orderbook |
| Betfair | WebSocket | Yes | Odds, volume |
| Smarkets | WebSocket | Yes | Odds, volume |
| Drift | REST | Yes | Prices, funding |
| Manifold | WebSocket | Read-only | Prices, comments |
| Metaculus | REST | Read-only | Forecasts |
| PredictIt | REST | Read-only | Prices |
---
Chat Commands
Search Markets
/feed search "trump" # Search all platforms
/feed search "election" --platform poly # Search specific platform
/feed search "fed rate" --limit 20 # Limit resultsGet Prices
/feed price poly <market-id> # Get current price
/feed price kalshi TRUMP-WIN # Kalshi market
/feed prices "trump" # Prices for all matching marketsOrderbook
/feed orderbook poly <market-id> # Get orderbook
/feed orderbook poly <market-id> --depth 10 # Limit depthSubscribe (Real-time)
/feed subscribe poly <market-id> # Subscribe to price updates
/feed unsubscribe poly <market-id> # Unsubscribe
/feed subscriptions # List active subscriptionsNews
/feed news "trump" # Get news for topic
/feed news <market-id> # News for specific market
/feed news --recent 10 # Last 10 news itemsEdge Analysis
/feed edge poly <market-id> # Analyze edge vs models
/feed kelly poly <market-id> --prob 0.55 # Calculate Kelly fraction---
TypeScript API Reference
Create Feed Manager
import { createFeedManager } from 'clodds/feeds';
const feeds = createFeedManager({
platforms: ['polymarket', 'kalshi', 'manifold', 'betfair'],
// Enable real-time
enableRealtime: true,
// Platform credentials (optional for read-only)
polymarket: { apiKey },
kalshi: { apiKey },
betfair: { appKey, sessionToken },
});Search Markets
// Search across all platforms
const results = await feeds.searchMarkets('trump election', {
platforms: ['polymarket', 'kalshi'],
limit: 20,
sortBy: 'volume',
});
for (const market of results) {
console.log(`[${market.platform}] ${market.question}`);
console.log(` Price: ${market.price}`);
console.log(` Volume: $${market.volume.toLocaleString()}`);
}Get Single Market
// Get specific market
const market = await feeds.getMarket('polymarket', 'market-123');
console.log(`Question: ${market.question}`);
console.log(`YES: ${market.yesPrice} / NO: ${market.noPrice}`);
console.log(`Volume: $${market.volume.toLocaleString()}`);
console.log(`End date: ${market.endDate}`);Get Price
// Get current price
const price = await feeds.getPrice('polymarket', 'market-123');
console.log(`YES: ${price.yes}`);
console.log(`NO: ${price.no}`);
console.log(`Spread: ${price.spread}`);
console.log(`Updated: ${price.timestamp}`);Get Orderbook
// Get orderbook
const orderbook = await feeds.getOrderbook('polymarket', 'market-123', {
depth: 10,
});
console.log('Bids (YES):');
for (const bid of orderbook.bids) {
console.log(` ${bid.price}: $${bid.size}`);
}
console.log('Asks (YES):');
for (const ask of orderbook.asks) {
console.log(` ${ask.price}: $${ask.size}`);
}Subscribe to Real-time Updates
// Subscribe to price updates
const subscription = await feeds.subscribePrice('polymarket', 'market-123');
subscription.on('price', (update) => {
console.log(`Price update: YES=${update.yes}, NO=${update.no}`);
});
subscription.on('trade', (trade) => {
console.log(`Trade: ${trade.side} ${trade.size} @ ${trade.price}`);
});
// Unsubscribe
await subscription.unsubscribe();News
// Get recent news
const news = await feeds.getRecentNews('trump', { limit: 10 });
for (const article of news) {
console.log(`[${article.source}] ${article.title}`);
console.log(` ${article.summary}`);
console.log(` ${article.url}`);
}
// Search news
const searchResults = await feeds.searchNews('federal reserve', {
from: '2024-01-01',
sources: ['reuters', 'bloomberg'],
});Edge Analysis
// Analyze edge vs external models
const edge = await feeds.analyzeEdge('polymarket', 'market-123');
console.log(`Market price: ${edge.marketPrice}`);
console.log(`Model estimates:`);
for (const model of edge.models) {
console.log(` ${model.name}: ${model.estimate}`);
console.log(` Edge: ${model.edge > 0 ? '+' : ''}${model.edge.toFixed(1)}%`);
}
console.log(`Consensus edge: ${edge.consensusEdge.toFixed(1)}%`);Kelly Criterion
// Calculate optimal position size
const kelly = await feeds.calculateKelly({
platform: 'polymarket',
marketId: 'market-123',
estimatedProbability: 0.55, // Your estimate
bankroll: 10000, // Your bankroll
kellyFraction: 0.5, // Half-Kelly for safety
});
console.log(`Market price: ${kelly.marketPrice}`);
console.log(`Your estimate: ${kelly.estimatedProb}`);
console.log(`Edge: ${kelly.edge.toFixed(1)}%`);
console.log(`Full Kelly: ${kelly.fullKelly.toFixed(1)}%`);
console.log(`Recommended size: $${kelly.recommendedSize}`);---
Platform-Specific Features
Polymarket RTDS (Real-time Data Service)
// Connect to RTDS for ultra-low-latency updates
const rtds = await feeds.connectRTDS('polymarket');
rtds.on('tick', (tick) => {
console.log(`${tick.market}: ${tick.price} (${tick.side})`);
});
rtds.subscribe(['market-123', 'market-456']);Betfair Odds
// Get Betfair odds
const odds = await feeds.getBetfairOdds('event-123');
for (const runner of odds.runners) {
console.log(`${runner.name}: ${runner.backOdds} / ${runner.layOdds}`);
}Metaculus Forecasts
// Get Metaculus community forecast
const forecast = await feeds.getMetaculusForecast('question-123');
console.log(`Community median: ${forecast.median}`);
console.log(`25th percentile: ${forecast.q25}`);
console.log(`75th percentile: ${forecast.q75}`);
console.log(`Forecasters: ${forecast.forecasterCount}`);---
Data Refresh Rates
| Platform | REST | WebSocket |
|---|---|---|
| Polymarket | 5s | Real-time |
| Kalshi | 10s | Real-time |
| Betfair | 1s | Real-time |
| Manifold | 30s | Real-time |
| Metaculus | 60s | N/A |
| PredictIt | 30s | N/A |
---
Best Practices
1. Use WebSocket for real-time trading decisions 2. Cache responses for non-time-sensitive queries 3. Respect rate limits - batch requests when possible 4. Subscribe selectively - only to markets you need 5. Handle reconnections - WebSocket connections can drop
/**
* Feeds CLI Skill
*
* Commands:
* /feeds list [category] - List all registered feeds
* /feeds info <id> - Detailed feed info
* /feeds capabilities - Group feeds by capability
* /feeds categories - Group feeds by category
* /feeds search <query> - Search feeds by name/description/capability
* /feeds status - Active feeds + cache stats
* /feeds ready - Feeds ready to activate (env vars present)
* /feeds env <id> - Show required/optional env vars for a feed
* /feeds subscribe <platform> <market> - Subscribe to price updates
* /feeds unsubscribe <platform> <market> - Unsubscribe from updates
* /feeds search-markets <query> [platform] - Search markets across feeds
* /feeds price <platform> <market> - Get current price
* /feeds cache [clear] - View or clear cache
*/
import {
getGlobalFeedRegistry,
FeedCapability,
type FeedSummary,
type FeedCategory,
} from '../../../feeds/registry';
import { registerAllFeeds } from '../../../feeds/descriptors';
import { formatHelp } from '../../help.js';
import { wrapSkillError } from '../../errors.js';
// Ensure feeds are registered on first use
let registered = false;
function ensureRegistered(): void {
if (!registered) {
registerAllFeeds();
registered = true;
}
}
// Track active subscriptions
const activeSubscriptions = new Map<string, () => void>();
// Lazy FeedManager singleton — avoids recreating on every command
import type { FeedManager } from '../../../feeds/index';
let _feedManager: FeedManager | null = null;
let _feedManagerPromise: Promise<FeedManager | null> | null = null;
async function getFeedManager(): Promise<FeedManager | null> {
if (_feedManager) return _feedManager;
if (_feedManagerPromise) return _feedManagerPromise;
_feedManagerPromise = (async () => {
try {
const feedsMod = await import('../../../feeds/index');
const configMod = await import('../../../config/index');
let config;
try { config = configMod.loadConfig(); } catch { config = configMod.DEFAULT_CONFIG; }
_feedManager = await feedsMod.createFeedManager(config.feeds ?? {} as any);
return _feedManager;
} catch {
_feedManagerPromise = null;
return null;
}
})();
return _feedManagerPromise;
}
// Capability labels for display
const capLabels: Record<string, string> = {
[FeedCapability.MARKET_DATA]: 'Market Data',
[FeedCapability.ORDERBOOK]: 'Orderbook',
[FeedCapability.REALTIME_PRICES]: 'Real-time Prices',
[FeedCapability.TRADING]: 'Trading',
[FeedCapability.NEWS]: 'News',
[FeedCapability.CRYPTO_PRICES]: 'Crypto Prices',
[FeedCapability.WEATHER]: 'Weather',
[FeedCapability.SPORTS]: 'Sports',
[FeedCapability.POLITICS]: 'Politics',
[FeedCapability.ECONOMICS]: 'Economics',
[FeedCapability.GEOPOLITICAL]: 'Geopolitical',
[FeedCapability.EDGE_DETECTION]: 'Edge Detection',
[FeedCapability.HISTORICAL]: 'Historical Data',
};
const categoryLabels: Record<FeedCategory, string> = {
prediction_market: 'Prediction Markets',
crypto: 'Crypto',
news: 'News & Social',
weather: 'Weather',
sports: 'Sports',
politics: 'Politics',
economics: 'Economics',
geopolitical: 'Geopolitical',
data: 'Data',
custom: 'Custom',
};
function statusIcon(s: FeedSummary): string {
if (s.status === 'planned') return '[~~]';
if (s.status === 'deprecated') return '[XX]';
if (s.active) return '[ON]';
if (s.ready) return '[--]';
return '[!!]';
}
function feedLine(s: FeedSummary): string {
let line = ` ${statusIcon(s)} **${s.name}** (\`${s.id}\`)`;
if (s.skillCommand) line += ` \`${s.skillCommand}\``;
line += ` — ${s.description}`;
return line;
}
async function execute(args: string): Promise<string> {
ensureRegistered();
const registry = getGlobalFeedRegistry();
const parts = args.trim().split(/\s+/);
const cmd = parts[0]?.toLowerCase() || 'list';
switch (cmd) {
// =========================================================================
// DISCOVERY
// =========================================================================
case 'list':
case 'ls': {
const filterCat = parts[1]?.toLowerCase() as FeedCategory | undefined;
const groups = registry.groupByCategory();
const stats = registry.stats();
let output = `**Feed Registry** — ${stats.total} feeds (${stats.active} active, ${stats.ready} ready)\n`;
output += `Legend: [ON] active [--] ready [!!] missing env [~~] planned\n\n`;
const cats = Object.keys(groups).sort() as FeedCategory[];
for (const cat of cats) {
if (filterCat && cat !== filterCat) continue;
const feeds = groups[cat];
output += `### ${categoryLabels[cat] || cat} (${feeds.length})\n`;
for (const s of feeds) {
output += feedLine(s) + '\n';
}
output += '\n';
}
if (filterCat && !groups[filterCat]) {
output += `No feeds in category "${filterCat}".\n`;
output += `Available: ${cats.join(', ')}`;
}
return output;
}
case 'info':
case 'i': {
if (!parts[1]) return 'Usage: /feeds info <feed-id>';
const desc = registry.get(parts[1]);
if (!desc) return `Feed "${parts[1]}" not found. Use \`/feeds list\` to see all.`;
const { ready, missing } = registry.canActivate(desc.id);
const active = registry.isActive(desc.id);
const isPlanned = desc.status === 'planned';
let output = `**${desc.name}** (\`${desc.id}\`)${isPlanned ? ' — PLANNED' : ''}\n\n`;
output += `${desc.description}\n\n`;
output += `Category: ${categoryLabels[desc.category] || desc.category}\n`;
output += `Connection: ${desc.connectionType}\n`;
if (isPlanned) {
output += `Status: Planned (not yet implemented)\n`;
} else {
output += `Status: ${active ? 'Active' : ready ? 'Ready' : 'Missing env vars'}\n`;
}
if (desc.skillCommand) output += `CLI: \`${desc.skillCommand} help\`\n`;
if (desc.version) output += `Version: ${desc.version}\n`;
if (desc.docsUrl) output += `Docs: ${desc.docsUrl}\n`;
output += '\n';
output += `**Capabilities:**\n`;
for (const cap of desc.capabilities) {
output += ` - ${capLabels[cap] || cap}\n`;
}
output += `\n**Data Types:** ${desc.dataTypes.join(', ')}\n`;
if (desc.requiredEnv?.length) {
output += `\n**Required Env Vars:**\n`;
for (const v of desc.requiredEnv) {
const set = !!process.env[v];
output += ` ${set ? '[x]' : '[ ]'} ${v}\n`;
}
}
if (desc.optionalEnv?.length) {
output += `\n**Optional Env Vars:**\n`;
for (const v of desc.optionalEnv) {
const set = !!process.env[v];
output += ` ${set ? '[x]' : '[ ]'} ${v}\n`;
}
}
if (missing.length > 0) {
output += `\n**Missing:** Set these to activate: ${missing.join(', ')}`;
}
return output;
}
case 'capabilities':
case 'caps': {
const groups = registry.groupByCapability();
let output = '**Feeds by Capability**\n\n';
const caps = Object.keys(groups).sort();
for (const cap of caps) {
const feeds = groups[cap as FeedCapability];
output += `**${capLabels[cap] || cap}** (${feeds.length})\n`;
for (const s of feeds) {
output += ` ${statusIcon(s)} ${s.name}\n`;
}
output += '\n';
}
return output;
}
case 'categories':
case 'cats': {
const groups = registry.groupByCategory();
let output = '**Feeds by Category**\n\n';
for (const [cat, feeds] of Object.entries(groups)) {
output += `**${categoryLabels[cat as FeedCategory] || cat}** (${feeds.length}): `;
output += feeds.map(s => s.name).join(', ') + '\n';
}
return output;
}
case 'search':
case 'find': {
if (!parts[1]) return 'Usage: /feeds search <query>';
const query = parts.slice(1).join(' ');
const results = registry.search(query);
if (results.length === 0) return `No feeds matching "${query}".`;
let output = `**Feed Search: "${query}"** (${results.length} results)\n\n`;
for (const desc of results) {
const { ready } = registry.canActivate(desc.id);
const active = registry.isActive(desc.id);
const icon = active ? '[ON]' : ready ? '[--]' : '[!!]';
output += `${icon} **${desc.name}** (\`${desc.id}\`) — ${desc.description}\n`;
output += ` Caps: ${desc.capabilities.map(c => capLabels[c] || c).join(', ')}\n\n`;
}
return output;
}
case 'ready': {
const ready = registry.listReady();
if (ready.length === 0) return 'No feeds ready to activate. Check env vars with `/feeds list`.';
let output = `**Ready Feeds** (${ready.length})\n\n`;
for (const s of ready) {
output += `${s.active ? '[ON]' : '[--]'} **${s.name}** — ${s.description}\n`;
}
return output;
}
case 'env': {
if (!parts[1]) return 'Usage: /feeds env <feed-id>';
const desc = registry.get(parts[1]);
if (!desc) return `Feed "${parts[1]}" not found.`;
let output = `**Env Vars for ${desc.name}**\n\n`;
if (desc.requiredEnv?.length) {
output += '**Required:**\n';
for (const v of desc.requiredEnv) {
output += ` ${process.env[v] ? '[x]' : '[ ]'} \`${v}\`\n`;
}
} else {
output += 'No required env vars (works out of the box).\n';
}
if (desc.optionalEnv?.length) {
output += '\n**Optional:**\n';
for (const v of desc.optionalEnv) {
output += ` ${process.env[v] ? '[x]' : '[ ]'} \`${v}\`\n`;
}
}
return output;
}
case 'active': {
const active = registry.listActive();
if (active.length === 0) return 'No feeds currently active.';
let output = `**Active Feeds** (${active.length})\n\n`;
for (const s of active) {
output += ` **${s.name}** — ${s.connectionType} — ${s.capabilities.map(c => capLabels[c] || c).join(', ')}\n`;
}
return output;
}
// =========================================================================
// MARKET DATA (preserved from original skill)
// =========================================================================
case 'status': {
const stats = registry.stats();
let output = `**Feed Status**\n\n`;
output += `Registered: ${stats.total} feeds\n`;
output += `Active: ${stats.active}\n`;
output += `Ready: ${stats.ready}\n`;
output += `Categories: ${stats.categories}\n\n`;
try {
const fm = await getFeedManager();
if (fm) {
const cache = fm.getCacheStats();
output += `**Cache:** ${cache.size} entries | Hit rate: ${(cache.hitRate * 100).toFixed(1)}% (${cache.hits}/${cache.hits + cache.misses})`;
}
} catch { /* cache stats optional */ }
return output;
}
case 'subscribe':
case 'sub': {
if (parts.length < 3) return 'Usage: /feeds subscribe <platform> <market-id>';
const platform = parts[1].toLowerCase();
const marketId = parts[2];
try {
const fm = await getFeedManager();
if (!fm) return 'Error: Could not initialize feed manager.';
const market = await fm.getMarket(marketId, platform);
if (!market) return `Market \`${marketId}\` not found on **${platform}**.`;
const subKey = `${platform}:${marketId}`;
const unsub = fm.subscribePrice(platform, marketId, () => {});
activeSubscriptions.set(subKey, unsub);
const price = market.outcomes?.[0]?.price;
let output = `Subscribed to **${platform}** market \`${marketId}\`\n\n`;
output += `**${market.question ?? market.id}**\n`;
if (price != null) output += `Current price: $${price.toFixed(3)}`;
return output;
} catch (e) {
return wrapSkillError('Feeds', 'subscribe', e);
}
}
case 'unsubscribe':
case 'unsub': {
if (parts.length < 3) return 'Usage: /feeds unsubscribe <platform> <market-id>';
const subKey = `${parts[1].toLowerCase()}:${parts[2]}`;
const unsub = activeSubscriptions.get(subKey);
if (!unsub) return `No active subscription for \`${subKey}\`.`;
unsub();
activeSubscriptions.delete(subKey);
return `Unsubscribed from \`${subKey}\`.`;
}
case 'search-markets':
case 'sm': {
if (parts.length < 2) return 'Usage: /feeds search-markets <query> [platform]';
const query = parts.slice(1, parts.length > 2 ? -1 : undefined).join(' ');
const platform = parts.length > 2 ? parts[parts.length - 1].toLowerCase() : undefined;
try {
const fm = await getFeedManager();
if (!fm) return 'Error: Could not initialize feed manager.';
const markets = await fm.searchMarkets(query, platform);
if (!markets.length) return `No markets found for "${query}".`;
let output = `**Market Search** (${markets.length})\n\n`;
for (const m of markets.slice(0, 10)) {
const price = m.outcomes?.[0]?.price;
output += `[${m.platform}] **${m.question ?? m.id}**\n`;
if (price != null) output += ` Price: $${price.toFixed(3)}`;
if (m.volume24h) output += ` | Vol: $${m.volume24h.toLocaleString()}`;
output += `\n ID: \`${m.id}\`\n\n`;
}
return output;
} catch (e) {
return wrapSkillError('Feeds', 'search-markets', e);
}
}
case 'price': {
if (parts.length < 3) return 'Usage: /feeds price <platform> <market-id>';
try {
const fm = await getFeedManager();
if (!fm) return 'Error: Could not initialize feed manager.';
const price = await fm.getPrice(parts[1].toLowerCase(), parts[2]);
if (price == null) return `Could not fetch price for \`${parts[2]}\` on **${parts[1]}**.`;
return `**${parts[1]}** \`${parts[2]}\`: $${price.toFixed(4)}`;
} catch (e) {
return wrapSkillError('Feeds', 'price', e);
}
}
case 'cache': {
try {
const fm = await getFeedManager();
if (!fm) return 'Error: Could not initialize feed manager.';
if (parts[1]?.toLowerCase() === 'clear') {
fm.clearCache();
return 'Market cache cleared.';
}
const stats = fm.getCacheStats();
return `**Cache:** ${stats.size} entries | ${(stats.hitRate * 100).toFixed(1)}% hit rate (${stats.hits}/${stats.hits + stats.misses})`;
} catch (e) {
return wrapSkillError('Feeds', 'cache', e);
}
}
case 'help':
default:
return formatHelp({
name: 'Feed Registry',
description: 'Discover, browse, and connect to data sources',
sections: [
{
title: 'Discovery',
commands: [
{ cmd: '/feeds list [category]', description: 'Browse all feeds (or filter: weather, crypto, ...)' },
{ cmd: '/feeds info <id>', description: 'Detailed info, env vars, CLI command' },
{ cmd: '/feeds search <query>', description: 'Search by name, description, or capability' },
{ cmd: '/feeds caps', description: 'Group feeds by capability' },
{ cmd: '/feeds cats', description: 'Group feeds by category' },
{ cmd: '/feeds ready', description: 'Feeds ready to activate now' },
{ cmd: '/feeds active', description: 'Currently running feeds' },
{ cmd: '/feeds env <id>', description: 'Required/optional env vars for a feed' },
],
},
{
title: 'Market Data',
commands: [
{ cmd: '/feeds status', description: 'Registry stats + cache' },
{ cmd: '/feeds search-markets <query>', description: 'Search markets across platforms' },
{ cmd: '/feeds price <platform> <id>', description: 'Get current price' },
{ cmd: '/feeds sub <platform> <id>', description: 'Subscribe to price updates' },
{ cmd: '/feeds unsub <platform> <id>', description: 'Unsubscribe' },
],
},
],
notes: [
'Categories: prediction_market, crypto, news, weather, economics, geopolitical',
'Legend: [ON] active [--] ready [!!] missing env [~~] planned',
],
seeAlso: [
{ cmd: '/markets', description: 'Market browser' },
{ cmd: '/signals', description: 'Trading signals' },
{ cmd: '/news', description: 'News feeds' },
{ cmd: '/ticks', description: 'Real-time ticks' },
],
});
}
}
export default {
name: 'feeds',
description: 'Feed registry — discover, browse, and connect to data sources',
commands: ['/feeds', '/feed'],
handle: execute,
};