feat(market): hot-searches range filters, flattened params, long-form fields

- Add --min-*/--max-* range flags to `market hot-searches` (same metric names as
  market trending); flatten filter fields onto each param (no nested filter object)
- Document response as long-form RankItem fields (server maps shortcodes); drop the
  filter_id field from the documented response shape
- Sync SKILL.md, cli-usage.md, Readme.md, Readme.zh.md, and OpenApiClient types

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Lau
2026-06-29 16:02:59 +08:00
parent 36771529d7
commit bdf1996e2f
6 changed files with 165 additions and 72 deletions
+8 -6
View File
@@ -179,16 +179,18 @@ export interface TokenSignalGroup {
max_create_or_open_ts?: string;
}
export interface HotSearchesFilter {
filters?: string[];
limit?: number;
}
// HotSearchesParam carries its filter fields flattened (no nested `filter` object):
// label/interval/chain plus optional `filters` boolean tags, `limit`, and rank-style
// numeric range bounds (min_<metric>/max_<metric>) incl. min_created/max_created.
// Extra range keys are forwarded verbatim; the service translates metric names per
// interval. See `market hot-searches` docs for the supported metric list.
export interface HotSearchesParam {
label?: string;
interval: string; // "1m" | "5m" | "1h" | "6h" | "24h"
chain: string; // "sol" | "bsc" | "base" | "eth" | "monad" | "megaeth" | "hyperevm" | "tron"
filter?: HotSearchesFilter;
filters?: string[];
limit?: number;
[key: string]: string[] | number | string | undefined;
}
export interface PumpFeeShareInfo {
+54 -32
View File
@@ -204,47 +204,69 @@ export function registerMarketCommands(program: Command): void {
printResult(data, opts["raw"] as boolean | undefined);
});
market
const hotSearchesCmd = market
.command("hot-searches")
.description("Get the hot-search ranking (most-searched tokens) for one or more chains")
.option("--chain <chain...>", "Chain(s), repeatable: sol / bsc / base / eth / monad / megaeth / hyperevm / tron (default: all 7 chains)")
.option("--interval <interval>", "Time window: 1m / 5m / 1h / 6h / 24h (default 24h)", "24h")
.option("--limit <n>", "Max results per chain (default 500)", parseInt)
.option("--filter <tag...>", "Filter tags, repeatable. sol defaults: renounced / frozen; EVM defaults: not_honeypot / verified / renounced")
.option("--params <json>", "Full params override: JSON array of param objects — overrides --chain/--interval/--limit/--filter when provided")
.option("--raw", "Output raw JSON")
.action(async (opts) => {
const interval = String(opts.interval);
if (!HOT_SEARCHES_INTERVALS.has(interval)) {
console.error(`[gmgn-cli] Invalid --interval "${interval}". Must be one of: ${[...HOT_SEARCHES_INTERVALS].join(", ")}`);
.option("--filter <tag...>", "Boolean filter tags, repeatable. sol defaults: renounced / frozen; EVM defaults: not_honeypot / verified / renounced")
.option("--params <json>", "Full params override: JSON array of param objects — overrides --chain/--interval/--limit/--filter and all range flags when provided")
.option("--raw", "Output raw JSON");
// Reuse the same min_*/max_* range flags as `market trending` — the hot_searches
// endpoint accepts the identical rank-style metric names inside `filter` and
// translates them server-side per interval (min_created/max_created are durations).
for (const def of RANK_RANGE_FIELDS) {
const flag = def.api.replace(/_/g, "-");
if (def.type === "int") {
hotSearchesCmd.option(`--${flag} <${def.type}>`, def.desc, parseInt);
} else if (def.type === "float") {
hotSearchesCmd.option(`--${flag} <${def.type}>`, def.desc, parseFloat);
} else if (def.type === "duration") {
hotSearchesCmd.option(`--${flag} <duration>`, def.desc, parseDuration);
} else {
hotSearchesCmd.option(`--${flag} <value>`, def.desc);
}
}
hotSearchesCmd.action(async (opts) => {
const interval = String(opts.interval);
if (!HOT_SEARCHES_INTERVALS.has(interval)) {
console.error(`[gmgn-cli] Invalid --interval "${interval}". Must be one of: ${[...HOT_SEARCHES_INTERVALS].join(", ")}`);
process.exit(1);
}
let params: HotSearchesParam[];
if (opts.params != null) {
try {
params = JSON.parse(opts.params as string) as HotSearchesParam[];
} catch {
console.error(`[gmgn-cli] --params must be a valid JSON array, e.g. '[{"chain":"sol","interval":"24h","filters":["renounced","frozen"],"limit":500,"min_liquidity":1000}]'`);
process.exit(1);
}
let params: HotSearchesParam[];
if (opts.params != null) {
try {
params = JSON.parse(opts.params as string) as HotSearchesParam[];
} catch {
console.error(`[gmgn-cli] --params must be a valid JSON array, e.g. '[{"chain":"sol","interval":"24h","filter":{"filters":["renounced","frozen"],"limit":500}}]'`);
process.exit(1);
} else {
// Empty params lets the server apply its default 7-chain config. Filter fields
// are flattened directly onto each param (no nested `filter` object).
const optsMap = opts as Record<string, unknown>;
const chains: string[] = opts.chain?.length ? (opts.chain as string[]) : [];
params = chains.map((chain) => {
const param: HotSearchesParam = { label: "hot-search", chain, interval };
if (opts.filter?.length) param.filters = opts.filter as string[];
if (opts.limit != null) param.limit = opts.limit as number;
// Fold in any rank-style min_*/max_* range flags (incl. min_created/max_created).
for (const def of RANK_RANGE_FIELDS) {
const val = optsMap[apiFieldToCliKey(def.api)];
if (val != null) param[def.api] = val as number | string;
}
} else {
// Empty params lets the server apply its default 7-chain config.
const chains: string[] = opts.chain?.length ? (opts.chain as string[]) : [];
params = chains.map((chain) => {
const param: HotSearchesParam = { label: "hot-search", chain, interval };
const filter: { filters?: string[]; limit?: number } = {};
if (opts.filter?.length) filter.filters = opts.filter as string[];
if (opts.limit != null) filter.limit = opts.limit as number;
if (Object.keys(filter).length) param.filter = filter;
return param;
});
}
return param;
});
}
const client = new OpenApiClient(getConfig());
const data = await client.getHotSearches(params).catch(exitOnError);
printResult(data, opts.raw);
});
const client = new OpenApiClient(getConfig());
const data = await client.getHotSearches(params).catch(exitOnError);
printResult(data, opts.raw);
});
}
const HOT_SEARCHES_INTERVALS = new Set(["1m", "5m", "1h", "6h", "24h"]);