Files
gmgn-skills/src/commands/track.ts
T
David LauandClaude Sonnet 4.6 21d78d0947 feat(track): add follow-tokens subcommand
Adds gmgn-cli track follow-tokens to query a wallet's followed token
list on a given chain via GET /v1/user/follow_tokens.

- Add getFollowTokens() to OpenApiClient (authExist)
- Add track follow-tokens command with --chain, --wallet (required),
  --group-id, --interval, --order-by, --direction, --limit, --cursor,
  --search optional flags
- Update SKILL.md: sub-commands table, rate limit table (weight 3),
  usage examples, options table, response fields
- Update docs/cli-usage.md and Readme.md / Readme.zh.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 10:49:53 +08:00

97 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Command } from "commander";
import { OpenApiClient } from "../client/OpenApiClient.js";
import { getConfig } from "../config.js";
import { exitOnError, printResult } from "../output.js";
import { validateChain } from "../validate.js";
export function registerTrackCommands(program: Command): void {
const track = program.command("track").description("On-chain tracking commands: follow-wallet trades, KOL trades, Smart Money trades");
track
.command("follow-tokens")
.description("Get the followed token list for a wallet on a given chain")
.requiredOption("--chain <chain>", "Chain: sol / bsc / base / eth")
.requiredOption("--wallet <address>", "Wallet address")
.option("--group-id <id>", "Filter by group: all_group (all), default, or a user-defined group ID")
.option("--interval <interval>", "Time interval for price change stats (e.g. 1m, 5m, 1h, 6h, 24h)")
.option("--order-by <field>", "Sort field: created_at / swaps / volume / market_cap / liquidity / price / open_timestamp")
.option("--direction <dir>", "Sort direction: asc / desc")
.option("--limit <n>", "Page size", parseInt)
.option("--cursor <cursor>", "Pagination cursor")
.option("--search <text>", "Search by token name or address")
.option("--raw", "Output raw JSON")
.action(async (opts) => {
validateChain(opts.chain);
const extra: Record<string, string | number> = {};
if (opts.groupId) extra["group_id"] = opts.groupId;
if (opts.interval) extra["interval"] = opts.interval;
if (opts.orderBy) extra["order_by"] = opts.orderBy;
if (opts.direction) extra["direction"] = opts.direction;
if (opts.limit != null) extra["limit"] = opts.limit;
if (opts.cursor) extra["cursor"] = opts.cursor;
if (opts.search) extra["search_text"] = opts.search;
const client = new OpenApiClient(getConfig());
const data = await client.getFollowTokens(opts.chain, opts.wallet, extra).catch(exitOnError);
printResult(data, opts.raw);
});
track
.command("follow-wallet")
.description("Get follow-wallet trade records")
.requiredOption("--chain <chain>", "Chain: sol / bsc / base / eth")
.option("--wallet <address>", "Filter by wallet address")
.option("--limit <n>", "Page size (1100, default 10)", parseInt)
.option("--side <side>", "Trade direction filter: buy / sell")
.option("--filter <tag...>", "Filter conditions, repeatable")
.option("--min-amount-usd <n>", "Minimum trade amount (USD)", parseFloat)
.option("--max-amount-usd <n>", "Maximum trade amount (USD)", parseFloat)
.option("--raw", "Output raw JSON")
.action(async (opts) => {
validateChain(opts.chain);
const extra: Record<string, string | number | string[]> = {};
if (opts.wallet) extra["wallet_address"] = opts.wallet;
if (opts.limit != null) extra["limit"] = opts.limit;
if (opts.side) extra["side"] = opts.side;
if (opts.filter?.length) extra["filters"] = opts.filter;
if (opts.minAmountUsd != null) extra["min_amount_usd"] = opts.minAmountUsd;
if (opts.maxAmountUsd != null) extra["max_amount_usd"] = opts.maxAmountUsd;
const client = new OpenApiClient(getConfig());
const data = await client.getFollowWallet(opts.chain, extra).catch(exitOnError);
printResult(data, opts.raw);
});
track
.command("kol")
.description("Get KOL trade records")
.requiredOption("--chain <chain>", "Chain: sol / bsc / base / eth")
.option("--limit <n>", "Page size (1200, default 100)", parseInt)
.option("--side <side>", "Filter by trade direction: buy / sell (client-side filter)")
.option("--raw", "Output raw JSON")
.action(async (opts) => {
if (opts.chain) validateChain(opts.chain);
const client = new OpenApiClient(getConfig());
const data = await client.getKol(opts.chain, opts.limit).catch(exitOnError) as { list?: { side: string }[] };
if (opts.side && data?.list) {
data.list = data.list.filter((item) => item.side === opts.side);
}
printResult(data, opts.raw);
});
track
.command("smartmoney")
.description("Get Smart Money trade records")
.requiredOption("--chain <chain>", "Chain: sol / bsc / base / eth")
.option("--limit <n>", "Page size (1200, default 100)", parseInt)
.option("--side <side>", "Filter by trade direction: buy / sell (client-side filter)")
.option("--raw", "Output raw JSON")
.action(async (opts) => {
if (opts.chain) validateChain(opts.chain);
const client = new OpenApiClient(getConfig());
const data = await client.getSmartMoney(opts.chain, opts.limit).catch(exitOnError) as { list?: { side: string }[] };
if (opts.side && data?.list) {
data.list = data.list.filter((item) => item.side === opts.side);
}
printResult(data, opts.raw);
});
}