echopai 2.4.0 → 2.5.0
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.
- package/dist/bin.js +221 -131
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -960,9 +960,9 @@ Phase 5.2 (server endpoint).
|
|
|
960
960
|
views_since_days: {
|
|
961
961
|
type: "integer",
|
|
962
962
|
minimum: 1,
|
|
963
|
-
maximum:
|
|
963
|
+
maximum: 30,
|
|
964
964
|
default: 7,
|
|
965
|
-
description: "Views lookback in days (1-
|
|
965
|
+
description: "Views lookback in days (1-30; views feed caps at 30d. Use the `views` verb's from/to for longer history)."
|
|
966
966
|
},
|
|
967
967
|
news_hours: {
|
|
968
968
|
type: "integer",
|
|
@@ -1489,6 +1489,39 @@ Phase 5.2 (server endpoint).
|
|
|
1489
1489
|
additionalProperties: false
|
|
1490
1490
|
}
|
|
1491
1491
|
},
|
|
1492
|
+
"limit-up.analysis": {
|
|
1493
|
+
cliKey: "limit-up.analysis",
|
|
1494
|
+
cliName: "limit-up analysis",
|
|
1495
|
+
method: "GET",
|
|
1496
|
+
path: "/v1/limit-up/analysis",
|
|
1497
|
+
description: '涨停股 LLM 归因:逐股「主导题材 leading_concept + 概念标签 concept_tags +\n涨停理由 reason」。题材由 LLM 依据最新研报 / 机构观点 / 快讯判定,**不依赖概念\n图谱**(可含图谱里还没有的全新题材),每交易日北京 9:45 / 11:00 / 12:30 / 14:00 /\n14:40 / 18:00 共 6 个时段自动刷新(后跑时段在前次基础上 refine)。\n\n与 `limit-up/pool` 互补:pool 给"涨停了哪些票"(行情数据,stockpulse 上游),\n本接口给"为什么涨停 / 属什么题材"(LLM 归因,echopai api 上游)。pool 里的\ngraph-based `leading_concept` 是另一条数据,本接口的 `leading_concept` 才是\nLLM 判定结果。\n\n⚠️ **trade_date 不传 = 库内最新「已归因」交易日**(非自然最新交易日):归因每个\n时段按当时池快照写库,今日**首个时段(北京约 09:47)落库前**,不传 trade_date\n会返回**上一交易日**的归因(不是今日空结果)——agent 拿当日数据请显式传今日\ntrade_date 并以 `trade_date` / `generated_at` 字段核对时效。显式传某交易日而该日\n尚未归因时,`analyses` 为空。\n\n平面归属:本接口在 **vu 平面**(前端 apiFetch 同源),与 `market/status` 同款\n`x-audience: vu`;行情类 `limit-up/pool|summary|history` 在 sp 平面(前端 spFetch)。\n需要 `market:read` 或任一 `quote:*` scope。\n',
|
|
1498
|
+
summary: "A-share limit-up LLM attribution (per-stock leading theme + reason)",
|
|
1499
|
+
positional: [],
|
|
1500
|
+
outputDefault: "json",
|
|
1501
|
+
pagination: "none",
|
|
1502
|
+
stream: false,
|
|
1503
|
+
billable: true,
|
|
1504
|
+
idempotencyRequired: false,
|
|
1505
|
+
scopesAny: [
|
|
1506
|
+
"market:read",
|
|
1507
|
+
"quote:l1",
|
|
1508
|
+
"quote:l2",
|
|
1509
|
+
"quote:delayed"
|
|
1510
|
+
],
|
|
1511
|
+
sideEffect: "read",
|
|
1512
|
+
dryRunSupported: false,
|
|
1513
|
+
inputSchema: {
|
|
1514
|
+
type: "object",
|
|
1515
|
+
properties: {
|
|
1516
|
+
trade_date: {
|
|
1517
|
+
type: "string",
|
|
1518
|
+
format: "date",
|
|
1519
|
+
description: "ISO date YYYY-MM-DD;不传走库内**最新已归因**交易日(今日首段落库前会回退到上一交易日)。"
|
|
1520
|
+
}
|
|
1521
|
+
},
|
|
1522
|
+
additionalProperties: false
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1492
1525
|
"limit-up.history": {
|
|
1493
1526
|
cliKey: "limit-up.history",
|
|
1494
1527
|
cliName: "limit-up history",
|
|
@@ -1723,7 +1756,17 @@ Phase 5.2 (server endpoint).
|
|
|
1723
1756
|
minimum: 1,
|
|
1724
1757
|
maximum: 720,
|
|
1725
1758
|
default: 24,
|
|
1726
|
-
description: "
|
|
1759
|
+
description: "Rolling-feed lookback in hours (≤720 = 30d). For longer history use from/to."
|
|
1760
|
+
},
|
|
1761
|
+
from: {
|
|
1762
|
+
type: "string",
|
|
1763
|
+
format: "date",
|
|
1764
|
+
description: "History range start (China date YYYY-MM-DD). Range mode: with the security filter present, long history is capped at 365 days and Pro-only (non-Pro clamped to 30 days)."
|
|
1765
|
+
},
|
|
1766
|
+
to: {
|
|
1767
|
+
type: "string",
|
|
1768
|
+
format: "date",
|
|
1769
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
1727
1770
|
},
|
|
1728
1771
|
limit: {
|
|
1729
1772
|
type: "integer",
|
|
@@ -1731,6 +1774,12 @@ Phase 5.2 (server endpoint).
|
|
|
1731
1774
|
maximum: 100,
|
|
1732
1775
|
default: 20,
|
|
1733
1776
|
description: "Max items per page."
|
|
1777
|
+
},
|
|
1778
|
+
offset: {
|
|
1779
|
+
type: "integer",
|
|
1780
|
+
minimum: 0,
|
|
1781
|
+
default: 0,
|
|
1782
|
+
description: "Pagination offset (page through long history ranges)."
|
|
1734
1783
|
}
|
|
1735
1784
|
},
|
|
1736
1785
|
additionalProperties: false,
|
|
@@ -1770,9 +1819,19 @@ Phase 5.2 (server endpoint).
|
|
|
1770
1819
|
since_hours: {
|
|
1771
1820
|
type: "integer",
|
|
1772
1821
|
minimum: 1,
|
|
1773
|
-
maximum:
|
|
1822
|
+
maximum: 720,
|
|
1774
1823
|
default: 24,
|
|
1775
|
-
description: "
|
|
1824
|
+
description: "Rolling-feed lookback in hours (≤720 = 30d). For longer history use from/to."
|
|
1825
|
+
},
|
|
1826
|
+
from: {
|
|
1827
|
+
type: "string",
|
|
1828
|
+
format: "date",
|
|
1829
|
+
description: "History range start (China date YYYY-MM-DD). Range mode: with the query filter present, long history is capped at 365 days and Pro-only (non-Pro clamped to 30 days)."
|
|
1830
|
+
},
|
|
1831
|
+
to: {
|
|
1832
|
+
type: "string",
|
|
1833
|
+
format: "date",
|
|
1834
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
1776
1835
|
},
|
|
1777
1836
|
limit: {
|
|
1778
1837
|
type: "integer",
|
|
@@ -1780,6 +1839,12 @@ Phase 5.2 (server endpoint).
|
|
|
1780
1839
|
maximum: 100,
|
|
1781
1840
|
default: 20,
|
|
1782
1841
|
description: "Max items per page."
|
|
1842
|
+
},
|
|
1843
|
+
offset: {
|
|
1844
|
+
type: "integer",
|
|
1845
|
+
minimum: 0,
|
|
1846
|
+
default: 0,
|
|
1847
|
+
description: "Pagination offset (page through long history ranges)."
|
|
1783
1848
|
}
|
|
1784
1849
|
},
|
|
1785
1850
|
additionalProperties: false,
|
|
@@ -2330,70 +2395,6 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2330
2395
|
additionalProperties: false
|
|
2331
2396
|
}
|
|
2332
2397
|
},
|
|
2333
|
-
"squawk.audio": {
|
|
2334
|
-
cliKey: "squawk.audio",
|
|
2335
|
-
cliName: "squawk audio",
|
|
2336
|
-
method: "GET",
|
|
2337
|
-
path: "/v1/squawk/{item_id}/audio",
|
|
2338
|
-
description: "返回 squawk 项的 TTS 音频(MP3)。直接 stream,可作为 `<audio>` src 用。",
|
|
2339
|
-
summary: "Stream squawk TTS audio",
|
|
2340
|
-
positional: [
|
|
2341
|
-
"item_id"
|
|
2342
|
-
],
|
|
2343
|
-
outputDefault: "json",
|
|
2344
|
-
pagination: "none",
|
|
2345
|
-
stream: false,
|
|
2346
|
-
billable: false,
|
|
2347
|
-
idempotencyRequired: false,
|
|
2348
|
-
scopesAny: [],
|
|
2349
|
-
sideEffect: "read",
|
|
2350
|
-
dryRunSupported: false,
|
|
2351
|
-
inputSchema: {
|
|
2352
|
-
type: "object",
|
|
2353
|
-
properties: {
|
|
2354
|
-
item_id: {
|
|
2355
|
-
type: "string",
|
|
2356
|
-
description: "Squawk item id (returned by squawk.recent items[].id)."
|
|
2357
|
-
}
|
|
2358
|
-
},
|
|
2359
|
-
additionalProperties: false,
|
|
2360
|
-
required: [
|
|
2361
|
-
"item_id"
|
|
2362
|
-
]
|
|
2363
|
-
}
|
|
2364
|
-
},
|
|
2365
|
-
"squawk.waveform": {
|
|
2366
|
-
cliKey: "squawk.waveform",
|
|
2367
|
-
cliName: "squawk waveform",
|
|
2368
|
-
method: "GET",
|
|
2369
|
-
path: "/v1/squawk/{item_id}/waveform",
|
|
2370
|
-
description: "返回 squawk 音频的预计算波形 peak 数据,用于客户端波形可视化。",
|
|
2371
|
-
summary: "Waveform peaks for squawk audio",
|
|
2372
|
-
positional: [
|
|
2373
|
-
"item_id"
|
|
2374
|
-
],
|
|
2375
|
-
outputDefault: "json",
|
|
2376
|
-
pagination: "none",
|
|
2377
|
-
stream: false,
|
|
2378
|
-
billable: false,
|
|
2379
|
-
idempotencyRequired: false,
|
|
2380
|
-
scopesAny: [],
|
|
2381
|
-
sideEffect: "read",
|
|
2382
|
-
dryRunSupported: false,
|
|
2383
|
-
inputSchema: {
|
|
2384
|
-
type: "object",
|
|
2385
|
-
properties: {
|
|
2386
|
-
item_id: {
|
|
2387
|
-
type: "string",
|
|
2388
|
-
description: "Squawk item id (returned by squawk.recent items[].id)."
|
|
2389
|
-
}
|
|
2390
|
-
},
|
|
2391
|
-
additionalProperties: false,
|
|
2392
|
-
required: [
|
|
2393
|
-
"item_id"
|
|
2394
|
-
]
|
|
2395
|
-
}
|
|
2396
|
-
},
|
|
2397
2398
|
"stocks.hot": {
|
|
2398
2399
|
cliKey: "stocks.hot",
|
|
2399
2400
|
cliName: "stocks hot",
|
|
@@ -2586,9 +2587,19 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2586
2587
|
since_days: {
|
|
2587
2588
|
type: "integer",
|
|
2588
2589
|
minimum: 1,
|
|
2589
|
-
maximum:
|
|
2590
|
+
maximum: 30,
|
|
2590
2591
|
default: 7,
|
|
2591
|
-
description: "
|
|
2592
|
+
description: "Rolling-feed lookback in days (≤30, translated to hours upstream). For longer history use from/to instead."
|
|
2593
|
+
},
|
|
2594
|
+
from: {
|
|
2595
|
+
type: "string",
|
|
2596
|
+
format: "date",
|
|
2597
|
+
description: "History range start (China calendar date YYYY-MM-DD). When set, switches to range mode: requires a narrowing filter (security/institution/analyst); long history is capped at 365 days and is Pro-only (non-Pro silently clamped to 30 days)."
|
|
2598
|
+
},
|
|
2599
|
+
to: {
|
|
2600
|
+
type: "string",
|
|
2601
|
+
format: "date",
|
|
2602
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
2592
2603
|
},
|
|
2593
2604
|
limit: {
|
|
2594
2605
|
type: "integer",
|
|
@@ -2596,6 +2607,12 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2596
2607
|
maximum: 100,
|
|
2597
2608
|
default: 30,
|
|
2598
2609
|
description: "Max items per page."
|
|
2610
|
+
},
|
|
2611
|
+
offset: {
|
|
2612
|
+
type: "integer",
|
|
2613
|
+
minimum: 0,
|
|
2614
|
+
default: 0,
|
|
2615
|
+
description: "Pagination offset (page through long history ranges)."
|
|
2599
2616
|
}
|
|
2600
2617
|
},
|
|
2601
2618
|
additionalProperties: false
|
|
@@ -2821,6 +2838,10 @@ function buildCommandTree(program, dispatch) {
|
|
|
2821
2838
|
{
|
|
2822
2839
|
const noun = program.command("limit-up");
|
|
2823
2840
|
noun.description("limit-up commands");
|
|
2841
|
+
{
|
|
2842
|
+
const cmd = noun.command("analysis");
|
|
2843
|
+
attachOperation(cmd, OPERATIONS["limit-up.analysis"], dispatch);
|
|
2844
|
+
}
|
|
2824
2845
|
{
|
|
2825
2846
|
const cmd = noun.command("history");
|
|
2826
2847
|
attachOperation(cmd, OPERATIONS["limit-up.history"], dispatch);
|
|
@@ -2938,18 +2959,6 @@ function buildCommandTree(program, dispatch) {
|
|
|
2938
2959
|
attachOperation(cmd, OPERATIONS["sentiment.turnover"], dispatch);
|
|
2939
2960
|
}
|
|
2940
2961
|
}
|
|
2941
|
-
{
|
|
2942
|
-
const noun = program.command("squawk");
|
|
2943
|
-
noun.description("squawk commands");
|
|
2944
|
-
{
|
|
2945
|
-
const cmd = noun.command("audio");
|
|
2946
|
-
attachOperation(cmd, OPERATIONS["squawk.audio"], dispatch);
|
|
2947
|
-
}
|
|
2948
|
-
{
|
|
2949
|
-
const cmd = noun.command("waveform");
|
|
2950
|
-
attachOperation(cmd, OPERATIONS["squawk.waveform"], dispatch);
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
2962
|
{
|
|
2954
2963
|
const noun = program.command("stocks");
|
|
2955
2964
|
noun.description("stocks commands");
|
|
@@ -3756,7 +3765,7 @@ import os2 from "node:os";
|
|
|
3756
3765
|
import path2 from "node:path";
|
|
3757
3766
|
|
|
3758
3767
|
// src/version.ts
|
|
3759
|
-
var CLI_VERSION = "2.
|
|
3768
|
+
var CLI_VERSION = "2.5.0";
|
|
3760
3769
|
|
|
3761
3770
|
// src/runtime/update_check.ts
|
|
3762
3771
|
var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
|
|
@@ -5178,7 +5187,7 @@ var digestSpec = {
|
|
|
5178
5187
|
description: "One-shot research digest for a single security: fan-out across views (PRIMARY), quote, 14-field valuation snapshot (PE/PB/PS/turnover/dividend/volume-ratio), market sentiment, supplementary news, and ownership_events (last-30d insider holder trades + share repurchases, plus next-30d lockup expirations — structured corporate-action reinforcement). Returns separated buckets — does NOT rank views vs news for the agent. Partial failures surface in meta.partial_failures[] without poisoning successful buckets. Use as the agent's first call when asked 'what's going on with <stock>'. Works across A-share | HK | US: A-share returns all buckets, while HK/US (e.g. HK:00700, US:BABA) return only the views + news buckets — quote / valuation / sentiment / ownership are A-share-only and are skipped (not reported as failures). AH dual-listing: for an A+H listed company pass the A-share canonical (e.g. SSE:601398) for the full picture; use the HK code only if the user explicitly wants the HK side.",
|
|
5179
5188
|
inputSchema: {
|
|
5180
5189
|
code: z5.string().regex(CODE_REGEX).describe("Canonical code: A-share SSE:600519 | HK HK:00700 | US US:BABA. HK/US return only views+news. Use `lookup` first if you only have a name."),
|
|
5181
|
-
views_since_days: z5.number().int().min(1).max(
|
|
5190
|
+
views_since_days: z5.number().int().min(1).max(30).default(7).describe("Lookback for views bucket (days, 1-30; views feed caps at 30d). For longer research history use the `views` verb's from/to."),
|
|
5182
5191
|
news_hours: z5.number().int().min(1).max(168).default(24).describe("Lookback for news bucket (hours, default 24 — event horizon)"),
|
|
5183
5192
|
limit_per_bucket: z5.number().int().min(1).max(50).default(10).describe("Per-bucket item cap (views/news). Quote/snapshot/sentiment aren't paginated here.")
|
|
5184
5193
|
},
|
|
@@ -5391,7 +5400,7 @@ function clamp2(raw, min, max, fallback) {
|
|
|
5391
5400
|
function buildDigestCommand() {
|
|
5392
5401
|
const cmd = new Command7(digestSpec.name).description(digestSpec.description);
|
|
5393
5402
|
cmd.addOption(new Option6("--code <canonical_code>", "Canonical code: SSE:600519 | HK:00700 | US:BABA (HK/US return only views+news)").makeOptionMandatory(true));
|
|
5394
|
-
cmd.addOption(new Option6("--views-since-days <n>", "Views lookback in days (1-
|
|
5403
|
+
cmd.addOption(new Option6("--views-since-days <n>", "Views lookback in days (1-30)").default("7"));
|
|
5395
5404
|
cmd.addOption(new Option6("--news-hours <n>", "News lookback in hours (1-168)").default("24"));
|
|
5396
5405
|
cmd.addOption(new Option6("--limit-per-bucket <n>", "Items per bucket (1-50)").default("10"));
|
|
5397
5406
|
cmd.action(async (opts) => {
|
|
@@ -5400,7 +5409,7 @@ function buildDigestCommand() {
|
|
|
5400
5409
|
}
|
|
5401
5410
|
const args = {
|
|
5402
5411
|
code: opts.code,
|
|
5403
|
-
views_since_days: clamp2(opts.viewsSinceDays, 1,
|
|
5412
|
+
views_since_days: clamp2(opts.viewsSinceDays, 1, 30, 7),
|
|
5404
5413
|
news_hours: clamp2(opts.newsHours, 1, 168, 24),
|
|
5405
5414
|
limit_per_bucket: clamp2(opts.limitPerBucket, 1, 50, 10)
|
|
5406
5415
|
};
|
|
@@ -5771,8 +5780,25 @@ var limitUpHistorySpec = {
|
|
|
5771
5780
|
},
|
|
5772
5781
|
backingOps: ["limit-up.history"]
|
|
5773
5782
|
};
|
|
5783
|
+
var limitUpAnalysisSpec = {
|
|
5784
|
+
name: "limit_up_analysis",
|
|
5785
|
+
description: "Per-stock limit-up LLM attribution for a given trade date: leading_concept (主导题材) + concept_tags (相关标签) + reason (涨停理由). Themes are judged by an LLM from the latest research / views / news (NOT from the concept graph, so brand-new themes are covered) and refreshed across 6 Beijing slots daily. Complements limit-up pool (which says '哪些票涨停'); this says '为什么涨停/属什么题材'. STALE-DATA WARNING: omitting trade_date returns the latest *attributed* trading day, NOT necessarily today — before today's first slot lands (~09:47 Beijing) it falls back to the PREVIOUS day's attribution. For today's data pass trade_date explicitly and verify the returned trade_date / generated_at fields. An explicit trade_date with no attribution yet returns an empty analyses list.",
|
|
5786
|
+
inputSchema: {
|
|
5787
|
+
trade_date: z8.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for the latest *attributed* day (falls back to the previous day until today's first slot lands ~09:47 Beijing — pass today explicitly for today's data)")
|
|
5788
|
+
},
|
|
5789
|
+
handler: async (args, ctx) => {
|
|
5790
|
+
const op = OPERATIONS["limit-up.analysis"];
|
|
5791
|
+
if (!op)
|
|
5792
|
+
throw new Error("limit-up.analysis op missing");
|
|
5793
|
+
const callArgs = {};
|
|
5794
|
+
if (args.trade_date)
|
|
5795
|
+
callArgs.trade_date = args.trade_date;
|
|
5796
|
+
return callOp(op, callArgs, ctx);
|
|
5797
|
+
},
|
|
5798
|
+
backingOps: ["limit-up.analysis"]
|
|
5799
|
+
};
|
|
5774
5800
|
function buildLimitUpCommand() {
|
|
5775
|
-
const cmd = new Command11("limit-up").description("A-share limit-up board — pool (per-stock detail) / summary (counts + ladder) / history (daily trend).");
|
|
5801
|
+
const cmd = new Command11("limit-up").description("A-share limit-up board — pool (per-stock detail) / summary (counts + ladder) / history (daily trend) / analysis (LLM 题材归因).");
|
|
5776
5802
|
const pool = cmd.command("pool").description(limitUpPoolSpec.description);
|
|
5777
5803
|
pool.addOption(new Option9("--trade-date <YYYY-MM-DD>", "Trade date; omit for latest"));
|
|
5778
5804
|
pool.addOption(new Option9("--include <s>", "active = 排除 ST/退市; all = 全部").choices([...INCLUDE_VALUES]).default("active"));
|
|
@@ -5809,6 +5835,17 @@ function buildLimitUpCommand() {
|
|
|
5809
5835
|
}
|
|
5810
5836
|
await executeVerb(async (ctx) => limitUpHistorySpec.handler({ days: clampInt5(opts.days, 1, 250, 30) }, ctx));
|
|
5811
5837
|
});
|
|
5838
|
+
const analysis = cmd.command("analysis").description(limitUpAnalysisSpec.description);
|
|
5839
|
+
analysis.addOption(new Option9("--trade-date <YYYY-MM-DD>", "Trade date; omit for latest"));
|
|
5840
|
+
analysis.action(async (opts) => {
|
|
5841
|
+
if (!OPERATIONS["limit-up.analysis"]) {
|
|
5842
|
+
emitVerbError("internal_error", "limit-up.analysis missing", undefined, 2);
|
|
5843
|
+
}
|
|
5844
|
+
const args = {};
|
|
5845
|
+
if (opts.tradeDate)
|
|
5846
|
+
args.trade_date = opts.tradeDate;
|
|
5847
|
+
await executeVerb(async (ctx) => limitUpAnalysisSpec.handler(args, ctx));
|
|
5848
|
+
});
|
|
5812
5849
|
return cmd;
|
|
5813
5850
|
}
|
|
5814
5851
|
// src/verbs/lookup.ts
|
|
@@ -6019,29 +6056,46 @@ var newsSpec = {
|
|
|
6019
6056
|
inputSchema: {
|
|
6020
6057
|
code: z11.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700). Takes precedence over `query`."),
|
|
6021
6058
|
query: z11.string().optional().describe("Free-text query (Chinese or English) matched against news content; omit for time-window feed. Do NOT pass canonical_code here — use `code` instead."),
|
|
6022
|
-
hours: z11.number().int().min(1).max(168).default(24).describe("
|
|
6023
|
-
|
|
6059
|
+
hours: z11.number().int().min(1).max(168).default(24).describe("Rolling-feed lookback in hours (default 24 — much narrower than views). For longer history use from/to with code or query."),
|
|
6060
|
+
from: z11.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs code or query; Pro-only up to 365 days back."),
|
|
6061
|
+
to: z11.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
|
|
6062
|
+
offset: z11.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
|
|
6063
|
+
limit: z11.number().int().min(1).max(100).default(20).describe("Max items per page")
|
|
6024
6064
|
},
|
|
6025
6065
|
handler: async (args, ctx) => {
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
if (!op2)
|
|
6029
|
-
throw new Error("news.list op missing");
|
|
6030
|
-
return callOp(op2, { security: args.code, since_hours: args.hours, limit: args.limit }, ctx);
|
|
6031
|
-
}
|
|
6032
|
-
if (args.query) {
|
|
6033
|
-
const op2 = OPERATIONS["news.search"];
|
|
6034
|
-
if (!op2)
|
|
6035
|
-
throw new Error("news.search op missing");
|
|
6036
|
-
return callOp(op2, { query: args.query, since_hours: args.hours, limit: args.limit }, ctx);
|
|
6037
|
-
}
|
|
6038
|
-
const op = OPERATIONS["news.feed"];
|
|
6066
|
+
const { opKey, callArgs } = buildNewsCall(args);
|
|
6067
|
+
const op = OPERATIONS[opKey];
|
|
6039
6068
|
if (!op)
|
|
6040
|
-
throw new Error(
|
|
6041
|
-
return callOp(op,
|
|
6069
|
+
throw new Error(`${opKey} op missing`);
|
|
6070
|
+
return callOp(op, callArgs, ctx);
|
|
6042
6071
|
},
|
|
6043
6072
|
backingOps: ["news.list", "news.search", "news.feed"]
|
|
6044
6073
|
};
|
|
6074
|
+
function buildNewsCall(args) {
|
|
6075
|
+
const hasRange = typeof args.from === "string" && args.from.length > 0;
|
|
6076
|
+
const withWindow = (a) => {
|
|
6077
|
+
if (typeof args.limit === "number")
|
|
6078
|
+
a.limit = args.limit;
|
|
6079
|
+
if (typeof args.offset === "number")
|
|
6080
|
+
a.offset = args.offset;
|
|
6081
|
+
if (hasRange) {
|
|
6082
|
+
a.from = args.from;
|
|
6083
|
+
if (typeof args.to === "string" && args.to.length > 0)
|
|
6084
|
+
a.to = args.to;
|
|
6085
|
+
} else {
|
|
6086
|
+
a.since_hours = args.hours;
|
|
6087
|
+
}
|
|
6088
|
+
return a;
|
|
6089
|
+
};
|
|
6090
|
+
if (args.code)
|
|
6091
|
+
return { opKey: "news.list", callArgs: withWindow({ security: args.code }) };
|
|
6092
|
+
if (args.query)
|
|
6093
|
+
return { opKey: "news.search", callArgs: withWindow({ query: args.query }) };
|
|
6094
|
+
if (hasRange) {
|
|
6095
|
+
throw new Error("range history (from/to) requires --code or --query; the bare feed has no narrowing filter");
|
|
6096
|
+
}
|
|
6097
|
+
return { opKey: "news.feed", callArgs: {} };
|
|
6098
|
+
}
|
|
6045
6099
|
function clamp3(raw, min, max, fallback) {
|
|
6046
6100
|
const n = Math.floor(Number(raw));
|
|
6047
6101
|
if (!Number.isFinite(n))
|
|
@@ -6056,17 +6110,25 @@ function buildNewsCommand() {
|
|
|
6056
6110
|
const cmd = new Command14(newsSpec.name).description(newsSpec.description);
|
|
6057
6111
|
cmd.addOption(new Option12("--code <canonical_code>", "Filter by security canonical_code (A | HK | US, e.g. SSE:600519, HK:00700); takes precedence over --query"));
|
|
6058
6112
|
cmd.addOption(new Option12("--query <text>", "Free-text query; omit for time-window feed"));
|
|
6059
|
-
cmd.addOption(new Option12("--hours <n>", "
|
|
6113
|
+
cmd.addOption(new Option12("--hours <n>", "Rolling-feed lookback (1-168 hours)").default("24"));
|
|
6114
|
+
cmd.addOption(new Option12("--from <date>", "History range start YYYY-MM-DD (range mode; needs --code or --query)"));
|
|
6115
|
+
cmd.addOption(new Option12("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
|
|
6116
|
+
cmd.addOption(new Option12("--offset <n>", "Pagination offset for long history").default("0"));
|
|
6060
6117
|
cmd.addOption(new Option12("--limit <n>", "Max items").default("20"));
|
|
6061
6118
|
cmd.action(async (opts) => {
|
|
6062
6119
|
const args = {
|
|
6063
6120
|
hours: clamp3(opts.hours, 1, 168, 24),
|
|
6121
|
+
offset: clamp3(opts.offset, 0, 1e5, 0),
|
|
6064
6122
|
limit: clamp3(opts.limit, 1, 100, 20)
|
|
6065
6123
|
};
|
|
6066
6124
|
if (opts.code)
|
|
6067
6125
|
args.code = opts.code;
|
|
6068
6126
|
if (opts.query)
|
|
6069
6127
|
args.query = opts.query;
|
|
6128
|
+
if (opts.from)
|
|
6129
|
+
args.from = opts.from;
|
|
6130
|
+
if (opts.to)
|
|
6131
|
+
args.to = opts.to;
|
|
6070
6132
|
await executeVerb(async (ctx) => newsSpec.handler(args, ctx));
|
|
6071
6133
|
});
|
|
6072
6134
|
return cmd;
|
|
@@ -6389,27 +6451,51 @@ var viewsSpec = {
|
|
|
6389
6451
|
code: z16.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700, US:AAPL)"),
|
|
6390
6452
|
analyst: z16.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
|
|
6391
6453
|
institution: z16.string().optional().describe("Broker / institution Chinese name"),
|
|
6392
|
-
since_days: z16.number().int().min(1).max(
|
|
6393
|
-
|
|
6454
|
+
since_days: z16.number().int().min(1).max(365).default(7).describe("Lookback in days. ≤30 = rolling feed; >30 auto-switches to range history " + "(needs a code/analyst/institution filter; Pro-only up to 365). For an explicit window use from/to."),
|
|
6455
|
+
from: z16.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs a filter; Pro-only up to 365 days back."),
|
|
6456
|
+
to: z16.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
|
|
6457
|
+
offset: z16.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
|
|
6458
|
+
limit: z16.number().int().min(1).max(100).default(30).describe("Max items per page")
|
|
6394
6459
|
},
|
|
6395
6460
|
handler: async (args, ctx) => {
|
|
6396
6461
|
const op = OPERATIONS["views.recent"];
|
|
6397
6462
|
if (!op)
|
|
6398
6463
|
throw new Error("views.recent op missing from codegen");
|
|
6399
|
-
|
|
6400
|
-
since_days: args.since_days,
|
|
6401
|
-
limit: args.limit
|
|
6402
|
-
};
|
|
6403
|
-
if (args.code)
|
|
6404
|
-
callArgs.security = args.code;
|
|
6405
|
-
if (args.analyst)
|
|
6406
|
-
callArgs.analyst = args.analyst;
|
|
6407
|
-
if (args.institution)
|
|
6408
|
-
callArgs.institution = args.institution;
|
|
6409
|
-
return callOp(op, callArgs, ctx);
|
|
6464
|
+
return callOp(op, buildViewsCallArgs(args), ctx);
|
|
6410
6465
|
},
|
|
6411
6466
|
backingOps: ["views.recent"]
|
|
6412
6467
|
};
|
|
6468
|
+
function buildViewsCallArgs(args) {
|
|
6469
|
+
const hasFilter = Boolean(args.code || args.analyst || args.institution);
|
|
6470
|
+
const sinceDays = typeof args.since_days === "number" ? args.since_days : 7;
|
|
6471
|
+
const callArgs = {};
|
|
6472
|
+
if (typeof args.limit === "number")
|
|
6473
|
+
callArgs.limit = args.limit;
|
|
6474
|
+
if (args.code)
|
|
6475
|
+
callArgs.security = args.code;
|
|
6476
|
+
if (args.analyst)
|
|
6477
|
+
callArgs.analyst = args.analyst;
|
|
6478
|
+
if (args.institution)
|
|
6479
|
+
callArgs.institution = args.institution;
|
|
6480
|
+
if (typeof args.offset === "number")
|
|
6481
|
+
callArgs.offset = args.offset;
|
|
6482
|
+
const explicitRange = typeof args.from === "string" && args.from.length > 0;
|
|
6483
|
+
if (explicitRange || sinceDays > 30) {
|
|
6484
|
+
if (!hasFilter) {
|
|
6485
|
+
throw new Error("range history (from/to or since_days>30) requires a narrowing filter: --code / --analyst / --institution");
|
|
6486
|
+
}
|
|
6487
|
+
callArgs.from = explicitRange ? args.from : chinaDateMinusDays(sinceDays);
|
|
6488
|
+
if (typeof args.to === "string" && args.to.length > 0)
|
|
6489
|
+
callArgs.to = args.to;
|
|
6490
|
+
} else {
|
|
6491
|
+
callArgs.since_days = sinceDays;
|
|
6492
|
+
}
|
|
6493
|
+
return callArgs;
|
|
6494
|
+
}
|
|
6495
|
+
function chinaDateMinusDays(days) {
|
|
6496
|
+
const chinaMs = Date.now() + 8 * 3600 * 1000 - days * 86400 * 1000;
|
|
6497
|
+
return new Date(chinaMs).toISOString().slice(0, 10);
|
|
6498
|
+
}
|
|
6413
6499
|
var reportSpec = {
|
|
6414
6500
|
name: "report",
|
|
6415
6501
|
description: "Fetch the FULL research-report text behind a view (mineru-parsed long-form), for when the `views` summary is not enough. Only works on views where `full_text_available=true` (gdrive sell-side reports). Two-step usage: default `format=outline` returns a few-KB heading map + total_chars + each heading's char_offset; then call `format=full` with `offset` (e.g. an outline char_offset) to read the body in pages — follow `next_offset` to page through large docs instead of pulling everything at once.",
|
|
@@ -6450,14 +6536,18 @@ function buildViewsCommand() {
|
|
|
6450
6536
|
cmd.addOption(new Option17("--code <canonical_code>", "Filter by security canonical_code"));
|
|
6451
6537
|
cmd.addOption(new Option17("--analyst <name>", "Analyst Chinese name (exact / fuzzy)"));
|
|
6452
6538
|
cmd.addOption(new Option17("--institution <name>", "Broker / institution Chinese name"));
|
|
6453
|
-
cmd.addOption(new Option17("--since-days <n>", "Lookback
|
|
6539
|
+
cmd.addOption(new Option17("--since-days <n>", "Lookback days (1-365). ≤30 = feed; >30 = range history (needs a filter, Pro-only)").default("7"));
|
|
6540
|
+
cmd.addOption(new Option17("--from <date>", "History range start YYYY-MM-DD (range mode; needs a filter)"));
|
|
6541
|
+
cmd.addOption(new Option17("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
|
|
6542
|
+
cmd.addOption(new Option17("--offset <n>", "Pagination offset for long history").default("0"));
|
|
6454
6543
|
cmd.addOption(new Option17("--limit <n>", "Max items (1-100)").default("30"));
|
|
6455
6544
|
cmd.action(async (opts) => {
|
|
6456
6545
|
if (!OPERATIONS["views.recent"]) {
|
|
6457
6546
|
emitVerbError("internal_error", "views.recent missing from codegen", undefined, 2);
|
|
6458
6547
|
}
|
|
6459
6548
|
const args = {
|
|
6460
|
-
since_days: clamp5(opts.sinceDays, 1,
|
|
6549
|
+
since_days: clamp5(opts.sinceDays, 1, 365, 7),
|
|
6550
|
+
offset: clamp5(opts.offset, 0, 1e5, 0),
|
|
6461
6551
|
limit: clamp5(opts.limit, 1, 100, 30)
|
|
6462
6552
|
};
|
|
6463
6553
|
if (opts.code)
|
|
@@ -6466,6 +6556,10 @@ function buildViewsCommand() {
|
|
|
6466
6556
|
args.analyst = opts.analyst;
|
|
6467
6557
|
if (opts.institution)
|
|
6468
6558
|
args.institution = opts.institution;
|
|
6559
|
+
if (opts.from)
|
|
6560
|
+
args.from = opts.from;
|
|
6561
|
+
if (opts.to)
|
|
6562
|
+
args.to = opts.to;
|
|
6469
6563
|
await executeVerb(async (ctx) => viewsSpec.handler(args, ctx));
|
|
6470
6564
|
});
|
|
6471
6565
|
return cmd;
|
|
@@ -6523,6 +6617,7 @@ var ALL_VERB_SPECS = [
|
|
|
6523
6617
|
limitUpPoolSpec,
|
|
6524
6618
|
limitUpSummarySpec,
|
|
6525
6619
|
limitUpHistorySpec,
|
|
6620
|
+
limitUpAnalysisSpec,
|
|
6526
6621
|
chartSpec,
|
|
6527
6622
|
barsBatchSpec,
|
|
6528
6623
|
scanSpec,
|
|
@@ -7521,6 +7616,11 @@ Phase 5.2 (server endpoint).
|
|
|
7521
7616
|
description: "Mirrors `/v1/concepts/snapshot` contract (PR #442 review 二轮):\n\n- **With `codes=`** (per-key completeness): hot → warm → CH fallback\n per industry; missing keys populated.\n- **Without `codes`** (best-effort dump): union of\n `industry_snapshots:latest` and `industry_snapshots:last_close`\n Redis hashes. Missing industries are **not** backfilled from CH; use\n GET `/v1/industries` for a fully populated list.\n\n9:00–9:14 reset window suppresses both Redis hashes and CH fallback.\nEach item carries `_source` ∈ {`latest`, `last_close`, `ch_daily`}.\n申万一级 (level=1) ≈ 127 industries, 申万二级 (level=2) ≈ 348.\n",
|
|
7522
7617
|
example: "echopai industries snapshot"
|
|
7523
7618
|
},
|
|
7619
|
+
"limit-up.analysis": {
|
|
7620
|
+
summary: "A-share limit-up LLM attribution (per-stock leading theme + reason)",
|
|
7621
|
+
description: '涨停股 LLM 归因:逐股「主导题材 leading_concept + 概念标签 concept_tags +\n涨停理由 reason」。题材由 LLM 依据最新研报 / 机构观点 / 快讯判定,**不依赖概念\n图谱**(可含图谱里还没有的全新题材),每交易日北京 9:45 / 11:00 / 12:30 / 14:00 /\n14:40 / 18:00 共 6 个时段自动刷新(后跑时段在前次基础上 refine)。\n\n与 `limit-up/pool` 互补:pool 给"涨停了哪些票"(行情数据,stockpulse 上游),\n本接口给"为什么涨停 / 属什么题材"(LLM 归因,echopai api 上游)。pool 里的\ngraph-based `leading_concept` 是另一条数据,本接口的 `leading_concept` 才是\nLLM 判定结果。\n\n⚠️ **trade_date 不传 = 库内最新「已归因」交易日**(非自然最新交易日):归因每个\n时段按当时池快照写库,今日**首个时段(北京约 09:47)落库前**,不传 trade_date\n会返回**上一交易日**的归因(不是今日空结果)——agent 拿当日数据请显式传今日\ntrade_date 并以 `trade_date` / `generated_at` 字段核对时效。显式传某交易日而该日\n尚未归因时,`analyses` 为空。\n\n平面归属:本接口在 **vu 平面**(前端 apiFetch 同源),与 `market/status` 同款\n`x-audience: vu`;行情类 `limit-up/pool|summary|history` 在 sp 平面(前端 spFetch)。\n需要 `market:read` 或任一 `quote:*` scope。\n',
|
|
7622
|
+
example: "echopai limit-up analysis"
|
|
7623
|
+
},
|
|
7524
7624
|
"limit-up.history": {
|
|
7525
7625
|
summary: "A-share limit-up daily trend (last N days)",
|
|
7526
7626
|
description: "近 N 个交易日每日涨停数 + 最高连板 + 炸板数。涨停数 / 最高连板来源\n`market_limit_up_pool` (仅 status=limit_up);炸板数来源\n`market_breadth_intraday`(all_a_ex_st 当日最新分钟行)。\n\n响应按 trade_date asc 排序(旧日期在前)。需要 `quote:*` scope。\n",
|
|
@@ -7649,16 +7749,6 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
7649
7749
|
description: "Intraday turnover time series across one or more trading days.\n`trade_date` 缺省 → 今日(盘前 9:00 前返空 items);显式 YYYY-MM-DD 拉\n历史任意交易日的当日 + 之前 `days-1` 天。Response 含 `current_turnover`\n/ `predicted_total` / `delta_vs_yesterday` headline 字段。Requires\n`sentiment:read` scope.\n",
|
|
7650
7750
|
example: "echopai sentiment turnover"
|
|
7651
7751
|
},
|
|
7652
|
-
"squawk.audio": {
|
|
7653
|
-
summary: "Stream squawk TTS audio",
|
|
7654
|
-
description: "返回 squawk 项的 TTS 音频(MP3)。直接 stream,可作为 `<audio>` src 用。",
|
|
7655
|
-
example: "echopai squawk audio VALUE"
|
|
7656
|
-
},
|
|
7657
|
-
"squawk.waveform": {
|
|
7658
|
-
summary: "Waveform peaks for squawk audio",
|
|
7659
|
-
description: "返回 squawk 音频的预计算波形 peak 数据,用于客户端波形可视化。",
|
|
7660
|
-
example: "echopai squawk waveform VALUE"
|
|
7661
|
-
},
|
|
7662
7752
|
"stocks.hot": {
|
|
7663
7753
|
summary: "Today's hot-stock leaderboard",
|
|
7664
7754
|
description: "今日最热股票榜(来源 East-Money),按搜索 / 关注 / 评论综合分排序。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Command-line interface for the EchoPai Open Platform: stock-market data, news, analyst views, sentiment, signals, backtests.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://echopai.com",
|