echopai 2.4.0 → 2.6.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 +340 -134
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -229,6 +229,66 @@ var OPERATIONS = {
|
|
|
229
229
|
additionalProperties: false
|
|
230
230
|
}
|
|
231
231
|
},
|
|
232
|
+
"announcements.search": {
|
|
233
|
+
cliKey: "announcements.search",
|
|
234
|
+
cliName: "announcements search",
|
|
235
|
+
method: "GET",
|
|
236
|
+
path: "/v1/announcements/search",
|
|
237
|
+
description: "公告混合搜索:名称 / 代码做结构化过滤 + 关键词 BM25 全文(ParadeDB jieba,覆盖 title / ai_summary / content_md 前 1500 字)。q 含 6 位代码或内嵌 A 股名 → 按代码精确过滤;残余或纯文本 → BM25 按相关度×时间衰减排序;纯代码/名称则按 published_at 倒序列出。需要 `announcements:read` scope。",
|
|
238
|
+
summary: "Hybrid search over A-share announcements",
|
|
239
|
+
positional: [],
|
|
240
|
+
outputDefault: "json",
|
|
241
|
+
pagination: "offset",
|
|
242
|
+
stream: false,
|
|
243
|
+
billable: true,
|
|
244
|
+
idempotencyRequired: false,
|
|
245
|
+
scopesAny: [
|
|
246
|
+
"announcements:read"
|
|
247
|
+
],
|
|
248
|
+
sideEffect: "read",
|
|
249
|
+
dryRunSupported: false,
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: "object",
|
|
252
|
+
properties: {
|
|
253
|
+
q: {
|
|
254
|
+
type: "string",
|
|
255
|
+
minLength: 1,
|
|
256
|
+
maxLength: 100,
|
|
257
|
+
description: '名称 / 代码 / 关键词,可混合(如 "新华医疗 回购"、"600587"、"回购")。',
|
|
258
|
+
example: "新华医疗 回购"
|
|
259
|
+
},
|
|
260
|
+
type: {
|
|
261
|
+
type: "string",
|
|
262
|
+
maxLength: 64,
|
|
263
|
+
description: "分类 slug 过滤(与 /v1/announcements/feed 同集合)。"
|
|
264
|
+
},
|
|
265
|
+
since_days: {
|
|
266
|
+
type: "integer",
|
|
267
|
+
minimum: 1,
|
|
268
|
+
maximum: 1825,
|
|
269
|
+
default: 90,
|
|
270
|
+
description: "回看天数(最多 5 年)。"
|
|
271
|
+
},
|
|
272
|
+
limit: {
|
|
273
|
+
type: "integer",
|
|
274
|
+
minimum: 1,
|
|
275
|
+
maximum: 200,
|
|
276
|
+
default: 50,
|
|
277
|
+
description: "Max items per page."
|
|
278
|
+
},
|
|
279
|
+
offset: {
|
|
280
|
+
type: "integer",
|
|
281
|
+
minimum: 0,
|
|
282
|
+
default: 0,
|
|
283
|
+
description: "Pagination offset (items to skip)."
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
additionalProperties: false,
|
|
287
|
+
required: [
|
|
288
|
+
"q"
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
},
|
|
232
292
|
"announcements.stock": {
|
|
233
293
|
cliKey: "announcements.stock",
|
|
234
294
|
cliName: "announcements stock",
|
|
@@ -960,9 +1020,9 @@ Phase 5.2 (server endpoint).
|
|
|
960
1020
|
views_since_days: {
|
|
961
1021
|
type: "integer",
|
|
962
1022
|
minimum: 1,
|
|
963
|
-
maximum:
|
|
1023
|
+
maximum: 30,
|
|
964
1024
|
default: 7,
|
|
965
|
-
description: "Views lookback in days (1-
|
|
1025
|
+
description: "Views lookback in days (1-30; views feed caps at 30d. Use the `views` verb's from/to for longer history)."
|
|
966
1026
|
},
|
|
967
1027
|
news_hours: {
|
|
968
1028
|
type: "integer",
|
|
@@ -1489,6 +1549,39 @@ Phase 5.2 (server endpoint).
|
|
|
1489
1549
|
additionalProperties: false
|
|
1490
1550
|
}
|
|
1491
1551
|
},
|
|
1552
|
+
"limit-up.analysis": {
|
|
1553
|
+
cliKey: "limit-up.analysis",
|
|
1554
|
+
cliName: "limit-up analysis",
|
|
1555
|
+
method: "GET",
|
|
1556
|
+
path: "/v1/limit-up/analysis",
|
|
1557
|
+
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',
|
|
1558
|
+
summary: "A-share limit-up LLM attribution (per-stock leading theme + reason)",
|
|
1559
|
+
positional: [],
|
|
1560
|
+
outputDefault: "json",
|
|
1561
|
+
pagination: "none",
|
|
1562
|
+
stream: false,
|
|
1563
|
+
billable: true,
|
|
1564
|
+
idempotencyRequired: false,
|
|
1565
|
+
scopesAny: [
|
|
1566
|
+
"market:read",
|
|
1567
|
+
"quote:l1",
|
|
1568
|
+
"quote:l2",
|
|
1569
|
+
"quote:delayed"
|
|
1570
|
+
],
|
|
1571
|
+
sideEffect: "read",
|
|
1572
|
+
dryRunSupported: false,
|
|
1573
|
+
inputSchema: {
|
|
1574
|
+
type: "object",
|
|
1575
|
+
properties: {
|
|
1576
|
+
trade_date: {
|
|
1577
|
+
type: "string",
|
|
1578
|
+
format: "date",
|
|
1579
|
+
description: "ISO date YYYY-MM-DD;不传走库内**最新已归因**交易日(今日首段落库前会回退到上一交易日)。"
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
1582
|
+
additionalProperties: false
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1492
1585
|
"limit-up.history": {
|
|
1493
1586
|
cliKey: "limit-up.history",
|
|
1494
1587
|
cliName: "limit-up history",
|
|
@@ -1723,7 +1816,17 @@ Phase 5.2 (server endpoint).
|
|
|
1723
1816
|
minimum: 1,
|
|
1724
1817
|
maximum: 720,
|
|
1725
1818
|
default: 24,
|
|
1726
|
-
description: "
|
|
1819
|
+
description: "Rolling-feed lookback in hours (≤720 = 30d). For longer history use from/to."
|
|
1820
|
+
},
|
|
1821
|
+
from: {
|
|
1822
|
+
type: "string",
|
|
1823
|
+
format: "date",
|
|
1824
|
+
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)."
|
|
1825
|
+
},
|
|
1826
|
+
to: {
|
|
1827
|
+
type: "string",
|
|
1828
|
+
format: "date",
|
|
1829
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
1727
1830
|
},
|
|
1728
1831
|
limit: {
|
|
1729
1832
|
type: "integer",
|
|
@@ -1731,6 +1834,12 @@ Phase 5.2 (server endpoint).
|
|
|
1731
1834
|
maximum: 100,
|
|
1732
1835
|
default: 20,
|
|
1733
1836
|
description: "Max items per page."
|
|
1837
|
+
},
|
|
1838
|
+
offset: {
|
|
1839
|
+
type: "integer",
|
|
1840
|
+
minimum: 0,
|
|
1841
|
+
default: 0,
|
|
1842
|
+
description: "Pagination offset (page through long history ranges)."
|
|
1734
1843
|
}
|
|
1735
1844
|
},
|
|
1736
1845
|
additionalProperties: false,
|
|
@@ -1770,9 +1879,19 @@ Phase 5.2 (server endpoint).
|
|
|
1770
1879
|
since_hours: {
|
|
1771
1880
|
type: "integer",
|
|
1772
1881
|
minimum: 1,
|
|
1773
|
-
maximum:
|
|
1882
|
+
maximum: 720,
|
|
1774
1883
|
default: 24,
|
|
1775
|
-
description: "
|
|
1884
|
+
description: "Rolling-feed lookback in hours (≤720 = 30d). For longer history use from/to."
|
|
1885
|
+
},
|
|
1886
|
+
from: {
|
|
1887
|
+
type: "string",
|
|
1888
|
+
format: "date",
|
|
1889
|
+
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)."
|
|
1890
|
+
},
|
|
1891
|
+
to: {
|
|
1892
|
+
type: "string",
|
|
1893
|
+
format: "date",
|
|
1894
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
1776
1895
|
},
|
|
1777
1896
|
limit: {
|
|
1778
1897
|
type: "integer",
|
|
@@ -1780,6 +1899,12 @@ Phase 5.2 (server endpoint).
|
|
|
1780
1899
|
maximum: 100,
|
|
1781
1900
|
default: 20,
|
|
1782
1901
|
description: "Max items per page."
|
|
1902
|
+
},
|
|
1903
|
+
offset: {
|
|
1904
|
+
type: "integer",
|
|
1905
|
+
minimum: 0,
|
|
1906
|
+
default: 0,
|
|
1907
|
+
description: "Pagination offset (page through long history ranges)."
|
|
1783
1908
|
}
|
|
1784
1909
|
},
|
|
1785
1910
|
additionalProperties: false,
|
|
@@ -2330,70 +2455,6 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2330
2455
|
additionalProperties: false
|
|
2331
2456
|
}
|
|
2332
2457
|
},
|
|
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
2458
|
"stocks.hot": {
|
|
2398
2459
|
cliKey: "stocks.hot",
|
|
2399
2460
|
cliName: "stocks hot",
|
|
@@ -2586,9 +2647,19 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2586
2647
|
since_days: {
|
|
2587
2648
|
type: "integer",
|
|
2588
2649
|
minimum: 1,
|
|
2589
|
-
maximum:
|
|
2650
|
+
maximum: 30,
|
|
2590
2651
|
default: 7,
|
|
2591
|
-
description: "
|
|
2652
|
+
description: "Rolling-feed lookback in days (≤30, translated to hours upstream). For longer history use from/to instead."
|
|
2653
|
+
},
|
|
2654
|
+
from: {
|
|
2655
|
+
type: "string",
|
|
2656
|
+
format: "date",
|
|
2657
|
+
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)."
|
|
2658
|
+
},
|
|
2659
|
+
to: {
|
|
2660
|
+
type: "string",
|
|
2661
|
+
format: "date",
|
|
2662
|
+
description: "History range end (inclusive, China date; defaults to today). Only used with from."
|
|
2592
2663
|
},
|
|
2593
2664
|
limit: {
|
|
2594
2665
|
type: "integer",
|
|
@@ -2596,6 +2667,12 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
2596
2667
|
maximum: 100,
|
|
2597
2668
|
default: 30,
|
|
2598
2669
|
description: "Max items per page."
|
|
2670
|
+
},
|
|
2671
|
+
offset: {
|
|
2672
|
+
type: "integer",
|
|
2673
|
+
minimum: 0,
|
|
2674
|
+
default: 0,
|
|
2675
|
+
description: "Pagination offset (page through long history ranges)."
|
|
2599
2676
|
}
|
|
2600
2677
|
},
|
|
2601
2678
|
additionalProperties: false
|
|
@@ -2677,6 +2754,10 @@ function buildCommandTree(program, dispatch) {
|
|
|
2677
2754
|
const cmd = noun.command("feed");
|
|
2678
2755
|
attachOperation(cmd, OPERATIONS["announcements.feed"], dispatch);
|
|
2679
2756
|
}
|
|
2757
|
+
{
|
|
2758
|
+
const cmd = noun.command("search");
|
|
2759
|
+
attachOperation(cmd, OPERATIONS["announcements.search"], dispatch);
|
|
2760
|
+
}
|
|
2680
2761
|
{
|
|
2681
2762
|
const cmd = noun.command("stock");
|
|
2682
2763
|
attachOperation(cmd, OPERATIONS["announcements.stock"], dispatch);
|
|
@@ -2821,6 +2902,10 @@ function buildCommandTree(program, dispatch) {
|
|
|
2821
2902
|
{
|
|
2822
2903
|
const noun = program.command("limit-up");
|
|
2823
2904
|
noun.description("limit-up commands");
|
|
2905
|
+
{
|
|
2906
|
+
const cmd = noun.command("analysis");
|
|
2907
|
+
attachOperation(cmd, OPERATIONS["limit-up.analysis"], dispatch);
|
|
2908
|
+
}
|
|
2824
2909
|
{
|
|
2825
2910
|
const cmd = noun.command("history");
|
|
2826
2911
|
attachOperation(cmd, OPERATIONS["limit-up.history"], dispatch);
|
|
@@ -2938,18 +3023,6 @@ function buildCommandTree(program, dispatch) {
|
|
|
2938
3023
|
attachOperation(cmd, OPERATIONS["sentiment.turnover"], dispatch);
|
|
2939
3024
|
}
|
|
2940
3025
|
}
|
|
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
3026
|
{
|
|
2954
3027
|
const noun = program.command("stocks");
|
|
2955
3028
|
noun.description("stocks commands");
|
|
@@ -3756,7 +3829,7 @@ import os2 from "node:os";
|
|
|
3756
3829
|
import path2 from "node:path";
|
|
3757
3830
|
|
|
3758
3831
|
// src/version.ts
|
|
3759
|
-
var CLI_VERSION = "2.
|
|
3832
|
+
var CLI_VERSION = "2.5.0";
|
|
3760
3833
|
|
|
3761
3834
|
// src/runtime/update_check.ts
|
|
3762
3835
|
var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
|
|
@@ -4583,6 +4656,32 @@ function buildQueryString3(params, method) {
|
|
|
4583
4656
|
var CODE_RE = /^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/;
|
|
4584
4657
|
var ISO_DATETIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/;
|
|
4585
4658
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4659
|
+
var announcementsSearchSpec = {
|
|
4660
|
+
name: "announcements_search",
|
|
4661
|
+
description: 'Hybrid search over A-share announcements (cninfo): mix Chinese company name / 6-digit code / free-text keyword in one query. Name or code → structured filter by stock; keyword → BM25 full-text (ParadeDB jieba over title + ai_summary + first 1500 chars of body), ranked by relevance × recency. Use when you DON\'T already have a canonical_code — e.g. "新华医疗 回购", "600587", or just "回购". For a known stock\'s full disclosure history prefer `announcements_stock`; for the market-wide recent feed prefer `announcements_feed`.',
|
|
4662
|
+
inputSchema: {
|
|
4663
|
+
q: z.string().min(1).max(100).describe('名称 / 代码 / 关键词,可混合(如 "新华医疗 回购"、"600587"、"回购")'),
|
|
4664
|
+
type: z.string().max(64).optional().describe("分类 slug 过滤(与 feed 同集合)"),
|
|
4665
|
+
since_days: z.number().int().min(1).max(1825).default(90).describe("回看天数(1-1825,默认 90)"),
|
|
4666
|
+
limit: z.number().int().min(1).max(200).default(50).describe("Max items (1-200, default 50)"),
|
|
4667
|
+
offset: z.number().int().min(0).default(0).describe("Pagination offset")
|
|
4668
|
+
},
|
|
4669
|
+
handler: async (args, ctx) => {
|
|
4670
|
+
const op = OPERATIONS["announcements.search"];
|
|
4671
|
+
if (!op)
|
|
4672
|
+
throw new Error("announcements.search op missing");
|
|
4673
|
+
const callArgs = {
|
|
4674
|
+
q: args.q,
|
|
4675
|
+
since_days: args.since_days,
|
|
4676
|
+
limit: args.limit,
|
|
4677
|
+
offset: args.offset
|
|
4678
|
+
};
|
|
4679
|
+
if (args.type)
|
|
4680
|
+
callArgs.type = args.type;
|
|
4681
|
+
return callOp(op, callArgs, ctx);
|
|
4682
|
+
},
|
|
4683
|
+
backingOps: ["announcements.search"]
|
|
4684
|
+
};
|
|
4586
4685
|
var announcementsFeedSpec = {
|
|
4587
4686
|
name: "announcements_feed",
|
|
4588
4687
|
description: "Recent A-share announcement feed (cninfo source, sorted by published_at desc). Filter by `type` slug (e.g. annual_report / equity_change / restructuring) or `since` lower bound. Use when surveying market-wide disclosures over a recent window — for a specific stock prefer `announcements_stock`.",
|
|
@@ -4659,7 +4758,27 @@ function clampInt(raw, min, max, fallback) {
|
|
|
4659
4758
|
return n;
|
|
4660
4759
|
}
|
|
4661
4760
|
function buildAnnouncementsCommand() {
|
|
4662
|
-
const cmd = new Command3("announcements").description("A-share announcements (cninfo) — recent feed, per-stock history, single-item detail.");
|
|
4761
|
+
const cmd = new Command3("announcements").description("A-share announcements (cninfo) — hybrid search, recent feed, per-stock history, single-item detail.");
|
|
4762
|
+
const search = cmd.command("search").description(announcementsSearchSpec.description);
|
|
4763
|
+
search.addOption(new Option2("--q <text>", "名称 / 代码 / 关键词,可混合").makeOptionMandatory(true));
|
|
4764
|
+
search.addOption(new Option2("--type <slug>", "Filter by 分类 slug"));
|
|
4765
|
+
search.addOption(new Option2("--since-days <n>", "Lookback window in days (1-1825)").default("90"));
|
|
4766
|
+
search.addOption(new Option2("--limit <n>", "Max items (1-200)").default("50"));
|
|
4767
|
+
search.addOption(new Option2("--offset <n>", "Pagination offset").default("0"));
|
|
4768
|
+
search.action(async (opts) => {
|
|
4769
|
+
if (!OPERATIONS["announcements.search"]) {
|
|
4770
|
+
emitVerbError("internal_error", "announcements.search missing", undefined, 2);
|
|
4771
|
+
}
|
|
4772
|
+
const args = {
|
|
4773
|
+
q: opts.q,
|
|
4774
|
+
since_days: clampInt(opts.sinceDays, 1, 1825, 90),
|
|
4775
|
+
limit: clampInt(opts.limit, 1, 200, 50),
|
|
4776
|
+
offset: Math.max(0, Math.floor(Number(opts.offset)) || 0)
|
|
4777
|
+
};
|
|
4778
|
+
if (opts.type)
|
|
4779
|
+
args.type = opts.type;
|
|
4780
|
+
await executeVerb(async (ctx) => announcementsSearchSpec.handler(args, ctx));
|
|
4781
|
+
});
|
|
4663
4782
|
const feed = cmd.command("feed").description(announcementsFeedSpec.description);
|
|
4664
4783
|
feed.addOption(new Option2("--type <slug>", "Filter by 分类 slug (annual_report, equity_change, ...)"));
|
|
4665
4784
|
feed.addOption(new Option2("--since <ISO-datetime>", "Lower bound published_at (RFC3339)"));
|
|
@@ -5178,7 +5297,7 @@ var digestSpec = {
|
|
|
5178
5297
|
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
5298
|
inputSchema: {
|
|
5180
5299
|
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(
|
|
5300
|
+
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
5301
|
news_hours: z5.number().int().min(1).max(168).default(24).describe("Lookback for news bucket (hours, default 24 — event horizon)"),
|
|
5183
5302
|
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
5303
|
},
|
|
@@ -5391,7 +5510,7 @@ function clamp2(raw, min, max, fallback) {
|
|
|
5391
5510
|
function buildDigestCommand() {
|
|
5392
5511
|
const cmd = new Command7(digestSpec.name).description(digestSpec.description);
|
|
5393
5512
|
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-
|
|
5513
|
+
cmd.addOption(new Option6("--views-since-days <n>", "Views lookback in days (1-30)").default("7"));
|
|
5395
5514
|
cmd.addOption(new Option6("--news-hours <n>", "News lookback in hours (1-168)").default("24"));
|
|
5396
5515
|
cmd.addOption(new Option6("--limit-per-bucket <n>", "Items per bucket (1-50)").default("10"));
|
|
5397
5516
|
cmd.action(async (opts) => {
|
|
@@ -5400,7 +5519,7 @@ function buildDigestCommand() {
|
|
|
5400
5519
|
}
|
|
5401
5520
|
const args = {
|
|
5402
5521
|
code: opts.code,
|
|
5403
|
-
views_since_days: clamp2(opts.viewsSinceDays, 1,
|
|
5522
|
+
views_since_days: clamp2(opts.viewsSinceDays, 1, 30, 7),
|
|
5404
5523
|
news_hours: clamp2(opts.newsHours, 1, 168, 24),
|
|
5405
5524
|
limit_per_bucket: clamp2(opts.limitPerBucket, 1, 50, 10)
|
|
5406
5525
|
};
|
|
@@ -5771,8 +5890,25 @@ var limitUpHistorySpec = {
|
|
|
5771
5890
|
},
|
|
5772
5891
|
backingOps: ["limit-up.history"]
|
|
5773
5892
|
};
|
|
5893
|
+
var limitUpAnalysisSpec = {
|
|
5894
|
+
name: "limit_up_analysis",
|
|
5895
|
+
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.",
|
|
5896
|
+
inputSchema: {
|
|
5897
|
+
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)")
|
|
5898
|
+
},
|
|
5899
|
+
handler: async (args, ctx) => {
|
|
5900
|
+
const op = OPERATIONS["limit-up.analysis"];
|
|
5901
|
+
if (!op)
|
|
5902
|
+
throw new Error("limit-up.analysis op missing");
|
|
5903
|
+
const callArgs = {};
|
|
5904
|
+
if (args.trade_date)
|
|
5905
|
+
callArgs.trade_date = args.trade_date;
|
|
5906
|
+
return callOp(op, callArgs, ctx);
|
|
5907
|
+
},
|
|
5908
|
+
backingOps: ["limit-up.analysis"]
|
|
5909
|
+
};
|
|
5774
5910
|
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).");
|
|
5911
|
+
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
5912
|
const pool = cmd.command("pool").description(limitUpPoolSpec.description);
|
|
5777
5913
|
pool.addOption(new Option9("--trade-date <YYYY-MM-DD>", "Trade date; omit for latest"));
|
|
5778
5914
|
pool.addOption(new Option9("--include <s>", "active = 排除 ST/退市; all = 全部").choices([...INCLUDE_VALUES]).default("active"));
|
|
@@ -5809,6 +5945,17 @@ function buildLimitUpCommand() {
|
|
|
5809
5945
|
}
|
|
5810
5946
|
await executeVerb(async (ctx) => limitUpHistorySpec.handler({ days: clampInt5(opts.days, 1, 250, 30) }, ctx));
|
|
5811
5947
|
});
|
|
5948
|
+
const analysis = cmd.command("analysis").description(limitUpAnalysisSpec.description);
|
|
5949
|
+
analysis.addOption(new Option9("--trade-date <YYYY-MM-DD>", "Trade date; omit for latest"));
|
|
5950
|
+
analysis.action(async (opts) => {
|
|
5951
|
+
if (!OPERATIONS["limit-up.analysis"]) {
|
|
5952
|
+
emitVerbError("internal_error", "limit-up.analysis missing", undefined, 2);
|
|
5953
|
+
}
|
|
5954
|
+
const args = {};
|
|
5955
|
+
if (opts.tradeDate)
|
|
5956
|
+
args.trade_date = opts.tradeDate;
|
|
5957
|
+
await executeVerb(async (ctx) => limitUpAnalysisSpec.handler(args, ctx));
|
|
5958
|
+
});
|
|
5812
5959
|
return cmd;
|
|
5813
5960
|
}
|
|
5814
5961
|
// src/verbs/lookup.ts
|
|
@@ -6019,29 +6166,46 @@ var newsSpec = {
|
|
|
6019
6166
|
inputSchema: {
|
|
6020
6167
|
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
6168
|
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
|
-
|
|
6169
|
+
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."),
|
|
6170
|
+
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."),
|
|
6171
|
+
to: z11.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
|
|
6172
|
+
offset: z11.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
|
|
6173
|
+
limit: z11.number().int().min(1).max(100).default(20).describe("Max items per page")
|
|
6024
6174
|
},
|
|
6025
6175
|
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"];
|
|
6176
|
+
const { opKey, callArgs } = buildNewsCall(args);
|
|
6177
|
+
const op = OPERATIONS[opKey];
|
|
6039
6178
|
if (!op)
|
|
6040
|
-
throw new Error(
|
|
6041
|
-
return callOp(op,
|
|
6179
|
+
throw new Error(`${opKey} op missing`);
|
|
6180
|
+
return callOp(op, callArgs, ctx);
|
|
6042
6181
|
},
|
|
6043
6182
|
backingOps: ["news.list", "news.search", "news.feed"]
|
|
6044
6183
|
};
|
|
6184
|
+
function buildNewsCall(args) {
|
|
6185
|
+
const hasRange = typeof args.from === "string" && args.from.length > 0;
|
|
6186
|
+
const withWindow = (a) => {
|
|
6187
|
+
if (typeof args.limit === "number")
|
|
6188
|
+
a.limit = args.limit;
|
|
6189
|
+
if (typeof args.offset === "number")
|
|
6190
|
+
a.offset = args.offset;
|
|
6191
|
+
if (hasRange) {
|
|
6192
|
+
a.from = args.from;
|
|
6193
|
+
if (typeof args.to === "string" && args.to.length > 0)
|
|
6194
|
+
a.to = args.to;
|
|
6195
|
+
} else {
|
|
6196
|
+
a.since_hours = args.hours;
|
|
6197
|
+
}
|
|
6198
|
+
return a;
|
|
6199
|
+
};
|
|
6200
|
+
if (args.code)
|
|
6201
|
+
return { opKey: "news.list", callArgs: withWindow({ security: args.code }) };
|
|
6202
|
+
if (args.query)
|
|
6203
|
+
return { opKey: "news.search", callArgs: withWindow({ query: args.query }) };
|
|
6204
|
+
if (hasRange) {
|
|
6205
|
+
throw new Error("range history (from/to) requires --code or --query; the bare feed has no narrowing filter");
|
|
6206
|
+
}
|
|
6207
|
+
return { opKey: "news.feed", callArgs: {} };
|
|
6208
|
+
}
|
|
6045
6209
|
function clamp3(raw, min, max, fallback) {
|
|
6046
6210
|
const n = Math.floor(Number(raw));
|
|
6047
6211
|
if (!Number.isFinite(n))
|
|
@@ -6056,17 +6220,25 @@ function buildNewsCommand() {
|
|
|
6056
6220
|
const cmd = new Command14(newsSpec.name).description(newsSpec.description);
|
|
6057
6221
|
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
6222
|
cmd.addOption(new Option12("--query <text>", "Free-text query; omit for time-window feed"));
|
|
6059
|
-
cmd.addOption(new Option12("--hours <n>", "
|
|
6223
|
+
cmd.addOption(new Option12("--hours <n>", "Rolling-feed lookback (1-168 hours)").default("24"));
|
|
6224
|
+
cmd.addOption(new Option12("--from <date>", "History range start YYYY-MM-DD (range mode; needs --code or --query)"));
|
|
6225
|
+
cmd.addOption(new Option12("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
|
|
6226
|
+
cmd.addOption(new Option12("--offset <n>", "Pagination offset for long history").default("0"));
|
|
6060
6227
|
cmd.addOption(new Option12("--limit <n>", "Max items").default("20"));
|
|
6061
6228
|
cmd.action(async (opts) => {
|
|
6062
6229
|
const args = {
|
|
6063
6230
|
hours: clamp3(opts.hours, 1, 168, 24),
|
|
6231
|
+
offset: clamp3(opts.offset, 0, 1e5, 0),
|
|
6064
6232
|
limit: clamp3(opts.limit, 1, 100, 20)
|
|
6065
6233
|
};
|
|
6066
6234
|
if (opts.code)
|
|
6067
6235
|
args.code = opts.code;
|
|
6068
6236
|
if (opts.query)
|
|
6069
6237
|
args.query = opts.query;
|
|
6238
|
+
if (opts.from)
|
|
6239
|
+
args.from = opts.from;
|
|
6240
|
+
if (opts.to)
|
|
6241
|
+
args.to = opts.to;
|
|
6070
6242
|
await executeVerb(async (ctx) => newsSpec.handler(args, ctx));
|
|
6071
6243
|
});
|
|
6072
6244
|
return cmd;
|
|
@@ -6147,11 +6319,11 @@ import { Command as Command17, Option as Option15 } from "commander";
|
|
|
6147
6319
|
import { z as z14 } from "zod";
|
|
6148
6320
|
var searchSpec = {
|
|
6149
6321
|
name: "search",
|
|
6150
|
-
description: "
|
|
6322
|
+
description: "Structured-first hybrid search across analyst views + news (views weighted higher per feedback_views_over_news). Three lanes — entity (company codes / names, analyst names), concept-graph hub (theme alias → concept → constituents & research), and ParadeDB BM25 full-text (jieba) — fused with RRF. Deterministic & explainable: no vector kNN, no LLM rerank. Best for theme/concept queries: 'CPO' brings out 光模块/光通信/光芯片; '减肥药' brings out 创新药; '智驾' brings out 智能驾驶. Use `news` or `views` instead if you need strict time-window listing.",
|
|
6151
6323
|
inputSchema: {
|
|
6152
6324
|
q: z14.string().min(1).max(200).describe("Free-text query (Chinese or English)"),
|
|
6153
6325
|
type: z14.enum(["news", "views", "all"]).default("all").describe("Search scope (views weighted higher in 'all')"),
|
|
6154
|
-
mode: z14.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid =
|
|
6326
|
+
mode: z14.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid = entity + concept-graph + BM25 fused; exact = BM25 keyword only"),
|
|
6155
6327
|
hours: z14.number().int().min(1).max(720).optional().describe("Lookback window in hours; omit for no time limit"),
|
|
6156
6328
|
limit: z14.number().int().min(1).max(50).default(20).describe("Max items returned")
|
|
6157
6329
|
},
|
|
@@ -6389,27 +6561,51 @@ var viewsSpec = {
|
|
|
6389
6561
|
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
6562
|
analyst: z16.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
|
|
6391
6563
|
institution: z16.string().optional().describe("Broker / institution Chinese name"),
|
|
6392
|
-
since_days: z16.number().int().min(1).max(
|
|
6393
|
-
|
|
6564
|
+
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."),
|
|
6565
|
+
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."),
|
|
6566
|
+
to: z16.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
|
|
6567
|
+
offset: z16.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
|
|
6568
|
+
limit: z16.number().int().min(1).max(100).default(30).describe("Max items per page")
|
|
6394
6569
|
},
|
|
6395
6570
|
handler: async (args, ctx) => {
|
|
6396
6571
|
const op = OPERATIONS["views.recent"];
|
|
6397
6572
|
if (!op)
|
|
6398
6573
|
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);
|
|
6574
|
+
return callOp(op, buildViewsCallArgs(args), ctx);
|
|
6410
6575
|
},
|
|
6411
6576
|
backingOps: ["views.recent"]
|
|
6412
6577
|
};
|
|
6578
|
+
function buildViewsCallArgs(args) {
|
|
6579
|
+
const hasFilter = Boolean(args.code || args.analyst || args.institution);
|
|
6580
|
+
const sinceDays = typeof args.since_days === "number" ? args.since_days : 7;
|
|
6581
|
+
const callArgs = {};
|
|
6582
|
+
if (typeof args.limit === "number")
|
|
6583
|
+
callArgs.limit = args.limit;
|
|
6584
|
+
if (args.code)
|
|
6585
|
+
callArgs.security = args.code;
|
|
6586
|
+
if (args.analyst)
|
|
6587
|
+
callArgs.analyst = args.analyst;
|
|
6588
|
+
if (args.institution)
|
|
6589
|
+
callArgs.institution = args.institution;
|
|
6590
|
+
if (typeof args.offset === "number")
|
|
6591
|
+
callArgs.offset = args.offset;
|
|
6592
|
+
const explicitRange = typeof args.from === "string" && args.from.length > 0;
|
|
6593
|
+
if (explicitRange || sinceDays > 30) {
|
|
6594
|
+
if (!hasFilter) {
|
|
6595
|
+
throw new Error("range history (from/to or since_days>30) requires a narrowing filter: --code / --analyst / --institution");
|
|
6596
|
+
}
|
|
6597
|
+
callArgs.from = explicitRange ? args.from : chinaDateMinusDays(sinceDays);
|
|
6598
|
+
if (typeof args.to === "string" && args.to.length > 0)
|
|
6599
|
+
callArgs.to = args.to;
|
|
6600
|
+
} else {
|
|
6601
|
+
callArgs.since_days = sinceDays;
|
|
6602
|
+
}
|
|
6603
|
+
return callArgs;
|
|
6604
|
+
}
|
|
6605
|
+
function chinaDateMinusDays(days) {
|
|
6606
|
+
const chinaMs = Date.now() + 8 * 3600 * 1000 - days * 86400 * 1000;
|
|
6607
|
+
return new Date(chinaMs).toISOString().slice(0, 10);
|
|
6608
|
+
}
|
|
6413
6609
|
var reportSpec = {
|
|
6414
6610
|
name: "report",
|
|
6415
6611
|
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 +6646,18 @@ function buildViewsCommand() {
|
|
|
6450
6646
|
cmd.addOption(new Option17("--code <canonical_code>", "Filter by security canonical_code"));
|
|
6451
6647
|
cmd.addOption(new Option17("--analyst <name>", "Analyst Chinese name (exact / fuzzy)"));
|
|
6452
6648
|
cmd.addOption(new Option17("--institution <name>", "Broker / institution Chinese name"));
|
|
6453
|
-
cmd.addOption(new Option17("--since-days <n>", "Lookback
|
|
6649
|
+
cmd.addOption(new Option17("--since-days <n>", "Lookback days (1-365). ≤30 = feed; >30 = range history (needs a filter, Pro-only)").default("7"));
|
|
6650
|
+
cmd.addOption(new Option17("--from <date>", "History range start YYYY-MM-DD (range mode; needs a filter)"));
|
|
6651
|
+
cmd.addOption(new Option17("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
|
|
6652
|
+
cmd.addOption(new Option17("--offset <n>", "Pagination offset for long history").default("0"));
|
|
6454
6653
|
cmd.addOption(new Option17("--limit <n>", "Max items (1-100)").default("30"));
|
|
6455
6654
|
cmd.action(async (opts) => {
|
|
6456
6655
|
if (!OPERATIONS["views.recent"]) {
|
|
6457
6656
|
emitVerbError("internal_error", "views.recent missing from codegen", undefined, 2);
|
|
6458
6657
|
}
|
|
6459
6658
|
const args = {
|
|
6460
|
-
since_days: clamp5(opts.sinceDays, 1,
|
|
6659
|
+
since_days: clamp5(opts.sinceDays, 1, 365, 7),
|
|
6660
|
+
offset: clamp5(opts.offset, 0, 1e5, 0),
|
|
6461
6661
|
limit: clamp5(opts.limit, 1, 100, 30)
|
|
6462
6662
|
};
|
|
6463
6663
|
if (opts.code)
|
|
@@ -6466,6 +6666,10 @@ function buildViewsCommand() {
|
|
|
6466
6666
|
args.analyst = opts.analyst;
|
|
6467
6667
|
if (opts.institution)
|
|
6468
6668
|
args.institution = opts.institution;
|
|
6669
|
+
if (opts.from)
|
|
6670
|
+
args.from = opts.from;
|
|
6671
|
+
if (opts.to)
|
|
6672
|
+
args.to = opts.to;
|
|
6469
6673
|
await executeVerb(async (ctx) => viewsSpec.handler(args, ctx));
|
|
6470
6674
|
});
|
|
6471
6675
|
return cmd;
|
|
@@ -6502,6 +6706,7 @@ var ALL_VERB_SPECS = [
|
|
|
6502
6706
|
viewsSpec,
|
|
6503
6707
|
reportSpec,
|
|
6504
6708
|
newsSpec,
|
|
6709
|
+
announcementsSearchSpec,
|
|
6505
6710
|
announcementsFeedSpec,
|
|
6506
6711
|
announcementsStockSpec,
|
|
6507
6712
|
announcementsDetailSpec,
|
|
@@ -6523,6 +6728,7 @@ var ALL_VERB_SPECS = [
|
|
|
6523
6728
|
limitUpPoolSpec,
|
|
6524
6729
|
limitUpSummarySpec,
|
|
6525
6730
|
limitUpHistorySpec,
|
|
6731
|
+
limitUpAnalysisSpec,
|
|
6526
6732
|
chartSpec,
|
|
6527
6733
|
barsBatchSpec,
|
|
6528
6734
|
scanSpec,
|
|
@@ -7347,6 +7553,11 @@ var HELP = {
|
|
|
7347
7553
|
description: "A 股公告 feed,按 published_at 降序、同日按 cninfo source_id 降序。可按 type / since 过滤。需要 `announcements:read` scope。",
|
|
7348
7554
|
example: "echopai announcements feed"
|
|
7349
7555
|
},
|
|
7556
|
+
"announcements.search": {
|
|
7557
|
+
summary: "Hybrid search over A-share announcements",
|
|
7558
|
+
description: "公告混合搜索:名称 / 代码做结构化过滤 + 关键词 BM25 全文(ParadeDB jieba,覆盖 title / ai_summary / content_md 前 1500 字)。q 含 6 位代码或内嵌 A 股名 → 按代码精确过滤;残余或纯文本 → BM25 按相关度×时间衰减排序;纯代码/名称则按 published_at 倒序列出。需要 `announcements:read` scope。",
|
|
7559
|
+
example: "echopai announcements search --q 新华医疗 回购"
|
|
7560
|
+
},
|
|
7350
7561
|
"announcements.stock": {
|
|
7351
7562
|
summary: "List announcements for a specific A-share security",
|
|
7352
7563
|
description: "指定股票代码的公告列表(含历史窗口)。代码接受 6 位纯数字或 SSE:600519 / SZSE:000001 / BSE:430000 形式。需要 `announcements:read` scope。",
|
|
@@ -7521,6 +7732,11 @@ Phase 5.2 (server endpoint).
|
|
|
7521
7732
|
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
7733
|
example: "echopai industries snapshot"
|
|
7523
7734
|
},
|
|
7735
|
+
"limit-up.analysis": {
|
|
7736
|
+
summary: "A-share limit-up LLM attribution (per-stock leading theme + reason)",
|
|
7737
|
+
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',
|
|
7738
|
+
example: "echopai limit-up analysis"
|
|
7739
|
+
},
|
|
7524
7740
|
"limit-up.history": {
|
|
7525
7741
|
summary: "A-share limit-up daily trend (last N days)",
|
|
7526
7742
|
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 +7865,6 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
|
|
|
7649
7865
|
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
7866
|
example: "echopai sentiment turnover"
|
|
7651
7867
|
},
|
|
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
7868
|
"stocks.hot": {
|
|
7663
7869
|
summary: "Today's hot-stock leaderboard",
|
|
7664
7870
|
description: "今日最热股票榜(来源 East-Money),按搜索 / 关注 / 评论综合分排序。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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",
|