echopai 2.6.0 → 2.7.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.
Files changed (2) hide show
  1. package/dist/bin.js +398 -838
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  // src/bin.ts
4
4
  import { spawn } from "node:child_process";
5
- import { existsSync as existsSync4 } from "node:fs";
6
- import { fileURLToPath as fileURLToPath2 } from "node:url";
5
+ import { fileURLToPath } from "node:url";
7
6
  import { Command as Command31, Option as Option23 } from "commander";
8
7
 
9
8
  // src/_generated/commands.ts
@@ -16,8 +15,8 @@ var OPERATIONS = {
16
15
  cliName: "agent session-end",
17
16
  method: "POST",
18
17
  path: "/v1/agent/session/{id}/end",
19
- description: "关闭 `/v1/agent/session/start` 开启的会话,最终用量提交入库。",
20
- summary: "End a billable agent session",
18
+ description: "Closes a session opened by `/v1/agent/session/start` and commits its final usage.",
19
+ summary: "Close a billable agent session.",
21
20
  positional: [
22
21
  "id"
23
22
  ],
@@ -51,8 +50,8 @@ var OPERATIONS = {
51
50
  cliName: "agent session-start",
52
51
  method: "POST",
53
52
  path: "/v1/agent/session/start",
54
- description: "开启 agent kind 凭据的计费会话。返回 session_id,用于将多次调用归并到一次 billable 单位。需要 `Idempotency-Key` 头。",
55
- summary: "Start a billable agent session",
53
+ description: "Opens a billable session for an agent credential and returns a session_id that groups subsequent calls into a single billable unit. Requires an `Idempotency-Key` header.",
54
+ summary: "Open a billable agent session.",
56
55
  positional: [],
57
56
  outputDefault: "json",
58
57
  pagination: "none",
@@ -116,8 +115,8 @@ var OPERATIONS = {
116
115
  cliName: "agent session-usage",
117
116
  method: "GET",
118
117
  path: "/v1/agent/session/{id}/usage",
119
- description: "返回 session 内累计 billable 调用次数、各 scope 配额消耗。",
120
- summary: "Get cumulative usage of an agent session",
118
+ description: "Returns the cumulative billable call count and per-scope quota consumption for the given session.",
119
+ summary: "Get cumulative usage for an agent session.",
121
120
  positional: [
122
121
  "id"
123
122
  ],
@@ -151,8 +150,8 @@ var OPERATIONS = {
151
150
  cliName: "announcements detail",
152
151
  method: "GET",
153
152
  path: "/v1/announcements/{announcement_id}",
154
- description: "返回公告完整内容:content_md、content_text、cninfo 元数据、解析状态。",
155
- summary: "Fetch a single announcement by id",
153
+ description: "Returns the full content of one announcement: body (markdown and plain text), metadata, and parse status.",
154
+ summary: "Get a single announcement by id.",
156
155
  positional: [
157
156
  "announcement_id"
158
157
  ],
@@ -186,8 +185,8 @@ var OPERATIONS = {
186
185
  cliName: "announcements feed",
187
186
  method: "GET",
188
187
  path: "/v1/announcements/feed",
189
- description: "A 股公告 feed,按 published_at 降序、同日按 cninfo source_id 降序。可按 type / since 过滤。需要 `announcements:read` scope",
190
- summary: "List recent A-share announcements",
188
+ description: "Returns a feed of recent A-share corporate announcements, sorted by publication date descending. Filter by `type` or `since`. Requires the `announcements:read` scope.",
189
+ summary: "List recent A-share announcements.",
191
190
  positional: [],
192
191
  outputDefault: "json",
193
192
  pagination: "offset",
@@ -234,8 +233,8 @@ var OPERATIONS = {
234
233
  cliName: "announcements search",
235
234
  method: "GET",
236
235
  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",
236
+ description: "Hybrid search over A-share announcements: structured filtering by security name or code, combined with full-text keyword search (over title, AI summary, and the first ~1500 characters of the body). A query containing a 6-digit code or an embedded security name is filtered by that security; remaining free text is ranked by relevance with time decay; a pure code or name lists results by publication date descending. Requires the `announcements:read` scope.",
237
+ summary: "Search A-share announcements.",
239
238
  positional: [],
240
239
  outputDefault: "json",
241
240
  pagination: "offset",
@@ -294,8 +293,8 @@ var OPERATIONS = {
294
293
  cliName: "announcements stock",
295
294
  method: "GET",
296
295
  path: "/v1/announcements/stock",
297
- description: "指定股票代码的公告列表(含历史窗口)。代码接受 6 位纯数字或 SSE:600519 / SZSE:000001 / BSE:430000 形式。需要 `announcements:read` scope",
298
- summary: "List announcements for a specific A-share security",
296
+ description: "Returns the announcement history for one A-share security over a date window. Accepts a 6-digit code or canonical form (e.g. `SSE:600519`). Requires the `announcements:read` scope.",
297
+ summary: "List announcements for one security.",
299
298
  positional: [],
300
299
  outputDefault: "json",
301
300
  pagination: "offset",
@@ -353,17 +352,8 @@ var OPERATIONS = {
353
352
  cliName: "auth whoami",
354
353
  method: "GET",
355
354
  path: "/v1/auth/whoami",
356
- description: `Returns the calling token's kind, scopes, audience, app metadata,
357
- rate_limit, allowed_clients, agent_budget (if kind=agent), api_version,
358
- feature_flags. Any valid JWT can call — no specific scope required.
359
-
360
- CLI/MCP call this once at startup, cache 5 minutes in-process, and use
361
- the response to derive \`verbs.available\` (intersection of curated verb
362
- scopes with token scopes) and to populate \`echopai doctor\` checks.
363
-
364
- See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
365
- `,
366
- summary: "Token introspection (CLI/MCP capability discovery)",
355
+ description: "Returns the calling token's kind, scopes, audience, app metadata, rate limit, allowed clients, agent budget (when kind=agent), API version, and feature flags. Any valid token may call this; no specific scope is required. Clients typically call it once at startup and cache the result briefly to determine which commands are available.",
356
+ summary: "Introspect the calling token (capability discovery).",
367
357
  positional: [],
368
358
  outputDefault: "json",
369
359
  pagination: "none",
@@ -384,8 +374,8 @@ See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
384
374
  cliName: "bars daily",
385
375
  method: "GET",
386
376
  path: "/v1/bars/daily",
387
- description: "Daily OHLC bars for one A-share security over a date range. Returns open / high / low / close / volume / turnover per trading day. Requires `bars:30d` (last 30 trading days) or `bars:full` (full history) scope.",
388
- summary: "Daily OHLC bars for one A-share security",
377
+ description: "Returns daily open/high/low/close/volume/turnover for one A-share security over a date range (canonical code, e.g. `SSE:600519`). End-of-day data. Requires the `bars:30d` scope (last 30 trading days) or `bars:full` scope (full history).",
378
+ summary: "List daily OHLC bars for one A-share security.",
389
379
  positional: [],
390
380
  outputDefault: "json",
391
381
  pagination: "none",
@@ -433,8 +423,8 @@ See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
433
423
  cliName: "bars daily-batch",
434
424
  method: "GET",
435
425
  path: "/v1/bars/daily-batch",
436
- description: "Batch daily OHLC bars for multiple A-share securities. Up to 100 codes\nper call, up to 1-year date range. Single round-trip; ClickHouse `IN(...)`\nunder the hood.\n\nReturns partial success envelope: codes denied by `allowed_securities`\nappear in `errors[]`, others in `items[]` (with empty `bars[]` if no\ndata found). Order in `items[]` matches input order.\n\nRequires `bars:30d` or `bars:full` scope.\n",
437
- summary: "Batch daily OHLC bars for up to 100 A-share securities",
426
+ description: "Returns daily OHLC bars for multiple A-share securities in one call: up to 100 codes and up to a one-year date range. Responses use a partial-success envelope: codes the token may not access are reported in `errors[]`, the rest in `items[]` (with an empty `bars[]` when no data is found), preserving input order. Requires the `bars:30d` or `bars:full` scope.",
427
+ summary: "Batch daily OHLC bars for up to 100 securities.",
438
428
  positional: [],
439
429
  outputDefault: "json",
440
430
  pagination: "none",
@@ -490,8 +480,8 @@ See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
490
480
  cliName: "bars minute",
491
481
  method: "GET",
492
482
  path: "/v1/bars/minute",
493
- description: "Minute-level OHLC bars for one A-share security on a single trade date. Requires `bars:30d` or `bars:full` scope.",
494
- summary: "Minute-level OHLC bars for one A-share security",
483
+ description: "Returns minute-level OHLC bars for one A-share security on a single trade date (canonical code, e.g. `SSE:600519`). Requires the `bars:30d` or `bars:full` scope.",
484
+ summary: "List minute OHLC bars for one A-share security.",
495
485
  positional: [],
496
486
  outputDefault: "json",
497
487
  pagination: "none",
@@ -532,8 +522,8 @@ See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
532
522
  cliName: "bars minute-batch",
533
523
  method: "GET",
534
524
  path: "/v1/bars/minute-batch",
535
- description: "Batch minute OHLC bars for multiple A-share securities. Up to 20 codes\nper call, up to 7 calendar days (5 trading days) date range. Single\nClickHouse round-trip; app groups rows by canonical_code.\n\nPartial success envelope same as daily-batch: codes denied by\n`allowed_securities` go to `errors[]`; others to `items[]` with flat\n`bars[]` (each bar carries its own `trade_date` for client grouping).\n\nRequires `bars:30d` or `bars:full` scope.\n",
536
- summary: "Batch minute OHLC bars for up to 20 A-share securities",
525
+ description: "Returns minute-level OHLC bars for multiple A-share securities in one call: up to 20 codes and up to a 7 calendar-day (~5 trading-day) range. Responses use a partial-success envelope: codes the token may not access are reported in `errors[]`, the rest in `items[]` with a flat `bars[]` array where each bar carries its own `trade_date` for client-side grouping. Requires the `bars:30d` or `bars:full` scope.",
526
+ summary: "Batch minute OHLC bars for up to 20 securities.",
537
527
  positional: [],
538
528
  outputDefault: "json",
539
529
  pagination: "none",
@@ -589,12 +579,8 @@ See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
589
579
  cliName: "concepts alerts",
590
580
  method: "GET",
591
581
  path: "/v1/concepts/alerts",
592
- description: `Returns the live \`concept_alerts:active\` hash. Two rules:
593
- - big_move: abs(pct_change) > 3%
594
- - limit_up_cluster: limit_up_count >= 3 AND stock_count >= 5
595
- See PLAN_CONCEPT_INDUSTRY_QUOTE §5.5 "异动检测与推送".
596
- `,
597
- summary: "Currently active concept alerts (big_move / limit_up_cluster)",
582
+ description: "Returns the live set of concept alerts. Two rules fire: `big_move` when absolute percent change exceeds 3%, and `limit_up_cluster` when a concept has at least 3 limit-up members across at least 5 member stocks.",
583
+ summary: "List currently active concept alerts.",
598
584
  positional: [],
599
585
  outputDefault: "json",
600
586
  pagination: "none",
@@ -619,8 +605,8 @@ See PLAN_CONCEPT_INDUSTRY_QUOTE §5.5 "异动检测与推送".
619
605
  cliName: "concepts alerts-history",
620
606
  method: "GET",
621
607
  path: "/v1/concepts/alerts/history",
622
- description: "Concept异动 historical events (default last 24h). Filter by rule (big_move / limit_up_cluster) and time window. Used by AI agent / admin review.",
623
- summary: "Concept alerts history (last 24h)",
608
+ description: "Returns historical concept alert events (default: last 24 hours). Filter by rule (`big_move` / `limit_up_cluster`) and time window. Useful for reviewing how concept activity evolved over a period.",
609
+ summary: "List recent concept alert history.",
624
610
  positional: [],
625
611
  outputDefault: "json",
626
612
  pagination: "none",
@@ -669,10 +655,8 @@ See PLAN_CONCEPT_INDUSTRY_QUOTE §5.5 "异动检测与推送".
669
655
  cliName: "concepts daily-bars",
670
656
  method: "GET",
671
657
  path: "/v1/concepts/{concept_id}/daily-bars",
672
- description: `Returns daily OHLC (concept index points, not stock prices average),
673
- breadth fields, and is_backfilled flag. See PLAN §5.4 algorithm.
674
- `,
675
- summary: "Concept index daily bars (chain-linked equal-weight, base 1000)",
658
+ description: "Returns daily OHLC for a concept index (index points, not an average of member stock prices), along with breadth fields and a backfill flag. The concept index is a chain-linked equal-weight return series with a base of 1000.",
659
+ summary: "List daily bars for a concept index.",
676
660
  positional: [
677
661
  "concept_id"
678
662
  ],
@@ -717,8 +701,8 @@ breadth fields, and is_backfilled flag. See PLAN §5.4 algorithm.
717
701
  cliName: "concepts list",
718
702
  method: "GET",
719
703
  path: "/v1/concepts",
720
- description: "Concept list joining CH `v_concept_meta` with Redis snapshot hashes,\nfollowing the hot/warm two-key pattern (PR #438):\n\nRead order **per concept** (not global):\n 1. `concept_snapshots:latest` (hot, TTL 5min) fresh tick\n 2. `concept_snapshots:last_close` (warm, TTL 7d) — post-close / weekend\n 3. CH `v_concept_daily_bars` argMax — extreme fallback (~ 7d+)\n\n9:00–9:14 pre-open reset window: hot + warm both suppressed (loading state),\nregardless of TTL; CH fallback is also disabled in this window.\n\nEach item carries `_source` {`latest`, `last_close`, `ch_daily`, `miss`}\nso the client can render a freshness badge.\n\nLive fields: pct_change / index_value / up/down_count / limit_up_count /\namount / speed_3min / `main_net_in`. `main_net_in` (元) is the concept-level\nsum of member-stocks' fundflow `main_net_in` (collector MGETs\n`stockpulse:fundflow:latest:{code}` each aggregation round). May be negative\n(net outflow); null when collector / Redis snapshot unavailable.\n\nSource / status / index are computed per PLAN_CONCEPT_INDUSTRY_QUOTE\n§5.4 algorithm (chain-linked equal-weight returns, base = 1000).\n",
721
- summary: "List concepts with live snapshot (hot/warm fallback)",
704
+ description: "Lists market concepts (themes/sectors) joined with their latest real-time snapshot, falling back to the last close and then to recent daily data when live ticks are unavailable. Each item carries a `_source` freshness indicator. Live fields include percent change, index value, advancing/declining counts, limit-up count, turnover amount, 3-minute momentum, and `main_net_in` (net main-capital inflow in CNY, which may be negative for net outflow and null when unavailable). The concept index is a chain-linked equal-weight return series with a base of 1000. During the 9:00–9:14 pre-open window, live data is suppressed and a loading state is returned.",
705
+ summary: "List concepts with their live snapshot.",
722
706
  positional: [],
723
707
  outputDefault: "json",
724
708
  pagination: "none",
@@ -774,8 +758,8 @@ breadth fields, and is_backfilled flag. See PLAN §5.4 algorithm.
774
758
  cliName: "concepts minute-bars",
775
759
  method: "GET",
776
760
  path: "/v1/concepts/{concept_id}/minute-bars",
777
- description: "Concept index minute-level OHLC (chain-linked equal-weight, base = 1000), max 7 days per request. See PLAN_CONCEPT_INDUSTRY_QUOTE §5.4.",
778
- summary: "Concept index minute bars (max 7 days)",
761
+ description: "Returns minute-level OHLC for a concept index (chain-linked equal-weight return series, base 1000), up to 7 days per request.",
762
+ summary: "List minute bars for a concept index.",
779
763
  positional: [
780
764
  "concept_id"
781
765
  ],
@@ -820,10 +804,8 @@ breadth fields, and is_backfilled flag. See PLAN §5.4 algorithm.
820
804
  cliName: "concepts news",
821
805
  method: "GET",
822
806
  path: "/v1/concepts/{concept_id}/news",
823
- description: `Short-term implementation: ILIKE on news.title + content with the
824
- concept name and full_name. Long-term: news AI tagging.
825
- `,
826
- summary: "Related news matching concept name (ILIKE)",
807
+ description: "Returns recent news whose title or body mentions the concept's name. Use it to see what is currently driving a theme.",
808
+ summary: "List news related to a concept.",
827
809
  positional: [
828
810
  "concept_id"
829
811
  ],
@@ -870,8 +852,8 @@ concept name and full_name. Long-term: news AI tagging.
870
852
  cliName: "concepts show",
871
853
  method: "GET",
872
854
  path: "/v1/concepts/{concept_id}",
873
- description: "Detail view for one concept: meta (full_name / source / status / approved_at / first_listed_at) plus current member securities with their latest snapshots.",
874
- summary: "Concept detail (meta + member securities)",
855
+ description: "Returns detail for one concept: metadata (full name, source, status, approval date, first-listed date) plus its current member securities with their latest snapshots.",
856
+ summary: "Get concept detail with member securities.",
875
857
  positional: [
876
858
  "concept_id"
877
859
  ],
@@ -906,8 +888,8 @@ concept name and full_name. Long-term: news AI tagging.
906
888
  cliName: "concepts snapshot",
907
889
  method: "GET",
908
890
  path: "/v1/concepts/snapshot",
909
- description: "Two contracts depending on `codes` parameter:\n\n- **With `codes=`** (per-key completeness): each requested id is resolved\n via hotwarmCH three-tier fallback. Missing ids are populated.\n- **Without `codes`** (best-effort dump): returns the **union** of\n `concept_snapshots:latest` and `concept_snapshots:last_close` Redis\n hashes. Concepts absent from both Redis hashes are **not** backfilled\n from CH. Use GET `/v1/concepts` instead if you need every known concept.\n\n9:00–9:14 pre-open reset window: hot and warm both suppressed, CH fallback\nalso disabled; the endpoint returns an empty list (loading state).\n\nEach item carries `_source` ∈ {`latest`, `last_close`, `ch_daily`}.\n",
910
- summary: "Bulk Redis snapshot of concepts (hot/warm union)",
891
+ description: "Returns real-time snapshots for concepts, with two modes. With `codes=`, each requested concept id is resolved via real-timelast-closerecent-daily fallback so every requested id is populated. Without `codes`, it returns a best-effort dump of all concepts that currently have a real-time or last-close snapshot; concepts absent from both are not backfilled (use `GET /v1/concepts` if you need every known concept). Each item carries a `_source` freshness indicator. During the 9:00–9:14 pre-open window, live data is suppressed and an empty list is returned.",
892
+ summary: "Bulk real-time snapshot of concepts.",
911
893
  positional: [],
912
894
  outputDefault: "json",
913
895
  pagination: "none",
@@ -937,8 +919,8 @@ concept name and full_name. Long-term: news AI tagging.
937
919
  cliName: "concepts views",
938
920
  method: "GET",
939
921
  path: "/v1/concepts/{concept_id}/views",
940
- description: "JOIN concept_views × broker_views (ai_status=done) sorted by published_at desc.",
941
- summary: "Broker views associated with this concept",
922
+ description: "Returns analyst views linked to this concept (with AI processing complete), sorted by publication date descending.",
923
+ summary: "List analyst views associated with a concept.",
942
924
  positional: [
943
925
  "concept_id"
944
926
  ],
@@ -978,25 +960,8 @@ concept name and full_name. Long-term: news AI tagging.
978
960
  cliName: "digest get",
979
961
  method: "GET",
980
962
  path: "/v1/digest/{code}",
981
- description: `Composite endpoint: in one HTTP call returns views (PRIMARY research),
982
- real-time quote, valuation snapshot (PE/PB/PS/换手率/股息率/量比 14 字段),
983
- market sentiment context, and supplementary news.
984
- Partial-failure tolerant:
985
- each bucket is independently fetched and per-bucket failures surface
986
- in \`meta.partial_failures[]\` rather than poisoning the response. If
987
- every sub-bucket fails the endpoint returns 502.
988
-
989
- Bucket scopes are checked *per bucket* (not at gateway level): a
990
- token with only \`views:read\` gets the views bucket populated and the
991
- rest reported as \`scope_insufficient\` partial failures. This mirrors
992
- the CLI fan-out contract exactly so \`echopai digest\` can either call
993
- this endpoint (preferred, single round-trip) or fall back to local
994
- fan-out without behavior drift.
995
-
996
- See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §3.3 (digest spec) and §11
997
- Phase 5.2 (server endpoint).
998
- `,
999
- summary: "One-shot research digest for a single security (composite)",
963
+ description: "Composite endpoint that, in a single call, returns separated sections for one security: analyst views (the primary research source), a real-time quote, a valuation snapshot (PE/PB/PS, turnover rate, dividend yield, volume ratio, and ~14 fields in total), market sentiment context, and supplementary news. Each section is fetched independently and per-section failures are reported in `meta.partial_failures[]` instead of failing the whole call; the endpoint returns 502 only if every section fails. Scopes are enforced per section, so a token with only `views:read` receives the views section and sees the rest reported as scope-insufficient. Use this when you want a single broad overview of a security (canonical code, e.g. `SSE:600519`).",
964
+ summary: "Build a one-shot research digest for one security.",
1000
965
  positional: [
1001
966
  "code"
1002
967
  ],
@@ -1050,8 +1015,8 @@ Phase 5.2 (server endpoint).
1050
1015
  cliName: "financials pit",
1051
1016
  method: "GET",
1052
1017
  path: "/v1/financials/pit",
1053
- description: "基于 TDX 财务数据的 Point-in-time 查询。给定 `code` `date`,返回该\n`trade_date` 当时市场上可见的最新一期财务报告(`announce_date <= date`,\n无公告日时保守延迟 90 天)。专为回测、AI agent、量化策略防未来函数泄漏。\n\n返回字段包括:EPS(基本/扣非/稀释)、BPSROE、毛利率、营收/净利润/总资产/\n归母权益等核心 ~25 字段。\n",
1054
- summary: "Point-in-time financial indicators for one A-share at a given date",
1018
+ description: "Returns the most recent financial report that was publicly available on a given date for one A-share security (canonical code, e.g. `SSE:600519`) i.e. announcement date on or before `date`, with a conservative 90-day fallback when the announcement date is unknown. Designed to avoid look-ahead bias in backtests and quantitative strategies. Fields include EPS (basic / non-recurring / diluted), BPS, ROE, gross margin, revenue, net profit, total assets, and parent-company equity (~25 core fields).",
1019
+ summary: "Get point-in-time fundamentals for one security at a date.",
1055
1020
  positional: [],
1056
1021
  outputDefault: "json",
1057
1022
  pagination: "none",
@@ -1090,8 +1055,8 @@ Phase 5.2 (server endpoint).
1090
1055
  cliName: "financials quote-snapshot",
1091
1056
  method: "GET",
1092
1057
  path: "/v1/financials/quote-snapshot",
1093
- description: "一站式估值快照 —— 一次返回 14 个字段,**全部基于 TDX 原始数据 + Sina 实时\n价自算,不依赖 Tushare**:\n\n- 估值: `pe` / `pe_ttm` / `pb` / `ps` / `ps_ttm`\n- 股本(万股): `total_share` / `float_share` / `free_share`\n- 市值(万元): `total_mv` / `circ_mv`\n- 流动性: `turnover_rate` / `turnover_rate_f`(%)/ `volume_ratio`(倍)\n- 分红: `dv_ratio` / `dv_ttm`(%)\n\n计算口径:\n- PE-TTM = 当前总市值 / 4 季度滚动净利润(PIT 表 `ni_parent_ttm`)\n- PE = 当前总市值 / 上年报净利润(`ni_parent_last1y`)\n- PB = 当前总市值 / 归母净资产(`equity_parent`,PIT 防穿越)\n- 换手率 = 当日累计成交量 / 流通股本 × 100\n- 量比 = 当日累计 / 近 5 日同时段累计平均(实时模式)\n / 近 5 日全日成交量平均(指定历史日期模式)\n- 股息率 = Σ(去年 / 近12月内派息事件 派现/10) / 现价 × 100\n\n默认 `date` 留空 实时(Sina 5s 快照价 + 当日累计成交量);\n给 `date=YYYY-MM-DD` 则按当日 close 取值(用于历史回测 / 校验)。\n\n财报数据走 `security_financial_indicators_pit` PIT 视图:保证 `visible_date`\n≤ trade_date,避免未来函数。\n\n准确性已对照 Tushare `daily_basic` 校验(见 `scripts/validation/validate_quote_snapshot_vs_tushare.py`)。\n",
1094
- summary: "Real-time valuation / share / turnover snapshot for one A-share",
1058
+ description: "Returns a one-stop valuation snapshot (14 fields) for one A-share security (canonical code, e.g. `SSE:600519`), combining real-time prices with point-in-time fundamentals so figures are free of look-ahead bias. Fields: valuation (`pe`, `pe_ttm`, `pb`, `ps`, `ps_ttm`); share counts in 10k shares (`total_share`, `float_share`, `free_share`); market cap in 10k CNY (`total_mv`, `circ_mv`); liquidity (`turnover_rate`, `turnover_rate_f` in %, `volume_ratio` as a multiple); and dividend yield (`dv_ratio`, `dv_ttm` in %). Leave `date` empty for a real-time snapshot, or pass `date=YYYY-MM-DD` to value as of that day's close (useful for historical checks and backtests).",
1059
+ summary: "Get a real-time valuation snapshot for one security.",
1095
1060
  positional: [],
1096
1061
  outputDefault: "json",
1097
1062
  pagination: "none",
@@ -1131,8 +1096,8 @@ Phase 5.2 (server endpoint).
1131
1096
  cliName: "financials reports",
1132
1097
  method: "GET",
1133
1098
  path: "/v1/financials/reports",
1134
- description: "返回该股票最近 N 期财务报告的核心指标(EPS/BPS/ROE/毛利率/营收/净利润/总\n资产/归母权益/经营现金流/总股本 ~25 字段)。可按 `kind` 过滤季报/半年报/\n年报。每期 `announce_date` 字段告知何时市场可见,建议结合回测时使用。\n\n**`kind` 口径说明**:\n\n- `Q1` / `H1` / `Q3` / `annual` —— 四个标准季度末快照(3-31 / 6-30 /\n 9-30 / 12-31),TDX 财务源数据全部落在这四个 report_date。\n- `preliminary` —— **当前数据源结构上不存在**。业绩预告 / 快报 indicators\n 行内的字段(`forecast_ni_lower` / `forecast_ni_upper` /\n `express_ni_parent` / `express_revenue` 等),通过 `financials.series`\n metric 参数访问,不是单独的 report_kind 行。`kind=preliminary` 会返\n 空列表 + meta.note。\n",
1135
- summary: "Recent financial reports for one A-share security",
1099
+ description: "Returns core indicators for the most recent N financial reports of one A-share security (EPS/BPS/ROE, gross margin, revenue, net profit, total assets, parent equity, operating cash flow, total shares, and ~25 fields in total). Filter by `kind` for quarterly / interim / annual reports. Each report's `announce_date` indicates when it became publicly visible, which is useful for point-in-time analysis. Note: `kind=preliminary` does not exist as a separate report row — earnings forecasts and flash reports are exposed as metric fields (e.g. `forecast_ni_lower`, `forecast_ni_upper`, `express_ni_parent`, `express_revenue`) via `financials.series`, so `kind=preliminary` returns an empty list with an explanatory note.",
1100
+ summary: "List recent financial reports for one security.",
1136
1101
  positional: [],
1137
1102
  outputDefault: "json",
1138
1103
  pagination: "none",
@@ -1183,8 +1148,8 @@ Phase 5.2 (server endpoint).
1183
1148
  cliName: "financials series",
1184
1149
  method: "GET",
1185
1150
  path: "/v1/financials/series",
1186
- description: "返回某只股票某个财务指标的历史时间序列(按报告期排列)。`metric` 是\nindicators 表中的字段名(如 `roe_simple` / `revenue` / `ni_parent` /\n`debt_asset_ratio` / `gross_margin` / `eps_basic` 等共 150 个)。\n\n典型用法:画茅台 10 ROE 走势、对比 5 只白酒股 net_margin。\n",
1187
- summary: "Time series of a single financial metric for one security",
1151
+ description: "Returns the historical time series of a single financial metric for one A-share security, ordered by reporting period. `metric` is an indicator field name (e.g. `roe_simple`, `revenue`, `ni_parent`, `debt_asset_ratio`, `gross_margin`, `eps_basic`; ~150 metrics available). Use it to chart a multi-year trend for one company or to compare the same metric across several securities.",
1152
+ summary: "Get a time series of one financial metric.",
1188
1153
  positional: [],
1189
1154
  outputDefault: "json",
1190
1155
  pagination: "none",
@@ -1242,8 +1207,8 @@ Phase 5.2 (server endpoint).
1242
1207
  cliName: "index daily-bars",
1243
1208
  method: "GET",
1244
1209
  path: "/v1/index/bars/daily",
1245
- description: "指数日线 OHLC。覆盖 v2 表中的指数(上证综指 `SSE:000001` / 深证成指\n`SZSE:399001` / 沪深300 `SSE:000300` / 北证50 `BSE:899050` / 中证系列 `CSI:000922` 等)。\n与 `/v1/bars/daily` 同语义,但 index 没有 `turnover_rate` / `paused` 等股票特有字段。\n",
1246
- summary: "Daily OHLC bars for one A-share index",
1210
+ description: "Returns daily OHLC for one A-share index (e.g. 上证综指 `SSE:000001`, 深证成指 `SZSE:399001`, 沪深300 `SSE:000300`, 北证50 `BSE:899050`, CSI series such as `CSI:000922`). Same semantics as `/v1/bars/daily`, but indices have no stock-specific fields such as turnover rate or suspension flag.",
1211
+ summary: "List daily OHLC bars for one A-share index.",
1247
1212
  positional: [],
1248
1213
  outputDefault: "json",
1249
1214
  pagination: "none",
@@ -1291,8 +1256,8 @@ Phase 5.2 (server endpoint).
1291
1256
  cliName: "index minute-bars",
1292
1257
  method: "GET",
1293
1258
  path: "/v1/index/bars/minute",
1294
- description: "指数 1min OHLC。日期范围 7 天(与 `/v1/bars/minute-batch` 对齐)。\n返回 `bar_time` / `trade_date` / OHLC / volume / amount / pct_change。\n",
1295
- summary: "Minute OHLC bars for one A-share index",
1259
+ description: "Returns 1-minute OHLC for one A-share index over a date range of up to 7 days. Each bar includes bar time, trade date, OHLC, volume, amount, and percent change.",
1260
+ summary: "List minute OHLC bars for one A-share index.",
1296
1261
  positional: [],
1297
1262
  outputDefault: "json",
1298
1263
  pagination: "none",
@@ -1338,8 +1303,8 @@ Phase 5.2 (server endpoint).
1338
1303
  cliName: "index snapshot",
1339
1304
  method: "GET",
1340
1305
  path: "/v1/index/snapshot",
1341
- description: "Returns the latest real-time snapshot for the 172 indices that Sina\nexposes a quote for (SSE / SZSE / BSE includes 北证50 `BSE:899050`\nand 专精特新 `899601.BJ`). Source is the Redis hash\n`stockpulse:index_snapshots:latest` which the Rust collector refreshes\nevery ~15 seconds during market hours; outside trading hours the last\nintraday snapshot is returned.\n\nUse `codes` to narrow to a subset (canonical format `SSE:000001` (exchange-prefix; legacy `000001.SH` is also accepted but discouraged)).\nOmit to receive all 172.\n\nNote: CSI-series indices (e.g. `CSI:000922`) are not available here —\nSina has no quote feed for them. Daily/minute history for those still\nlives behind `/api/internal/index/{daily-bars,minute-bars-range}`.\n\nRequires `quote:l1`, `quote:l2`, or `quote:delayed` scope.\n",
1342
- summary: "Real-time snapshot of all Sina-OK A-share indices (172 indices)",
1306
+ description: "Returns the latest real-time snapshot for the 172 A-share indices that have a live quote feed (SSE / SZSE / BSE, including 北证50 `BSE:899050`). The snapshot refreshes roughly every 15 seconds during market hours; outside trading hours the last intraday snapshot is returned. Use `codes` (canonical format, e.g. `SSE:000001`) to narrow to a subset, or omit it to receive all indices. Note: CSI-series indices (e.g. `CSI:000922`) have no live quote feed and are not returned here. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
1307
+ summary: "Snapshot real-time quotes for A-share indices.",
1343
1308
  positional: [],
1344
1309
  outputDefault: "json",
1345
1310
  pagination: "none",
@@ -1365,197 +1330,13 @@ Phase 5.2 (server endpoint).
1365
1330
  additionalProperties: false
1366
1331
  }
1367
1332
  },
1368
- "industries.alerts": {
1369
- cliKey: "industries.alerts",
1370
- cliName: "industries alerts",
1371
- method: "GET",
1372
- path: "/v1/industries/alerts",
1373
- description: "Currently active industry alerts (big_move / limit_up_cluster). Same rule set as concepts.",
1374
- summary: "Currently active industry alerts",
1375
- positional: [],
1376
- outputDefault: "json",
1377
- pagination: "none",
1378
- stream: false,
1379
- billable: false,
1380
- idempotencyRequired: false,
1381
- scopesAny: [
1382
- "quote:l1",
1383
- "quote:l2",
1384
- "quote:delayed"
1385
- ],
1386
- sideEffect: "read",
1387
- dryRunSupported: false,
1388
- inputSchema: {
1389
- type: "object",
1390
- properties: {},
1391
- additionalProperties: false
1392
- }
1393
- },
1394
- "industries.daily-bars": {
1395
- cliKey: "industries.daily-bars",
1396
- cliName: "industries daily-bars",
1397
- method: "GET",
1398
- path: "/v1/industries/{key}/daily-bars",
1399
- description: "Industry index daily OHLC (chain-linked equal-weight, base = 1000). Path key format `<sw_industry_code>:<sw_level>` (e.g. `401001:1`).",
1400
- summary: "Industry index daily bars",
1401
- positional: [
1402
- "key"
1403
- ],
1404
- outputDefault: "json",
1405
- pagination: "none",
1406
- stream: false,
1407
- billable: true,
1408
- idempotencyRequired: false,
1409
- scopesAny: [
1410
- "bars:read"
1411
- ],
1412
- sideEffect: "read",
1413
- dryRunSupported: false,
1414
- inputSchema: {
1415
- type: "object",
1416
- properties: {
1417
- key: {
1418
- type: "string",
1419
- description: "Industry key in `<sw_industry_code>:<sw_level>` format (e.g. `401001:1`).",
1420
- example: "401001:1"
1421
- },
1422
- from: {
1423
- type: "string",
1424
- format: "date",
1425
- description: "Window start. For daily/minute bars: ISO date `YYYY-MM-DD`. For alerts history: Unix milliseconds (default = now - 24h)."
1426
- },
1427
- to: {
1428
- type: "string",
1429
- format: "date",
1430
- description: "Window end. For daily/minute bars: ISO date `YYYY-MM-DD`. For alerts history: Unix milliseconds (default = now)."
1431
- }
1432
- },
1433
- additionalProperties: false,
1434
- required: [
1435
- "key",
1436
- "from",
1437
- "to"
1438
- ]
1439
- }
1440
- },
1441
- "industries.list": {
1442
- cliKey: "industries.list",
1443
- cliName: "industries list",
1444
- method: "GET",
1445
- path: "/v1/industries",
1446
- description: "Industry index list joining CH `v_industry_meta` with Redis snapshot.\n申万一级 (level=1) ≈ 127 industries, 申万二级 (level=2) ≈ 348.\nChain-linked equal-weight algorithm identical to concepts (PLAN §5.4).\n\nRead order per industry (mirrors `/v1/concepts`, PR #442):\n 1. `industry_snapshots:latest` (hot, TTL 5min)\n 2. `industry_snapshots:last_close` (warm, TTL 7d)\n 3. CH `v_industry_daily_bars` argMax\n9:00–9:14 reset window suppresses hot+warm (loading state).\nEach item carries `_source` ∈ {`latest`, `last_close`, `ch_daily`, `miss`}.\n",
1447
- summary: "List industries (申万 L1/L2) with hot/warm fallback",
1448
- positional: [],
1449
- outputDefault: "json",
1450
- pagination: "none",
1451
- stream: false,
1452
- billable: true,
1453
- idempotencyRequired: false,
1454
- scopesAny: [
1455
- "quote:l1",
1456
- "quote:l2",
1457
- "quote:delayed"
1458
- ],
1459
- sideEffect: "read",
1460
- dryRunSupported: false,
1461
- inputSchema: {
1462
- type: "object",
1463
- properties: {
1464
- level: {
1465
- type: "integer",
1466
- enum: [
1467
- 1,
1468
- 2
1469
- ],
1470
- default: 1,
1471
- description: "申万 industry level (1 or 2)."
1472
- },
1473
- limit: {
1474
- type: "integer",
1475
- minimum: 1,
1476
- maximum: 1000,
1477
- default: 500,
1478
- description: "Maximum number of items to return."
1479
- }
1480
- },
1481
- additionalProperties: false
1482
- }
1483
- },
1484
- "industries.show": {
1485
- cliKey: "industries.show",
1486
- cliName: "industries show",
1487
- method: "GET",
1488
- path: "/v1/industries/{key}",
1489
- description: "Path key format: `<sw_industry_code>:<sw_level>` (e.g. `401001:1`).",
1490
- summary: "Industry detail (meta + today members)",
1491
- positional: [
1492
- "key"
1493
- ],
1494
- outputDefault: "json",
1495
- pagination: "none",
1496
- stream: false,
1497
- billable: true,
1498
- idempotencyRequired: false,
1499
- scopesAny: [
1500
- "quote:l1",
1501
- "quote:l2",
1502
- "quote:delayed"
1503
- ],
1504
- sideEffect: "read",
1505
- dryRunSupported: false,
1506
- inputSchema: {
1507
- type: "object",
1508
- properties: {
1509
- key: {
1510
- type: "string",
1511
- description: "Industry key in `<sw_industry_code>:<sw_level>` format (e.g. `401001:1`).",
1512
- example: "401001:1"
1513
- }
1514
- },
1515
- additionalProperties: false,
1516
- required: [
1517
- "key"
1518
- ]
1519
- }
1520
- },
1521
- "industries.snapshot": {
1522
- cliKey: "industries.snapshot",
1523
- cliName: "industries snapshot",
1524
- method: "GET",
1525
- path: "/v1/industries/snapshot",
1526
- 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",
1527
- summary: "Bulk Redis snapshot of industries (hot/warm union)",
1528
- positional: [],
1529
- outputDefault: "json",
1530
- pagination: "none",
1531
- stream: false,
1532
- billable: true,
1533
- idempotencyRequired: false,
1534
- scopesAny: [
1535
- "quote:l1",
1536
- "quote:l2",
1537
- "quote:delayed"
1538
- ],
1539
- sideEffect: "read",
1540
- dryRunSupported: false,
1541
- inputSchema: {
1542
- type: "object",
1543
- properties: {
1544
- codes: {
1545
- type: "string",
1546
- description: "Comma-separated `sw_code:level` (e.g. `401001:1,401002:1`); omit for best-effort Redis union."
1547
- }
1548
- },
1549
- additionalProperties: false
1550
- }
1551
- },
1552
1333
  "limit-up.analysis": {
1553
1334
  cliKey: "limit-up.analysis",
1554
1335
  cliName: "limit-up analysis",
1555
1336
  method: "GET",
1556
1337
  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)",
1338
+ description: "Returns LLM-generated attribution for each limit-up stock: the leading theme (`leading_concept`), concept tags, and a reason explaining why it hit the limit. Themes are inferred by an LLM from the latest research, institutional views, and news, so they may include brand-new themes; results refresh automatically at six points each trading day, with later runs refining earlier ones. This complements the limit-up pool, which tells you which stocks limited up; this endpoint tells you why and under which theme. Note: when `trade_date` is omitted, the latest attributed trading day is returned — before today's first run completes (around 09:47 Beijing time) this may be the previous trading day rather than empty, so pass an explicit `trade_date` for today and verify the `trade_date` / `generated_at` fields. Requires the `market:read` scope or any `quote:*` scope.",
1339
+ summary: "Get LLM attribution for limit-up stocks.",
1559
1340
  positional: [],
1560
1341
  outputDefault: "json",
1561
1342
  pagination: "none",
@@ -1587,8 +1368,8 @@ Phase 5.2 (server endpoint).
1587
1368
  cliName: "limit-up history",
1588
1369
  method: "GET",
1589
1370
  path: "/v1/limit-up/history",
1590
- 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",
1591
- summary: "A-share limit-up daily trend (last N days)",
1371
+ description: "Returns the daily limit-up trend over the last N trading days: per-day limit-up count, highest consecutive-board height, and seal-break count. Sorted by trade date ascending (oldest first). Requires a `quote:*` scope.",
1372
+ summary: "Get the A-share limit-up daily trend.",
1592
1373
  positional: [],
1593
1374
  outputDefault: "json",
1594
1375
  pagination: "none",
@@ -1621,8 +1402,8 @@ Phase 5.2 (server endpoint).
1621
1402
  cliName: "limit-up pool",
1622
1403
  method: "GET",
1623
1404
  path: "/v1/limit-up/pool",
1624
- description: "当日(或指定 `trade_date`)涨停股池逐股明细:首/末次封板时间、最终涨幅、\n封单金额/封单量、炸板次数、连板数、N 日板内统计、一字板标记、当前 status\n(limit_up / broken)、板块分类。\n\n盘前回退规则(与所有行情类页面一致):\n - `< 9:00`:返回 max(trade_date) 的最新一日数据(昨日)\n - `9:00-9:14`:集合竞价前空窗期,items=[] / trade_date=null\n - `>= 9:15`:collector 已写入今日数据\n\n排序:连板数 desc 板内板数 desc 封单金额 desc。需要 `quote:*` scope。\n",
1625
- summary: "A-share limit-up pool (per-stock detail) for a given trade date",
1405
+ description: "Returns per-stock detail for the limit-up pool on the current (or specified `trade_date`) trading day: first/last seal time, final percent change, seal amount and seal volume, number of times the seal broke, consecutive limit-up days, in-pool statistics over N days, one-line-board flag, current status (limit_up / broken), and sector classification. Sorted by consecutive limit-up days, then in-pool board count, then seal amount (all descending). Before 9:00 the previous trading day's data is returned; during 9:009:14 an empty result is returned; from 9:15 today's data is available. Requires a `quote:*` scope.",
1406
+ summary: "List the A-share limit-up pool for a trade date.",
1626
1407
  positional: [],
1627
1408
  outputDefault: "json",
1628
1409
  pagination: "none",
@@ -1667,8 +1448,8 @@ Phase 5.2 (server endpoint).
1667
1448
  cliName: "limit-up summary",
1668
1449
  method: "GET",
1669
1450
  path: "/v1/limit-up/summary",
1670
- description: "涨停股池聚合统计:涨停数、炸板数、跌停数、最高连板高度、连板梯队\n`ladder: [{ height, count }, ...]`。\n\n炸板数 / 跌停数 与情绪页同源(`market_breadth_intraday` 当日最新分钟行);\n连板梯队仅统计当前封板 status=limit_up 的个股。\n\n盘前回退规则与 `limit-up/pool` 一致。需要 `quote:*` scope。\n",
1671
- summary: "A-share limit-up summary (counts + height ladder) for a given trade date",
1451
+ description: "Returns aggregate limit-up statistics for the trade date: limit-up count, seal-break count, limit-down count, the highest consecutive-board height, and a height ladder (`ladder: [{ height, count }, ...]`). The height ladder counts only stocks currently sealed at limit-up. Pre-open fallback follows the same rules as the limit-up pool. Requires a `quote:*` scope.",
1452
+ summary: "Summarize A-share limit-up activity for a trade date.",
1672
1453
  positional: [],
1673
1454
  outputDefault: "json",
1674
1455
  pagination: "none",
@@ -1708,8 +1489,8 @@ Phase 5.2 (server endpoint).
1708
1489
  cliName: "market status",
1709
1490
  method: "GET",
1710
1491
  path: "/v1/market/status",
1711
- description: "Current A-share market session: pre-open / open / lunch / closed; current and next trading day; holiday flag. Requires `market:read` or any `quote:*` scope.",
1712
- summary: "Current A-share market session state",
1492
+ description: "Returns the current A-share trading session (pre-open / open / lunch / closed), the current and next trading day, and a holiday flag. Requires the `market:read` scope or any `quote:*` scope.",
1493
+ summary: "Get the current A-share market session state.",
1713
1494
  positional: [],
1714
1495
  outputDefault: "json",
1715
1496
  pagination: "none",
@@ -1735,8 +1516,8 @@ Phase 5.2 (server endpoint).
1735
1516
  cliName: "news feed",
1736
1517
  method: "GET",
1737
1518
  path: "/v1/news",
1738
- description: " `/v1/news/list` 行为一致,旧客户端兼容用。",
1739
- summary: "List recent news (alias of /v1/news/list)",
1519
+ description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility.",
1520
+ summary: "List recent news.",
1740
1521
  positional: [],
1741
1522
  outputDefault: "json",
1742
1523
  pagination: "offset",
@@ -1757,8 +1538,8 @@ Phase 5.2 (server endpoint).
1757
1538
  cliName: "news get",
1758
1539
  method: "GET",
1759
1540
  path: "/v1/news/{news_id}",
1760
- description: "返回新闻完整内容:title、content snippet、tagged securities。(采集端字段 source / source_id / source_url 对外不暴露,admin 路径 /v1/admin/news/{id} 可见。)",
1761
- summary: "Fetch a single news item by id",
1541
+ description: "Returns the full content of one news item: title, content snippet, and tagged securities.",
1542
+ summary: "Get a single news item by id.",
1762
1543
  positional: [
1763
1544
  "news_id"
1764
1545
  ],
@@ -1789,8 +1570,8 @@ Phase 5.2 (server endpoint).
1789
1570
  cliName: "news list",
1790
1571
  method: "GET",
1791
1572
  path: "/v1/news/list",
1792
- description: "List news mentioning a specific A-share security, ordered by published_at desc. Requires `news:read` scope.",
1793
- summary: "List news mentioning a specific security",
1573
+ description: "Returns news items mentioning a specific A-share security, ordered by publication date descending. Requires the `news:read` scope.",
1574
+ summary: "List news mentioning one security.",
1794
1575
  positional: [],
1795
1576
  outputDefault: "json",
1796
1577
  pagination: "none",
@@ -1853,8 +1634,8 @@ Phase 5.2 (server endpoint).
1853
1634
  cliName: "news search",
1854
1635
  method: "GET",
1855
1636
  path: "/v1/news/search",
1856
- description: "Full-text search recent news / market briefs. Returns title, published_at, snippet, tagged securities. (Internal collector identifiers — `source`/`source_id`/`source_url` — are NOT exposed publicly; admin tools see them via /v1/admin/*.) Requires `news:read` scope. Note: news fields are user-generated; meta.untrusted_text_fields lists fields that need sanitization before passing to an LLM.",
1857
- summary: "Full-text search recent news",
1637
+ description: "Full-text search over recent news and market briefs. Returns title, publication date, snippet, and tagged securities. Requires the `news:read` scope. Note: news fields are user-generated; `meta.untrusted_text_fields` lists fields that should be sanitized before being passed to an LLM.",
1638
+ summary: "Full-text search recent news.",
1858
1639
  positional: [],
1859
1640
  outputDefault: "json",
1860
1641
  pagination: "none",
@@ -1918,10 +1699,8 @@ Phase 5.2 (server endpoint).
1918
1699
  cliName: "ownership lockups",
1919
1700
  method: "GET",
1920
1701
  path: "/v1/ownership-events/lockups",
1921
- description: `全市场未来 days 天即将解禁扫描,按解禁日升序 + 占比降序。前瞻抛压预警,
1922
- digest 给不了(digest 是单 code 维度)。min_pct 过滤小额解禁,limit 控量。
1923
- `,
1924
- summary: "Scan upcoming share-lockup expirations across the whole market",
1702
+ description: "Scans the whole market for share lockups expiring within the next `days` days, sorted by unlock date ascending then by unlocking ratio descending — a forward-looking selling-pressure screen across all securities. Use `min_pct` to filter out small unlocks and `limit` to cap the result size. T+1 data.",
1703
+ summary: "Scan upcoming share-lockup expirations market-wide.",
1925
1704
  positional: [],
1926
1705
  outputDefault: "json",
1927
1706
  pagination: "none",
@@ -1966,20 +1745,8 @@ digest 给不了(digest 是单 code 维度)。min_pct 过滤小额解禁,l
1966
1745
  cliName: "ownership show",
1967
1746
  method: "GET",
1968
1747
  path: "/v1/ownership-events",
1969
- description: `单股股权事件三合一结构化字段,给研究 agent 一眼可读的「近 30 天增减持/回购
1970
- + 未来 30 天限售解禁」。源 Tushare(T+1),落 PG 专表幂等同步。
1971
-
1972
- - shareholder_transactions:增减持回看 txn_days 天,含净增减股数/比例 +
1973
- 增减笔数 + 明细(方向/股东/均价/区间)。
1974
- - share_repurchases:回购回看 repurchase_days 天,含在进行笔数 + 已执行
1975
- 累计金额/股数(仅计 in_progress/completed)+ 最新进度 + 明细。
1976
- - lockup_expirations:解禁前瞻 lockup_days 天(unlock_date ∈ [today,
1977
- today+N]),含合计解禁股数/占比 + 最近解禁日 + 明细。前瞻抛压预警。
1978
-
1979
- 枚举 slug(direction / holder_category / status / lockup_type)见
1980
- docs/OWNERSHIP_EVENTS.md。
1981
- `,
1982
- summary: "Ownership events for one A-share (holder trades / repurchases / lockups)",
1748
+ description: "Returns a structured three-in-one view of ownership events for one A-share security (canonical code, e.g. `SSE:600519`): recent shareholder transactions, recent share repurchases, and upcoming lockup expirations. T+1 data. Shareholder transactions look back `txn_days` days and include net change in shares/ratio, transaction count, and per-transaction detail (direction, holder, average price, date range). Repurchases look back `repurchase_days` days and include in-progress count, cumulative executed amount/shares, latest progress, and detail. Lockup expirations look ahead `lockup_days` days and include total unlocking shares/ratio, the nearest unlock date, and detail — useful as a forward-looking selling-pressure warning. Enum values (direction, holder category, status, lockup type) are documented in the response schema.",
1749
+ summary: "Get ownership events for one security.",
1983
1750
  positional: [],
1984
1751
  outputDefault: "json",
1985
1752
  pagination: "none",
@@ -2028,35 +1795,13 @@ docs/OWNERSHIP_EVENTS.md。
2028
1795
  ]
2029
1796
  }
2030
1797
  },
2031
- "payment.plans": {
2032
- cliKey: "payment.plans",
2033
- cliName: "payment plans",
2034
- method: "GET",
2035
- path: "/v1/payment/plans",
2036
- description: "返回订阅计划清单(plan_id + 价格 + 时长 + 描述)。",
2037
- summary: "List subscription plans",
2038
- positional: [],
2039
- outputDefault: "json",
2040
- pagination: "none",
2041
- stream: false,
2042
- billable: false,
2043
- idempotencyRequired: false,
2044
- scopesAny: [],
2045
- sideEffect: "read",
2046
- dryRunSupported: false,
2047
- inputSchema: {
2048
- type: "object",
2049
- properties: {},
2050
- additionalProperties: false
2051
- }
2052
- },
2053
1798
  quote: {
2054
1799
  cliKey: "quote",
2055
1800
  cliName: "quote",
2056
1801
  method: "GET",
2057
1802
  path: "/v1/quote/realtime",
2058
- description: "Real-time quote for one or more A-share securities. Returns last price, volume, change %, bid/ask. Up to 200 codes per call. Requires `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
2059
- summary: "Real-time quote for one or more A-share securities",
1803
+ description: "Returns real-time quotes for one or more A-share securities (canonical codes, e.g. `SSE:600519`): last price, volume, percent change, and bid/ask. Up to 200 codes per call. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
1804
+ summary: "Get real-time quotes for one or more securities.",
2060
1805
  positional: [],
2061
1806
  outputDefault: "json",
2062
1807
  pagination: "none",
@@ -2106,8 +1851,8 @@ docs/OWNERSHIP_EVENTS.md。
2106
1851
  cliName: "quote scan",
2107
1852
  method: "GET",
2108
1853
  path: "/v1/quote/scan",
2109
- description: 'Dumps the entire Redis snapshot of A-share real-time quotes in a single\nround-trip. Use for "全市场扫描" / agent universe-of-interest discovery\nwhen you don\'t yet know which codes to query.\n\nFiltering:\n- `exchange=SSE|SZSE|BSE` narrows to one exchange (canonical_code prefix)\n- `app.allowed_securities` is ALWAYS applied; codes outside drop silently\n\nResponse size: ~3 MB unfiltered (~5800 items). CLI users should pair\nwith `--max-bytes` / `--fields` / `--query` from Phase 1.4.\n\n9:00–9:14 集合竞价时段返回空 items + `note` 字段说明。\n\nEach item also includes `main_net_in` (元, may be negative) — current-day\ncumulative main-capital net inflow merged from fundflow snapshot via\nhot/warm two-key pattern (PR #438):\n - `stockpulse:fundflow:latest:{code}` (hot, TTL 180s) for fresh\n - `stockpulse:fundflow:last_close:{code}` (warm, TTL 7d) for post-close\nCompanion field `main_net_in_stale` (boolean): false = fresh from hot,\ntrue = served from warm (post-close / weekend / collector down).\nTreated as a public field (same tier as `amount`); not scrubbed under\nany scope.\n\nRequires `quote:l1`, `quote:l2`, or `quote:delayed` scope.\n',
2110
- summary: "Full-market real-time quote scan (~5800 A-share securities)",
1854
+ description: "Returns a real-time quote for every A-share in a single call — use it for full-market scans or to discover a universe of interest when you do not yet know which codes to query. Filter with `exchange=SSE|SZSE|BSE` to narrow to one exchange. The payload is large (~3 MB, ~5,800 instruments), so pair it with `--fields`, `--jq`, or `--max-bytes` to trim it. During the 9:00–9:14 pre-open auction an empty result is returned with an explanatory note. Each item also includes `main_net_in` (current-day cumulative net main-capital inflow in CNY, which may be negative) and a companion `main_net_in_stale` flag (true when served from the last-close fallback). Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
1855
+ summary: "Snapshot every A-share real-time quote in one call.",
2111
1856
  positional: [],
2112
1857
  outputDefault: "json",
2113
1858
  pagination: "none",
@@ -2145,15 +1890,8 @@ docs/OWNERSHIP_EVENTS.md。
2145
1890
  cliName: "search semantic",
2146
1891
  method: "GET",
2147
1892
  path: "/v1/search",
2148
- description: `语义 + 模糊泛化搜索。结合 entity 精确匹配(公司代码/分析师)、向量召回(pgvector
2149
- + DashScope embedding)、trigram 模糊、ILIKE 精确四路召回,RRF 融合后用 reranker
2150
- 精排,叠时间衰减 + views 加权(views 主源优先)。
2151
-
2152
- - 搜"锂电池"会带出新能源产业链(正极/负极/碳酸锂/宁德时代…)
2153
- - 搜"芯片"会带出存储芯片/洁净室/晶圆代工…
2154
- - mode=exact 兼容老 ILIKE 行为,仅在精确关键词命中时返回。
2155
- `,
2156
- summary: "Hybrid semantic search across news + analyst views",
1893
+ description: 'Hybrid semantic search across news and analyst views that generalizes beyond exact keywords. It combines exact entity matching (security codes, analysts), semantic (embedding-based) recall, and fuzzy matching, then fuses and re-ranks results with time decay and a weighting toward analyst views as the primary source. For example, searching "锂电池" surfaces the broader new-energy supply chain (cathode/anode materials, lithium carbonate, leading battery makers). Pass `mode=exact` for legacy exact-keyword behavior.',
1894
+ summary: "Semantic search across news and analyst views.",
2157
1895
  positional: [],
2158
1896
  outputDefault: "json",
2159
1897
  pagination: "none",
@@ -2215,52 +1953,13 @@ docs/OWNERSHIP_EVENTS.md。
2215
1953
  ]
2216
1954
  }
2217
1955
  },
2218
- "securities.industry": {
2219
- cliKey: "securities.industry",
2220
- cliName: "securities industry",
2221
- method: "GET",
2222
- path: "/v1/securities/industry",
2223
- description: `返回该股票的 TDX 自定义行业代码(T1001 食品饮料等)与申万行业三级代码
2224
- L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2225
-
2226
- 用法:
2227
- - 股票详情页"申万行业:白酒 / 食品饮料"标签
2228
- - 筛选器交叉过滤:行业 + ROE/营收增长率
2229
- - AI agent \`find_peers(code)\` 用申万二级行业找同业
2230
- `,
2231
- summary: "TDX + 申万 industry classification for one A-share security",
2232
- positional: [],
2233
- outputDefault: "json",
2234
- pagination: "none",
2235
- stream: false,
2236
- billable: false,
2237
- idempotencyRequired: false,
2238
- scopesAny: [],
2239
- sideEffect: "read",
2240
- dryRunSupported: false,
2241
- inputSchema: {
2242
- type: "object",
2243
- properties: {
2244
- code: {
2245
- type: "string",
2246
- description: "A-share canonical code (SSE/SZSE/BSE only). For quote/chart/bars/financials endpoints.",
2247
- pattern: "^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$",
2248
- example: "SSE:600519"
2249
- }
2250
- },
2251
- additionalProperties: false,
2252
- required: [
2253
- "code"
2254
- ]
2255
- }
2256
- },
2257
1956
  "semantic.find": {
2258
1957
  cliKey: "semantic.find",
2259
1958
  cliName: "semantic find",
2260
1959
  method: "GET",
2261
1960
  path: "/v1/securities/search",
2262
- description: "Find A-share securities matching a name (Chinese), code (e.g. 600519), or pinyin initials (e.g. gzmt). Use this when an agent has a description and needs canonical codes.",
2263
- summary: "Find A-share securities by name / code / pinyin",
1961
+ description: "Finds A-share securities matching a Chinese name, a code (e.g. `600519`), or pinyin initials (e.g. `gzmt`). Use it to turn a free-form description into canonical codes (e.g. `SSE:600519`).",
1962
+ summary: "Resolve A-share securities by name, code, or pinyin.",
2264
1963
  positional: [],
2265
1964
  outputDefault: "json",
2266
1965
  pagination: "none",
@@ -2299,8 +1998,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2299
1998
  cliName: "sentiment breadth",
2300
1999
  method: "GET",
2301
2000
  path: "/v1/sentiment/breadth",
2302
- description: "Intraday breadth time series for one trading day.\n`trade_date` 缺省 今日(盘前 9:00 前返回空 items);显式传 YYYY-MM-DD 可\n拉历史任意交易日(来源 ClickHouse `market_breadth_intraday`,剔除 13:00\n稀疏分钟,单日最多 241 行)。Requires `sentiment:read` scope.\n",
2303
- summary: "Intraday breadth time series",
2001
+ description: "Returns the intraday market-breadth time series for one trading day. Omit `trade_date` for today (returns empty before 9:00); pass `YYYY-MM-DD` for any historical trading day (sparse minutes around the lunch break are excluded; up to 241 rows per day). Requires the `sentiment:read` scope.",
2002
+ summary: "Get the intraday market-breadth time series.",
2304
2003
  positional: [],
2305
2004
  outputDefault: "json",
2306
2005
  pagination: "none",
@@ -2342,8 +2041,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2342
2041
  cliName: "sentiment overview",
2343
2042
  method: "GET",
2344
2043
  path: "/v1/sentiment/overview",
2345
- description: "Aggregate sentiment snapshot for one trading day's latest minute:\nlimit-up/down counts, breadth, divergence index, MA / 52w breadth.\n`trade_date` 缺省 今日(Redis 实时,盘前 9:00 前返空结构);显式传\nYYYY-MM-DD 拉历史任意日(最后一分钟聚合行,来源 ClickHouse\n`market_breadth_intraday`)。Requires `sentiment:read` scope.\n",
2346
- summary: "Aggregate market sentiment indicators",
2044
+ description: "Returns an aggregate sentiment snapshot for the latest minute of one trading day: limit-up/down counts, breadth, divergence index, and moving-average / 52-week breadth. Omit `trade_date` for today's real-time snapshot (returns an empty structure before 9:00); pass `YYYY-MM-DD` for any historical day (the day's final aggregated minute). Requires the `sentiment:read` scope.",
2045
+ summary: "Get aggregate market sentiment indicators.",
2347
2046
  positional: [],
2348
2047
  outputDefault: "json",
2349
2048
  pagination: "none",
@@ -2386,8 +2085,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2386
2085
  cliName: "sentiment pct-distribution",
2387
2086
  method: "GET",
2388
2087
  path: "/v1/sentiment/pct-distribution",
2389
- description: "返回 A 股全市场涨跌幅分桶(≤-10% / -9% / ... / +10% / 涨停)的家数分布,用于市场宽度可视化。",
2390
- summary: "Distribution of intraday percent change across the universe",
2088
+ description: "Returns the distribution of intraday percent change across the full A-share universe, bucketed (≤-10% / -9% / ... / +10% / limit-up), for visualizing market breadth.",
2089
+ summary: "Get the intraday percent-change distribution.",
2391
2090
  positional: [],
2392
2091
  outputDefault: "json",
2393
2092
  pagination: "none",
@@ -2410,8 +2109,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2410
2109
  cliName: "sentiment turnover",
2411
2110
  method: "GET",
2412
2111
  path: "/v1/sentiment/turnover",
2413
- 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",
2414
- summary: "Intraday turnover time series",
2112
+ description: "Returns the intraday turnover time series across one or more trading days. Omit `trade_date` for today (returns empty before 9:00); pass `YYYY-MM-DD` for that day plus the preceding `days-1` days. The response includes headline fields `current_turnover`, `predicted_total`, and `delta_vs_yesterday`. Requires the `sentiment:read` scope.",
2113
+ summary: "Get the intraday turnover time series.",
2415
2114
  positional: [],
2416
2115
  outputDefault: "json",
2417
2116
  pagination: "none",
@@ -2460,8 +2159,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2460
2159
  cliName: "stocks hot",
2461
2160
  method: "GET",
2462
2161
  path: "/v1/stocks/hot",
2463
- description: "今日最热股票榜(来源 East-Money),按搜索 / 关注 / 评论综合分排序。",
2464
- summary: "Today's hot-stock leaderboard",
2162
+ description: "Returns today's most-popular A-share stocks, ranked by a composite score of searches, follows, and comments.",
2163
+ summary: "Get today's hot-stock leaderboard.",
2465
2164
  positional: [],
2466
2165
  outputDefault: "json",
2467
2166
  pagination: "none",
@@ -2482,8 +2181,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2482
2181
  cliName: "stocks social-hot",
2483
2182
  method: "GET",
2484
2183
  path: "/v1/stocks/social-hot",
2485
- description: "跨平台(微博 / 雪球 / Twitter)社交媒体提及聚合榜,按提及量 / 情感打分。",
2486
- summary: "Cross-platform social-hot stocks",
2184
+ description: "Returns a leaderboard of A-share stocks aggregated by social-media mentions across multiple platforms, ranked by mention volume and sentiment score.",
2185
+ summary: "Get cross-platform social-hot stocks.",
2487
2186
  positional: [],
2488
2187
  outputDefault: "json",
2489
2188
  pagination: "none",
@@ -2504,8 +2203,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2504
2203
  cliName: "views document",
2505
2204
  method: "GET",
2506
2205
  path: "/v1/views/{view_id}/document",
2507
- description: "取一条研报 view 背后的全文(mineru 解析的 parsed_md)。仅 `full_text_available=true` view 可用(gdrive 研报)。大文档默认 format=outline 只回标题目录 + total_chars;format=full offset/max_chars 切片正文并给 next_offset 游标翻页。需要 `views:read` scope。CLI 顶层 curated 别名:`report`(`echopai report <view_id>`)。",
2508
- summary: "Fetch the full research-report text behind a view",
2206
+ description: "Returns the full parsed text of the research report behind one view. Available only for views with `full_text_available=true`. Large documents default to `format=outline`, returning a heading-only table of contents plus total character count; use `format=full` with `offset` / `max_chars` to page through the body via the `next_offset` cursor. Requires the `views:read` scope.",
2207
+ summary: "Get the full research-report text behind a view.",
2509
2208
  positional: [
2510
2209
  "view_id"
2511
2210
  ],
@@ -2560,8 +2259,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2560
2259
  cliName: "views feed",
2561
2260
  method: "GET",
2562
2261
  path: "/v1/views",
2563
- description: "分析师观点 / 卖方研报 / 长文本观点流。可按机构 / 分析师 / 证券 / hours 过滤。需要 `views:read` scope",
2564
- summary: "List analyst views",
2262
+ description: "Returns a stream of analyst views, sell-side research, and long-form opinion. Filter by institution, analyst, security, or recency (`hours`). Requires the `views:read` scope.",
2263
+ summary: "List analyst views.",
2565
2264
  positional: [],
2566
2265
  outputDefault: "json",
2567
2266
  pagination: "offset",
@@ -2582,8 +2281,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2582
2281
  cliName: "views get",
2583
2282
  method: "GET",
2584
2283
  path: "/v1/views/{view_id}",
2585
- description: "返回单条 view 完整内容、附件图片、tagged securitiesAI 摘要。",
2586
- summary: "Fetch a single analyst view",
2284
+ description: "Returns the full content of one analyst view: body, attached images, tagged securities, and AI summary.",
2285
+ summary: "Get a single analyst view.",
2587
2286
  positional: [
2588
2287
  "view_id"
2589
2288
  ],
@@ -2614,8 +2313,8 @@ L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
2614
2313
  cliName: "views recent",
2615
2314
  method: "GET",
2616
2315
  path: "/v1/views/recent",
2617
- description: "List recent broker / analyst views (research notes, price targets, ratings). Filter by analyst, institution, or a specific security. Requires `views:read` scope.",
2618
- summary: "Recent broker / analyst views",
2316
+ description: "Returns recent broker / analyst views (research notes, price targets, ratings). Filter by analyst, institution, or a specific security. Requires the `views:read` scope.",
2317
+ summary: "List recent analyst views.",
2619
2318
  positional: [],
2620
2319
  outputDefault: "json",
2621
2320
  pagination: "none",
@@ -2729,7 +2428,7 @@ function attachOperation(cmd, op, dispatch) {
2729
2428
  function buildCommandTree(program, dispatch) {
2730
2429
  {
2731
2430
  const noun = program.command("agent");
2732
- noun.description("agent commands");
2431
+ noun.description("Manage agent billing sessions and identity.");
2733
2432
  {
2734
2433
  const cmd = noun.command("session-end");
2735
2434
  attachOperation(cmd, OPERATIONS["agent.session-end"], dispatch);
@@ -2745,7 +2444,7 @@ function buildCommandTree(program, dispatch) {
2745
2444
  }
2746
2445
  {
2747
2446
  const noun = program.command("announcements");
2748
- noun.description("announcements commands");
2447
+ noun.description("Listed-company disclosures: feed, per-stock, and full-text search.");
2749
2448
  {
2750
2449
  const cmd = noun.command("detail");
2751
2450
  attachOperation(cmd, OPERATIONS["announcements.detail"], dispatch);
@@ -2765,7 +2464,7 @@ function buildCommandTree(program, dispatch) {
2765
2464
  }
2766
2465
  {
2767
2466
  const noun = program.command("auth");
2768
- noun.description("auth commands");
2467
+ noun.description("Authentication and caller identity.");
2769
2468
  {
2770
2469
  const cmd = noun.command("whoami");
2771
2470
  attachOperation(cmd, OPERATIONS["auth.whoami"], dispatch);
@@ -2773,7 +2472,7 @@ function buildCommandTree(program, dispatch) {
2773
2472
  }
2774
2473
  {
2775
2474
  const noun = program.command("bars");
2776
- noun.description("bars commands");
2475
+ noun.description("Historical OHLC price bars — daily and minute, single or batch.");
2777
2476
  {
2778
2477
  const cmd = noun.command("daily");
2779
2478
  attachOperation(cmd, OPERATIONS["bars.daily"], dispatch);
@@ -2793,7 +2492,7 @@ function buildCommandTree(program, dispatch) {
2793
2492
  }
2794
2493
  {
2795
2494
  const noun = program.command("concepts");
2796
- noun.description("concepts commands");
2495
+ noun.description("Thematic concept boards: constituents, quotes, and alerts.");
2797
2496
  {
2798
2497
  const cmd = noun.command("alerts");
2799
2498
  attachOperation(cmd, OPERATIONS["concepts.alerts"], dispatch);
@@ -2841,7 +2540,7 @@ function buildCommandTree(program, dispatch) {
2841
2540
  }
2842
2541
  {
2843
2542
  const noun = program.command("financials");
2844
- noun.description("financials commands");
2543
+ noun.description("Point-in-time fundamental financials.");
2845
2544
  {
2846
2545
  const cmd = noun.command("pit");
2847
2546
  attachOperation(cmd, OPERATIONS["financials.pit"], dispatch);
@@ -2861,7 +2560,7 @@ function buildCommandTree(program, dispatch) {
2861
2560
  }
2862
2561
  {
2863
2562
  const noun = program.command("index");
2864
- noun.description("index commands");
2563
+ noun.description("Index quotes and snapshots.");
2865
2564
  {
2866
2565
  const cmd = noun.command("daily-bars");
2867
2566
  attachOperation(cmd, OPERATIONS["index.daily-bars"], dispatch);
@@ -2875,33 +2574,9 @@ function buildCommandTree(program, dispatch) {
2875
2574
  attachOperation(cmd, OPERATIONS["index.snapshot"], dispatch);
2876
2575
  }
2877
2576
  }
2878
- {
2879
- const noun = program.command("industries");
2880
- noun.description("industries commands");
2881
- {
2882
- const cmd = noun.command("alerts");
2883
- attachOperation(cmd, OPERATIONS["industries.alerts"], dispatch);
2884
- }
2885
- {
2886
- const cmd = noun.command("daily-bars");
2887
- attachOperation(cmd, OPERATIONS["industries.daily-bars"], dispatch);
2888
- }
2889
- {
2890
- const cmd = noun.command("list");
2891
- attachOperation(cmd, OPERATIONS["industries.list"], dispatch);
2892
- }
2893
- {
2894
- const cmd = noun.command("show");
2895
- attachOperation(cmd, OPERATIONS["industries.show"], dispatch);
2896
- }
2897
- {
2898
- const cmd = noun.command("snapshot");
2899
- attachOperation(cmd, OPERATIONS["industries.snapshot"], dispatch);
2900
- }
2901
- }
2902
2577
  {
2903
2578
  const noun = program.command("limit-up");
2904
- noun.description("limit-up commands");
2579
+ noun.description("Daily limit-up pool and attribution analysis.");
2905
2580
  {
2906
2581
  const cmd = noun.command("analysis");
2907
2582
  attachOperation(cmd, OPERATIONS["limit-up.analysis"], dispatch);
@@ -2921,7 +2596,7 @@ function buildCommandTree(program, dispatch) {
2921
2596
  }
2922
2597
  {
2923
2598
  const noun = program.command("market");
2924
- noun.description("market commands");
2599
+ noun.description("Market-wide trading status and breadth.");
2925
2600
  {
2926
2601
  const cmd = noun.command("status");
2927
2602
  attachOperation(cmd, OPERATIONS["market.status"], dispatch);
@@ -2929,7 +2604,7 @@ function buildCommandTree(program, dispatch) {
2929
2604
  }
2930
2605
  {
2931
2606
  const noun = program.command("news");
2932
- noun.description("news commands");
2607
+ noun.description("Market news feed and full-text search.");
2933
2608
  {
2934
2609
  const cmd = noun.command("feed");
2935
2610
  attachOperation(cmd, OPERATIONS["news.feed"], dispatch);
@@ -2949,7 +2624,7 @@ function buildCommandTree(program, dispatch) {
2949
2624
  }
2950
2625
  {
2951
2626
  const noun = program.command("ownership");
2952
- noun.description("ownership commands");
2627
+ noun.description("Insider trades, share buybacks, and lockup expirations.");
2953
2628
  {
2954
2629
  const cmd = noun.command("lockups");
2955
2630
  attachOperation(cmd, OPERATIONS["ownership.lockups"], dispatch);
@@ -2959,14 +2634,6 @@ function buildCommandTree(program, dispatch) {
2959
2634
  attachOperation(cmd, OPERATIONS["ownership.show"], dispatch);
2960
2635
  }
2961
2636
  }
2962
- {
2963
- const noun = program.command("payment");
2964
- noun.description("payment commands");
2965
- {
2966
- const cmd = noun.command("plans");
2967
- attachOperation(cmd, OPERATIONS["payment.plans"], dispatch);
2968
- }
2969
- }
2970
2637
  {
2971
2638
  const noun = program.command("quote");
2972
2639
  noun.description("quote commands");
@@ -2981,23 +2648,15 @@ function buildCommandTree(program, dispatch) {
2981
2648
  }
2982
2649
  {
2983
2650
  const noun = program.command("search");
2984
- noun.description("search commands");
2651
+ noun.description("Search securities and content.");
2985
2652
  {
2986
2653
  const cmd = noun.command("semantic");
2987
2654
  attachOperation(cmd, OPERATIONS["search.semantic"], dispatch);
2988
2655
  }
2989
2656
  }
2990
- {
2991
- const noun = program.command("securities");
2992
- noun.description("securities commands");
2993
- {
2994
- const cmd = noun.command("industry");
2995
- attachOperation(cmd, OPERATIONS["securities.industry"], dispatch);
2996
- }
2997
- }
2998
2657
  {
2999
2658
  const noun = program.command("semantic");
3000
- noun.description("semantic commands");
2659
+ noun.description("Meaning-based (semantic) security and content search.");
3001
2660
  {
3002
2661
  const cmd = noun.command("find");
3003
2662
  attachOperation(cmd, OPERATIONS["semantic.find"], dispatch);
@@ -3005,7 +2664,7 @@ function buildCommandTree(program, dispatch) {
3005
2664
  }
3006
2665
  {
3007
2666
  const noun = program.command("sentiment");
3008
- noun.description("sentiment commands");
2667
+ noun.description("Market sentiment and breadth indicators.");
3009
2668
  {
3010
2669
  const cmd = noun.command("breadth");
3011
2670
  attachOperation(cmd, OPERATIONS["sentiment.breadth"], dispatch);
@@ -3025,7 +2684,7 @@ function buildCommandTree(program, dispatch) {
3025
2684
  }
3026
2685
  {
3027
2686
  const noun = program.command("stocks");
3028
- noun.description("stocks commands");
2687
+ noun.description("Per-stock overview, hot lists, and screening.");
3029
2688
  {
3030
2689
  const cmd = noun.command("hot");
3031
2690
  attachOperation(cmd, OPERATIONS["stocks.hot"], dispatch);
@@ -3037,7 +2696,7 @@ function buildCommandTree(program, dispatch) {
3037
2696
  }
3038
2697
  {
3039
2698
  const noun = program.command("views");
3040
- noun.description("views commands");
2699
+ noun.description("Analyst and broker research views (primary research source).");
3041
2700
  {
3042
2701
  const cmd = noun.command("document");
3043
2702
  attachOperation(cmd, OPERATIONS["views.document"], dispatch);
@@ -3057,6 +2716,137 @@ function buildCommandTree(program, dispatch) {
3057
2716
  }
3058
2717
  }
3059
2718
 
2719
+ // src/runtime/dist.ts
2720
+ import { spawnSync } from "node:child_process";
2721
+ import { createHash } from "node:crypto";
2722
+ import { existsSync, readFileSync } from "node:fs";
2723
+ var RELEASE_BASE = process.env.ECHOPAI_RELEASE_BASE?.replace(/\/+$/, "") || "https://downloads.echopai.com/echopai-cli-releases";
2724
+ var VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+(-\S+)?$/;
2725
+ var SHA256_RE = /^[a-f0-9]{64}$/;
2726
+ var FETCH_TIMEOUT_MS = 8000;
2727
+ function isStandaloneFromUrl(url) {
2728
+ return url.includes("$bunfs") || url.includes("~BUN");
2729
+ }
2730
+ function isStandaloneBinary() {
2731
+ return isStandaloneFromUrl(import.meta.url);
2732
+ }
2733
+ function computePlatformKey(i) {
2734
+ let arch = i.arch === "x64" || i.arch === "amd64" ? "x64" : i.arch === "arm64" ? "arm64" : null;
2735
+ if (!arch)
2736
+ return null;
2737
+ if (i.platform === "darwin") {
2738
+ if (arch === "x64" && i.isRosetta)
2739
+ arch = "arm64";
2740
+ return `darwin-${arch}`;
2741
+ }
2742
+ if (i.platform === "win32") {
2743
+ return arch === "x64" ? "windows-x64" : null;
2744
+ }
2745
+ if (i.platform === "linux") {
2746
+ if (arch === "x64") {
2747
+ if (i.isMusl)
2748
+ return "linux-x64-musl";
2749
+ return i.hasAvx2 ? "linux-x64" : "linux-x64-baseline";
2750
+ }
2751
+ return i.isMusl ? "linux-arm64-musl" : "linux-arm64";
2752
+ }
2753
+ return null;
2754
+ }
2755
+ function detectMusl() {
2756
+ return existsSync("/lib/libc.musl-x86_64.so.1") || existsSync("/lib/libc.musl-aarch64.so.1");
2757
+ }
2758
+ function detectAvx2() {
2759
+ try {
2760
+ return /\bavx2\b/.test(readFileSync("/proc/cpuinfo", "utf8"));
2761
+ } catch {
2762
+ return false;
2763
+ }
2764
+ }
2765
+ function detectRosetta() {
2766
+ try {
2767
+ const r = spawnSync("sysctl", ["-n", "sysctl.proc_translated"], {
2768
+ encoding: "utf8",
2769
+ timeout: 1000
2770
+ });
2771
+ return r.stdout.trim() === "1";
2772
+ } catch {
2773
+ return false;
2774
+ }
2775
+ }
2776
+ function detectPlatformKey() {
2777
+ return computePlatformKey({
2778
+ platform: process.platform,
2779
+ arch: process.arch,
2780
+ isMusl: process.platform === "linux" ? detectMusl() : false,
2781
+ hasAvx2: process.platform === "linux" && process.arch === "x64" ? detectAvx2() : true,
2782
+ isRosetta: process.platform === "darwin" && process.arch === "x64" ? detectRosetta() : false
2783
+ });
2784
+ }
2785
+ function parseManifestChecksum(manifest, platform) {
2786
+ if (!manifest || typeof manifest !== "object")
2787
+ return null;
2788
+ const platforms = manifest.platforms;
2789
+ if (!platforms || typeof platforms !== "object")
2790
+ return null;
2791
+ const entry = platforms[platform];
2792
+ if (!entry || typeof entry !== "object")
2793
+ return null;
2794
+ const cs = entry.checksum;
2795
+ return typeof cs === "string" && SHA256_RE.test(cs) ? cs : null;
2796
+ }
2797
+ async function fetchText(url) {
2798
+ const ac = new AbortController;
2799
+ const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
2800
+ try {
2801
+ const res = await fetch(url, { signal: ac.signal });
2802
+ if (!res.ok)
2803
+ return null;
2804
+ return await res.text();
2805
+ } catch {
2806
+ return null;
2807
+ } finally {
2808
+ clearTimeout(timer);
2809
+ }
2810
+ }
2811
+ async function resolveLatestFromCdn() {
2812
+ const txt = await fetchText(`${RELEASE_BASE}/latest`);
2813
+ if (txt == null)
2814
+ return null;
2815
+ const v = txt.trim();
2816
+ return VERSION_RE.test(v) ? v : null;
2817
+ }
2818
+ async function fetchManifest(version) {
2819
+ const txt = await fetchText(`${RELEASE_BASE}/${version}/manifest.json`);
2820
+ if (txt == null)
2821
+ return null;
2822
+ try {
2823
+ return JSON.parse(txt);
2824
+ } catch {
2825
+ return null;
2826
+ }
2827
+ }
2828
+ function binaryUrl(version, platform) {
2829
+ const name = platform.startsWith("windows-") ? "echopai.exe" : "echopai";
2830
+ return `${RELEASE_BASE}/${version}/${platform}/${name}`;
2831
+ }
2832
+ async function downloadAndVerify(url, expectedSha) {
2833
+ const ac = new AbortController;
2834
+ const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS * 4);
2835
+ try {
2836
+ const res = await fetch(url, { signal: ac.signal });
2837
+ if (!res.ok)
2838
+ throw new Error(`下载失败 HTTP ${res.status}: ${url}`);
2839
+ const buf = Buffer.from(await res.arrayBuffer());
2840
+ const actual = createHash("sha256").update(buf).digest("hex");
2841
+ if (actual !== expectedSha) {
2842
+ throw new Error(`校验和不匹配(期望 ${expectedSha},实际 ${actual})`);
2843
+ }
2844
+ return buf;
2845
+ } finally {
2846
+ clearTimeout(timer);
2847
+ }
2848
+ }
2849
+
3060
2850
  // src/runtime/invoker.ts
3061
2851
  import AjvPkg from "ajv";
3062
2852
  import addFormatsPkg from "ajv-formats";
@@ -3563,9 +3353,9 @@ function resolveRequestId(serverHeader, clientGenerated) {
3563
3353
  // src/runtime/trace.ts
3564
3354
  import {
3565
3355
  appendFileSync,
3566
- existsSync as existsSync2,
3356
+ existsSync as existsSync3,
3567
3357
  mkdirSync as mkdirSync2,
3568
- readFileSync as readFileSync2,
3358
+ readFileSync as readFileSync3,
3569
3359
  renameSync,
3570
3360
  statSync
3571
3361
  } from "node:fs";
@@ -3586,7 +3376,7 @@ function appendTrace(record, opts) {
3586
3376
  const maxBytes = opts?.ringMaxBytes ?? RING_MAX_BYTES;
3587
3377
  try {
3588
3378
  mkdirSync2(dirname(file), { recursive: true });
3589
- if (existsSync2(file)) {
3379
+ if (existsSync3(file)) {
3590
3380
  const size = statSync(file).size;
3591
3381
  if (size >= maxBytes) {
3592
3382
  renameSync(file, file + ".1");
@@ -3598,9 +3388,9 @@ function appendTrace(record, opts) {
3598
3388
  }
3599
3389
  function readNdjsonLines(file) {
3600
3390
  try {
3601
- if (!existsSync2(file))
3391
+ if (!existsSync3(file))
3602
3392
  return [];
3603
- const raw = readFileSync2(file, "utf-8");
3393
+ const raw = readFileSync3(file, "utf-8");
3604
3394
  const out = [];
3605
3395
  for (const line of raw.split(`
3606
3396
  `)) {
@@ -3824,12 +3614,12 @@ function renderError(env) {
3824
3614
  }
3825
3615
 
3826
3616
  // src/runtime/update_check.ts
3827
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
3617
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
3828
3618
  import os2 from "node:os";
3829
3619
  import path2 from "node:path";
3830
3620
 
3831
3621
  // src/version.ts
3832
- var CLI_VERSION = "2.5.0";
3622
+ var CLI_VERSION = "2.7.0";
3833
3623
 
3834
3624
  // src/runtime/update_check.ts
3835
3625
  var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
@@ -3864,7 +3654,7 @@ function isNewer(latest, current) {
3864
3654
  }
3865
3655
  function readCachedUpdate() {
3866
3656
  try {
3867
- const raw = readFileSync3(UPDATE_CACHE_PATH, "utf-8");
3657
+ const raw = readFileSync4(UPDATE_CACHE_PATH, "utf-8");
3868
3658
  const obj = JSON.parse(raw);
3869
3659
  if (typeof obj.checked_at === "string" && typeof obj.current === "string" && typeof obj.latest === "string") {
3870
3660
  return obj;
@@ -4279,139 +4069,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4279
4069
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4280
4070
  import { Command as Command20, Option as Option18 } from "commander";
4281
4071
 
4282
- // src/runtime/dist.ts
4283
- import { spawnSync } from "node:child_process";
4284
- import { createHash } from "node:crypto";
4285
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
4286
- import { fileURLToPath } from "node:url";
4287
- var RELEASE_BASE = process.env.ECHOPAI_RELEASE_BASE?.replace(/\/+$/, "") || "https://downloads.echopai.com/echopai-cli-releases";
4288
- var VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+(-\S+)?$/;
4289
- var SHA256_RE = /^[a-f0-9]{64}$/;
4290
- var FETCH_TIMEOUT_MS = 8000;
4291
- function isStandaloneBinary() {
4292
- try {
4293
- return !existsSync3(fileURLToPath(import.meta.url));
4294
- } catch {
4295
- return true;
4296
- }
4297
- }
4298
- function computePlatformKey(i) {
4299
- let arch = i.arch === "x64" || i.arch === "amd64" ? "x64" : i.arch === "arm64" ? "arm64" : null;
4300
- if (!arch)
4301
- return null;
4302
- if (i.platform === "darwin") {
4303
- if (arch === "x64" && i.isRosetta)
4304
- arch = "arm64";
4305
- return `darwin-${arch}`;
4306
- }
4307
- if (i.platform === "win32") {
4308
- return arch === "x64" ? "windows-x64" : null;
4309
- }
4310
- if (i.platform === "linux") {
4311
- if (arch === "x64") {
4312
- if (i.isMusl)
4313
- return "linux-x64-musl";
4314
- return i.hasAvx2 ? "linux-x64" : "linux-x64-baseline";
4315
- }
4316
- return i.isMusl ? "linux-arm64-musl" : "linux-arm64";
4317
- }
4318
- return null;
4319
- }
4320
- function detectMusl() {
4321
- return existsSync3("/lib/libc.musl-x86_64.so.1") || existsSync3("/lib/libc.musl-aarch64.so.1");
4322
- }
4323
- function detectAvx2() {
4324
- try {
4325
- return /\bavx2\b/.test(readFileSync4("/proc/cpuinfo", "utf8"));
4326
- } catch {
4327
- return false;
4328
- }
4329
- }
4330
- function detectRosetta() {
4331
- try {
4332
- const r = spawnSync("sysctl", ["-n", "sysctl.proc_translated"], {
4333
- encoding: "utf8",
4334
- timeout: 1000
4335
- });
4336
- return r.stdout.trim() === "1";
4337
- } catch {
4338
- return false;
4339
- }
4340
- }
4341
- function detectPlatformKey() {
4342
- return computePlatformKey({
4343
- platform: process.platform,
4344
- arch: process.arch,
4345
- isMusl: process.platform === "linux" ? detectMusl() : false,
4346
- hasAvx2: process.platform === "linux" && process.arch === "x64" ? detectAvx2() : true,
4347
- isRosetta: process.platform === "darwin" && process.arch === "x64" ? detectRosetta() : false
4348
- });
4349
- }
4350
- function parseManifestChecksum(manifest, platform) {
4351
- if (!manifest || typeof manifest !== "object")
4352
- return null;
4353
- const platforms = manifest.platforms;
4354
- if (!platforms || typeof platforms !== "object")
4355
- return null;
4356
- const entry = platforms[platform];
4357
- if (!entry || typeof entry !== "object")
4358
- return null;
4359
- const cs = entry.checksum;
4360
- return typeof cs === "string" && SHA256_RE.test(cs) ? cs : null;
4361
- }
4362
- async function fetchText(url) {
4363
- const ac = new AbortController;
4364
- const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
4365
- try {
4366
- const res = await fetch(url, { signal: ac.signal });
4367
- if (!res.ok)
4368
- return null;
4369
- return await res.text();
4370
- } catch {
4371
- return null;
4372
- } finally {
4373
- clearTimeout(timer);
4374
- }
4375
- }
4376
- async function resolveLatestFromCdn() {
4377
- const txt = await fetchText(`${RELEASE_BASE}/latest`);
4378
- if (txt == null)
4379
- return null;
4380
- const v = txt.trim();
4381
- return VERSION_RE.test(v) ? v : null;
4382
- }
4383
- async function fetchManifest(version) {
4384
- const txt = await fetchText(`${RELEASE_BASE}/${version}/manifest.json`);
4385
- if (txt == null)
4386
- return null;
4387
- try {
4388
- return JSON.parse(txt);
4389
- } catch {
4390
- return null;
4391
- }
4392
- }
4393
- function binaryUrl(version, platform) {
4394
- const name = platform.startsWith("windows-") ? "echopai.exe" : "echopai";
4395
- return `${RELEASE_BASE}/${version}/${platform}/${name}`;
4396
- }
4397
- async function downloadAndVerify(url, expectedSha) {
4398
- const ac = new AbortController;
4399
- const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS * 4);
4400
- try {
4401
- const res = await fetch(url, { signal: ac.signal });
4402
- if (!res.ok)
4403
- throw new Error(`下载失败 HTTP ${res.status}: ${url}`);
4404
- const buf = Buffer.from(await res.arrayBuffer());
4405
- const actual = createHash("sha256").update(buf).digest("hex");
4406
- if (actual !== expectedSha) {
4407
- throw new Error(`校验和不匹配(期望 ${expectedSha},实际 ${actual})`);
4408
- }
4409
- return buf;
4410
- } finally {
4411
- clearTimeout(timer);
4412
- }
4413
- }
4414
-
4415
4072
  // src/runtime/whoami_cache.ts
4416
4073
  var DEFAULT_TTL_MS = 5 * 60 * 1000;
4417
4074
  var cache = null;
@@ -7529,370 +7186,273 @@ import { z as z17 } from "zod";
7529
7186
  // src/_generated/help.ts
7530
7187
  var HELP = {
7531
7188
  "agent.session-end": {
7532
- summary: "End a billable agent session",
7533
- description: "关闭 `/v1/agent/session/start` 开启的会话,最终用量提交入库。",
7189
+ summary: "Close a billable agent session.",
7190
+ description: "Closes a session opened by `/v1/agent/session/start` and commits its final usage.",
7534
7191
  example: "echopai agent session-end VALUE"
7535
7192
  },
7536
7193
  "agent.session-start": {
7537
- summary: "Start a billable agent session",
7538
- description: "开启 agent kind 凭据的计费会话。返回 session_id,用于将多次调用归并到一次 billable 单位。需要 `Idempotency-Key` 头。",
7194
+ summary: "Open a billable agent session.",
7195
+ description: "Opens a billable session for an agent credential and returns a session_id that groups subsequent calls into a single billable unit. Requires an `Idempotency-Key` header.",
7539
7196
  example: "echopai agent session-start --agent-id agent-prod-1 --budget-usd 1.0"
7540
7197
  },
7541
7198
  "agent.session-usage": {
7542
- summary: "Get cumulative usage of an agent session",
7543
- description: "返回 session 内累计 billable 调用次数、各 scope 配额消耗。",
7199
+ summary: "Get cumulative usage for an agent session.",
7200
+ description: "Returns the cumulative billable call count and per-scope quota consumption for the given session.",
7544
7201
  example: "echopai agent session-usage VALUE"
7545
7202
  },
7546
7203
  "announcements.detail": {
7547
- summary: "Fetch a single announcement by id",
7548
- description: "返回公告完整内容:content_md、content_text、cninfo 元数据、解析状态。",
7204
+ summary: "Get a single announcement by id.",
7205
+ description: "Returns the full content of one announcement: body (markdown and plain text), metadata, and parse status.",
7549
7206
  example: "echopai announcements detail VALUE"
7550
7207
  },
7551
7208
  "announcements.feed": {
7552
- summary: "List recent A-share announcements",
7553
- description: "A 股公告 feed,按 published_at 降序、同日按 cninfo source_id 降序。可按 type / since 过滤。需要 `announcements:read` scope",
7209
+ summary: "List recent A-share announcements.",
7210
+ description: "Returns a feed of recent A-share corporate announcements, sorted by publication date descending. Filter by `type` or `since`. Requires the `announcements:read` scope.",
7554
7211
  example: "echopai announcements feed"
7555
7212
  },
7556
7213
  "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",
7214
+ summary: "Search A-share announcements.",
7215
+ description: "Hybrid search over A-share announcements: structured filtering by security name or code, combined with full-text keyword search (over title, AI summary, and the first ~1500 characters of the body). A query containing a 6-digit code or an embedded security name is filtered by that security; remaining free text is ranked by relevance with time decay; a pure code or name lists results by publication date descending. Requires the `announcements:read` scope.",
7559
7216
  example: "echopai announcements search --q 新华医疗 回购"
7560
7217
  },
7561
7218
  "announcements.stock": {
7562
- summary: "List announcements for a specific A-share security",
7563
- description: "指定股票代码的公告列表(含历史窗口)。代码接受 6 位纯数字或 SSE:600519 / SZSE:000001 / BSE:430000 形式。需要 `announcements:read` scope",
7219
+ summary: "List announcements for one security.",
7220
+ description: "Returns the announcement history for one A-share security over a date window. Accepts a 6-digit code or canonical form (e.g. `SSE:600519`). Requires the `announcements:read` scope.",
7564
7221
  example: "echopai announcements stock --code SSE:600519"
7565
7222
  },
7566
7223
  "auth.whoami": {
7567
- summary: "Token introspection (CLI/MCP capability discovery)",
7568
- description: `Returns the calling token's kind, scopes, audience, app metadata,
7569
- rate_limit, allowed_clients, agent_budget (if kind=agent), api_version,
7570
- feature_flags. Any valid JWT can call — no specific scope required.
7571
-
7572
- CLI/MCP call this once at startup, cache 5 minutes in-process, and use
7573
- the response to derive \`verbs.available\` (intersection of curated verb
7574
- scopes with token scopes) and to populate \`echopai doctor\` checks.
7575
-
7576
- See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §5.1.
7577
- `,
7224
+ summary: "Introspect the calling token (capability discovery).",
7225
+ description: "Returns the calling token's kind, scopes, audience, app metadata, rate limit, allowed clients, agent budget (when kind=agent), API version, and feature flags. Any valid token may call this; no specific scope is required. Clients typically call it once at startup and cache the result briefly to determine which commands are available.",
7578
7226
  example: "echopai auth whoami"
7579
7227
  },
7580
7228
  "bars.daily": {
7581
- summary: "Daily OHLC bars for one A-share security",
7582
- description: "Daily OHLC bars for one A-share security over a date range. Returns open / high / low / close / volume / turnover per trading day. Requires `bars:30d` (last 30 trading days) or `bars:full` (full history) scope.",
7229
+ summary: "List daily OHLC bars for one A-share security.",
7230
+ description: "Returns daily open/high/low/close/volume/turnover for one A-share security over a date range (canonical code, e.g. `SSE:600519`). End-of-day data. Requires the `bars:30d` scope (last 30 trading days) or `bars:full` scope (full history).",
7583
7231
  example: "echopai bars daily --code SSE:600519 --from 2026-01-01 --to 2026-05-01"
7584
7232
  },
7585
7233
  "bars.daily-batch": {
7586
- summary: "Batch daily OHLC bars for up to 100 A-share securities",
7587
- description: "Batch daily OHLC bars for multiple A-share securities. Up to 100 codes\nper call, up to 1-year date range. Single round-trip; ClickHouse `IN(...)`\nunder the hood.\n\nReturns partial success envelope: codes denied by `allowed_securities`\nappear in `errors[]`, others in `items[]` (with empty `bars[]` if no\ndata found). Order in `items[]` matches input order.\n\nRequires `bars:30d` or `bars:full` scope.\n",
7234
+ summary: "Batch daily OHLC bars for up to 100 securities.",
7235
+ description: "Returns daily OHLC bars for multiple A-share securities in one call: up to 100 codes and up to a one-year date range. Responses use a partial-success envelope: codes the token may not access are reported in `errors[]`, the rest in `items[]` (with an empty `bars[]` when no data is found), preserving input order. Requires the `bars:30d` or `bars:full` scope.",
7588
7236
  example: "echopai bars daily-batch --codes ['SSE:600519', 'SZSE:000001']"
7589
7237
  },
7590
7238
  "bars.minute": {
7591
- summary: "Minute-level OHLC bars for one A-share security",
7592
- description: "Minute-level OHLC bars for one A-share security on a single trade date. Requires `bars:30d` or `bars:full` scope.",
7239
+ summary: "List minute OHLC bars for one A-share security.",
7240
+ description: "Returns minute-level OHLC bars for one A-share security on a single trade date (canonical code, e.g. `SSE:600519`). Requires the `bars:30d` or `bars:full` scope.",
7593
7241
  example: "echopai bars minute --code SSE:600519 --date 2026-05-09"
7594
7242
  },
7595
7243
  "bars.minute-batch": {
7596
- summary: "Batch minute OHLC bars for up to 20 A-share securities",
7597
- description: "Batch minute OHLC bars for multiple A-share securities. Up to 20 codes\nper call, up to 7 calendar days (5 trading days) date range. Single\nClickHouse round-trip; app groups rows by canonical_code.\n\nPartial success envelope same as daily-batch: codes denied by\n`allowed_securities` go to `errors[]`; others to `items[]` with flat\n`bars[]` (each bar carries its own `trade_date` for client grouping).\n\nRequires `bars:30d` or `bars:full` scope.\n",
7244
+ summary: "Batch minute OHLC bars for up to 20 securities.",
7245
+ description: "Returns minute-level OHLC bars for multiple A-share securities in one call: up to 20 codes and up to a 7 calendar-day (~5 trading-day) range. Responses use a partial-success envelope: codes the token may not access are reported in `errors[]`, the rest in `items[]` with a flat `bars[]` array where each bar carries its own `trade_date` for client-side grouping. Requires the `bars:30d` or `bars:full` scope.",
7598
7246
  example: "echopai bars minute-batch --codes ['SSE:600519', 'SZSE:000001']"
7599
7247
  },
7600
7248
  "concepts.alerts": {
7601
- summary: "Currently active concept alerts (big_move / limit_up_cluster)",
7602
- description: `Returns the live \`concept_alerts:active\` hash. Two rules:
7603
- - big_move: abs(pct_change) > 3%
7604
- - limit_up_cluster: limit_up_count >= 3 AND stock_count >= 5
7605
- See PLAN_CONCEPT_INDUSTRY_QUOTE §5.5 "异动检测与推送".
7606
- `,
7249
+ summary: "List currently active concept alerts.",
7250
+ description: "Returns the live set of concept alerts. Two rules fire: `big_move` when absolute percent change exceeds 3%, and `limit_up_cluster` when a concept has at least 3 limit-up members across at least 5 member stocks.",
7607
7251
  example: "echopai concepts alerts"
7608
7252
  },
7609
7253
  "concepts.alerts-history": {
7610
- summary: "Concept alerts history (last 24h)",
7611
- description: "Concept异动 historical events (default last 24h). Filter by rule (big_move / limit_up_cluster) and time window. Used by AI agent / admin review.",
7254
+ summary: "List recent concept alert history.",
7255
+ description: "Returns historical concept alert events (default: last 24 hours). Filter by rule (`big_move` / `limit_up_cluster`) and time window. Useful for reviewing how concept activity evolved over a period.",
7612
7256
  example: "echopai concepts alerts-history"
7613
7257
  },
7614
7258
  "concepts.daily-bars": {
7615
- summary: "Concept index daily bars (chain-linked equal-weight, base 1000)",
7616
- description: `Returns daily OHLC (concept index points, not stock prices average),
7617
- breadth fields, and is_backfilled flag. See PLAN §5.4 algorithm.
7618
- `,
7259
+ summary: "List daily bars for a concept index.",
7260
+ description: "Returns daily OHLC for a concept index (index points, not an average of member stock prices), along with breadth fields and a backfill flag. The concept index is a chain-linked equal-weight return series with a base of 1000.",
7619
7261
  example: "echopai concepts daily-bars VALUE"
7620
7262
  },
7621
7263
  "concepts.list": {
7622
- summary: "List concepts with live snapshot (hot/warm fallback)",
7623
- description: "Concept list joining CH `v_concept_meta` with Redis snapshot hashes,\nfollowing the hot/warm two-key pattern (PR #438):\n\nRead order **per concept** (not global):\n 1. `concept_snapshots:latest` (hot, TTL 5min) fresh tick\n 2. `concept_snapshots:last_close` (warm, TTL 7d) — post-close / weekend\n 3. CH `v_concept_daily_bars` argMax — extreme fallback (~ 7d+)\n\n9:00–9:14 pre-open reset window: hot + warm both suppressed (loading state),\nregardless of TTL; CH fallback is also disabled in this window.\n\nEach item carries `_source` {`latest`, `last_close`, `ch_daily`, `miss`}\nso the client can render a freshness badge.\n\nLive fields: pct_change / index_value / up/down_count / limit_up_count /\namount / speed_3min / `main_net_in`. `main_net_in` (元) is the concept-level\nsum of member-stocks' fundflow `main_net_in` (collector MGETs\n`stockpulse:fundflow:latest:{code}` each aggregation round). May be negative\n(net outflow); null when collector / Redis snapshot unavailable.\n\nSource / status / index are computed per PLAN_CONCEPT_INDUSTRY_QUOTE\n§5.4 algorithm (chain-linked equal-weight returns, base = 1000).\n",
7264
+ summary: "List concepts with their live snapshot.",
7265
+ description: "Lists market concepts (themes/sectors) joined with their latest real-time snapshot, falling back to the last close and then to recent daily data when live ticks are unavailable. Each item carries a `_source` freshness indicator. Live fields include percent change, index value, advancing/declining counts, limit-up count, turnover amount, 3-minute momentum, and `main_net_in` (net main-capital inflow in CNY, which may be negative for net outflow and null when unavailable). The concept index is a chain-linked equal-weight return series with a base of 1000. During the 9:00–9:14 pre-open window, live data is suppressed and a loading state is returned.",
7624
7266
  example: "echopai concepts list"
7625
7267
  },
7626
7268
  "concepts.minute-bars": {
7627
- summary: "Concept index minute bars (max 7 days)",
7628
- description: "Concept index minute-level OHLC (chain-linked equal-weight, base = 1000), max 7 days per request. See PLAN_CONCEPT_INDUSTRY_QUOTE §5.4.",
7269
+ summary: "List minute bars for a concept index.",
7270
+ description: "Returns minute-level OHLC for a concept index (chain-linked equal-weight return series, base 1000), up to 7 days per request.",
7629
7271
  example: "echopai concepts minute-bars VALUE"
7630
7272
  },
7631
7273
  "concepts.news": {
7632
- summary: "Related news matching concept name (ILIKE)",
7633
- description: `Short-term implementation: ILIKE on news.title + content with the
7634
- concept name and full_name. Long-term: news AI tagging.
7635
- `,
7274
+ summary: "List news related to a concept.",
7275
+ description: "Returns recent news whose title or body mentions the concept's name. Use it to see what is currently driving a theme.",
7636
7276
  example: "echopai concepts news VALUE"
7637
7277
  },
7638
7278
  "concepts.show": {
7639
- summary: "Concept detail (meta + member securities)",
7640
- description: "Detail view for one concept: meta (full_name / source / status / approved_at / first_listed_at) plus current member securities with their latest snapshots.",
7279
+ summary: "Get concept detail with member securities.",
7280
+ description: "Returns detail for one concept: metadata (full name, source, status, approval date, first-listed date) plus its current member securities with their latest snapshots.",
7641
7281
  example: "echopai concepts show VALUE"
7642
7282
  },
7643
7283
  "concepts.snapshot": {
7644
- summary: "Bulk Redis snapshot of concepts (hot/warm union)",
7645
- description: "Two contracts depending on `codes` parameter:\n\n- **With `codes=`** (per-key completeness): each requested id is resolved\n via hotwarmCH three-tier fallback. Missing ids are populated.\n- **Without `codes`** (best-effort dump): returns the **union** of\n `concept_snapshots:latest` and `concept_snapshots:last_close` Redis\n hashes. Concepts absent from both Redis hashes are **not** backfilled\n from CH. Use GET `/v1/concepts` instead if you need every known concept.\n\n9:00–9:14 pre-open reset window: hot and warm both suppressed, CH fallback\nalso disabled; the endpoint returns an empty list (loading state).\n\nEach item carries `_source` ∈ {`latest`, `last_close`, `ch_daily`}.\n",
7284
+ summary: "Bulk real-time snapshot of concepts.",
7285
+ description: "Returns real-time snapshots for concepts, with two modes. With `codes=`, each requested concept id is resolved via real-timelast-closerecent-daily fallback so every requested id is populated. Without `codes`, it returns a best-effort dump of all concepts that currently have a real-time or last-close snapshot; concepts absent from both are not backfilled (use `GET /v1/concepts` if you need every known concept). Each item carries a `_source` freshness indicator. During the 9:00–9:14 pre-open window, live data is suppressed and an empty list is returned.",
7646
7286
  example: "echopai concepts snapshot"
7647
7287
  },
7648
7288
  "concepts.views": {
7649
- summary: "Broker views associated with this concept",
7650
- description: "JOIN concept_views × broker_views (ai_status=done) sorted by published_at desc.",
7289
+ summary: "List analyst views associated with a concept.",
7290
+ description: "Returns analyst views linked to this concept (with AI processing complete), sorted by publication date descending.",
7651
7291
  example: "echopai concepts views VALUE"
7652
7292
  },
7653
7293
  "digest.get": {
7654
- summary: "One-shot research digest for a single security (composite)",
7655
- description: `Composite endpoint: in one HTTP call returns views (PRIMARY research),
7656
- real-time quote, valuation snapshot (PE/PB/PS/换手率/股息率/量比 等 14 字段),
7657
- market sentiment context, and supplementary news.
7658
- Partial-failure tolerant:
7659
- each bucket is independently fetched and per-bucket failures surface
7660
- in \`meta.partial_failures[]\` rather than poisoning the response. If
7661
- every sub-bucket fails the endpoint returns 502.
7662
-
7663
- Bucket scopes are checked *per bucket* (not at gateway level): a
7664
- token with only \`views:read\` gets the views bucket populated and the
7665
- rest reported as \`scope_insufficient\` partial failures. This mirrors
7666
- the CLI fan-out contract exactly so \`echopai digest\` can either call
7667
- this endpoint (preferred, single round-trip) or fall back to local
7668
- fan-out without behavior drift.
7669
-
7670
- See \`docs/PLAN_CLI_V2_AGENT_SURFACE.md\` §3.3 (digest spec) and §11
7671
- Phase 5.2 (server endpoint).
7672
- `,
7294
+ summary: "Build a one-shot research digest for one security.",
7295
+ description: "Composite endpoint that, in a single call, returns separated sections for one security: analyst views (the primary research source), a real-time quote, a valuation snapshot (PE/PB/PS, turnover rate, dividend yield, volume ratio, and ~14 fields in total), market sentiment context, and supplementary news. Each section is fetched independently and per-section failures are reported in `meta.partial_failures[]` instead of failing the whole call; the endpoint returns 502 only if every section fails. Scopes are enforced per section, so a token with only `views:read` receives the views section and sees the rest reported as scope-insufficient. Use this when you want a single broad overview of a security (canonical code, e.g. `SSE:600519`).",
7673
7296
  example: "echopai digest get HK:00700"
7674
7297
  },
7675
7298
  "financials.pit": {
7676
- summary: "Point-in-time financial indicators for one A-share at a given date",
7677
- description: "基于 TDX 财务数据的 Point-in-time 查询。给定 `code` `date`,返回该\n`trade_date` 当时市场上可见的最新一期财务报告(`announce_date <= date`,\n无公告日时保守延迟 90 天)。专为回测、AI agent、量化策略防未来函数泄漏。\n\n返回字段包括:EPS(基本/扣非/稀释)、BPSROE、毛利率、营收/净利润/总资产/\n归母权益等核心 ~25 字段。\n",
7299
+ summary: "Get point-in-time fundamentals for one security at a date.",
7300
+ description: "Returns the most recent financial report that was publicly available on a given date for one A-share security (canonical code, e.g. `SSE:600519`) i.e. announcement date on or before `date`, with a conservative 90-day fallback when the announcement date is unknown. Designed to avoid look-ahead bias in backtests and quantitative strategies. Fields include EPS (basic / non-recurring / diluted), BPS, ROE, gross margin, revenue, net profit, total assets, and parent-company equity (~25 core fields).",
7678
7301
  example: "echopai financials pit --code SSE:600519 --date 2024-11-01"
7679
7302
  },
7680
7303
  "financials.quote-snapshot": {
7681
- summary: "Real-time valuation / share / turnover snapshot for one A-share",
7682
- description: "一站式估值快照 —— 一次返回 14 个字段,**全部基于 TDX 原始数据 + Sina 实时\n价自算,不依赖 Tushare**:\n\n- 估值: `pe` / `pe_ttm` / `pb` / `ps` / `ps_ttm`\n- 股本(万股): `total_share` / `float_share` / `free_share`\n- 市值(万元): `total_mv` / `circ_mv`\n- 流动性: `turnover_rate` / `turnover_rate_f`(%)/ `volume_ratio`(倍)\n- 分红: `dv_ratio` / `dv_ttm`(%)\n\n计算口径:\n- PE-TTM = 当前总市值 / 4 季度滚动净利润(PIT 表 `ni_parent_ttm`)\n- PE = 当前总市值 / 上年报净利润(`ni_parent_last1y`)\n- PB = 当前总市值 / 归母净资产(`equity_parent`,PIT 防穿越)\n- 换手率 = 当日累计成交量 / 流通股本 × 100\n- 量比 = 当日累计 / 近 5 日同时段累计平均(实时模式)\n / 近 5 日全日成交量平均(指定历史日期模式)\n- 股息率 = Σ(去年 / 近12月内派息事件 派现/10) / 现价 × 100\n\n默认 `date` 留空 实时(Sina 5s 快照价 + 当日累计成交量);\n给 `date=YYYY-MM-DD` 则按当日 close 取值(用于历史回测 / 校验)。\n\n财报数据走 `security_financial_indicators_pit` PIT 视图:保证 `visible_date`\n≤ trade_date,避免未来函数。\n\n准确性已对照 Tushare `daily_basic` 校验(见 `scripts/validation/validate_quote_snapshot_vs_tushare.py`)。\n",
7304
+ summary: "Get a real-time valuation snapshot for one security.",
7305
+ description: "Returns a one-stop valuation snapshot (14 fields) for one A-share security (canonical code, e.g. `SSE:600519`), combining real-time prices with point-in-time fundamentals so figures are free of look-ahead bias. Fields: valuation (`pe`, `pe_ttm`, `pb`, `ps`, `ps_ttm`); share counts in 10k shares (`total_share`, `float_share`, `free_share`); market cap in 10k CNY (`total_mv`, `circ_mv`); liquidity (`turnover_rate`, `turnover_rate_f` in %, `volume_ratio` as a multiple); and dividend yield (`dv_ratio`, `dv_ttm` in %). Leave `date` empty for a real-time snapshot, or pass `date=YYYY-MM-DD` to value as of that day's close (useful for historical checks and backtests).",
7683
7306
  example: "echopai financials quote-snapshot --code SSE:600519 --date 2026-05-13"
7684
7307
  },
7685
7308
  "financials.reports": {
7686
- summary: "Recent financial reports for one A-share security",
7687
- description: "返回该股票最近 N 期财务报告的核心指标(EPS/BPS/ROE/毛利率/营收/净利润/总\n资产/归母权益/经营现金流/总股本 ~25 字段)。可按 `kind` 过滤季报/半年报/\n年报。每期 `announce_date` 字段告知何时市场可见,建议结合回测时使用。\n\n**`kind` 口径说明**:\n\n- `Q1` / `H1` / `Q3` / `annual` —— 四个标准季度末快照(3-31 / 6-30 /\n 9-30 / 12-31),TDX 财务源数据全部落在这四个 report_date。\n- `preliminary` —— **当前数据源结构上不存在**。业绩预告 / 快报 indicators\n 行内的字段(`forecast_ni_lower` / `forecast_ni_upper` /\n `express_ni_parent` / `express_revenue` 等),通过 `financials.series`\n metric 参数访问,不是单独的 report_kind 行。`kind=preliminary` 会返\n 空列表 + meta.note。\n",
7309
+ summary: "List recent financial reports for one security.",
7310
+ description: "Returns core indicators for the most recent N financial reports of one A-share security (EPS/BPS/ROE, gross margin, revenue, net profit, total assets, parent equity, operating cash flow, total shares, and ~25 fields in total). Filter by `kind` for quarterly / interim / annual reports. Each report's `announce_date` indicates when it became publicly visible, which is useful for point-in-time analysis. Note: `kind=preliminary` does not exist as a separate report row — earnings forecasts and flash reports are exposed as metric fields (e.g. `forecast_ni_lower`, `forecast_ni_upper`, `express_ni_parent`, `express_revenue`) via `financials.series`, so `kind=preliminary` returns an empty list with an explanatory note.",
7688
7311
  example: "echopai financials reports --code SSE:600519"
7689
7312
  },
7690
7313
  "financials.series": {
7691
- summary: "Time series of a single financial metric for one security",
7692
- description: "返回某只股票某个财务指标的历史时间序列(按报告期排列)。`metric` 是\nindicators 表中的字段名(如 `roe_simple` / `revenue` / `ni_parent` /\n`debt_asset_ratio` / `gross_margin` / `eps_basic` 等共 150 个)。\n\n典型用法:画茅台 10 ROE 走势、对比 5 只白酒股 net_margin。\n",
7314
+ summary: "Get a time series of one financial metric.",
7315
+ description: "Returns the historical time series of a single financial metric for one A-share security, ordered by reporting period. `metric` is an indicator field name (e.g. `roe_simple`, `revenue`, `ni_parent`, `debt_asset_ratio`, `gross_margin`, `eps_basic`; ~150 metrics available). Use it to chart a multi-year trend for one company or to compare the same metric across several securities.",
7693
7316
  example: "echopai financials series --code SSE:600519 --metric roe_simple"
7694
7317
  },
7695
7318
  "index.daily-bars": {
7696
- summary: "Daily OHLC bars for one A-share index",
7697
- description: "指数日线 OHLC。覆盖 v2 表中的指数(上证综指 `SSE:000001` / 深证成指\n`SZSE:399001` / 沪深300 `SSE:000300` / 北证50 `BSE:899050` / 中证系列 `CSI:000922` 等)。\n与 `/v1/bars/daily` 同语义,但 index 没有 `turnover_rate` / `paused` 等股票特有字段。\n",
7319
+ summary: "List daily OHLC bars for one A-share index.",
7320
+ description: "Returns daily OHLC for one A-share index (e.g. 上证综指 `SSE:000001`, 深证成指 `SZSE:399001`, 沪深300 `SSE:000300`, 北证50 `BSE:899050`, CSI series such as `CSI:000922`). Same semantics as `/v1/bars/daily`, but indices have no stock-specific fields such as turnover rate or suspension flag.",
7698
7321
  example: "echopai index daily-bars --code SSE:000001 --from 2024-01-01 --to 2024-12-31"
7699
7322
  },
7700
7323
  "index.minute-bars": {
7701
- summary: "Minute OHLC bars for one A-share index",
7702
- description: "指数 1min OHLC。日期范围 7 天(与 `/v1/bars/minute-batch` 对齐)。\n返回 `bar_time` / `trade_date` / OHLC / volume / amount / pct_change。\n",
7324
+ summary: "List minute OHLC bars for one A-share index.",
7325
+ description: "Returns 1-minute OHLC for one A-share index over a date range of up to 7 days. Each bar includes bar time, trade date, OHLC, volume, amount, and percent change.",
7703
7326
  example: "echopai index minute-bars --code SSE:000001"
7704
7327
  },
7705
7328
  "index.snapshot": {
7706
- summary: "Real-time snapshot of all Sina-OK A-share indices (172 indices)",
7707
- description: "Returns the latest real-time snapshot for the 172 indices that Sina\nexposes a quote for (SSE / SZSE / BSE includes 北证50 `BSE:899050`\nand 专精特新 `899601.BJ`). Source is the Redis hash\n`stockpulse:index_snapshots:latest` which the Rust collector refreshes\nevery ~15 seconds during market hours; outside trading hours the last\nintraday snapshot is returned.\n\nUse `codes` to narrow to a subset (canonical format `SSE:000001` (exchange-prefix; legacy `000001.SH` is also accepted but discouraged)).\nOmit to receive all 172.\n\nNote: CSI-series indices (e.g. `CSI:000922`) are not available here —\nSina has no quote feed for them. Daily/minute history for those still\nlives behind `/api/internal/index/{daily-bars,minute-bars-range}`.\n\nRequires `quote:l1`, `quote:l2`, or `quote:delayed` scope.\n",
7329
+ summary: "Snapshot real-time quotes for A-share indices.",
7330
+ description: "Returns the latest real-time snapshot for the 172 A-share indices that have a live quote feed (SSE / SZSE / BSE, including 北证50 `BSE:899050`). The snapshot refreshes roughly every 15 seconds during market hours; outside trading hours the last intraday snapshot is returned. Use `codes` (canonical format, e.g. `SSE:000001`) to narrow to a subset, or omit it to receive all indices. Note: CSI-series indices (e.g. `CSI:000922`) have no live quote feed and are not returned here. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
7708
7331
  example: "echopai index snapshot --codes SSE:000001,SZSE:399001,BSE:899050"
7709
7332
  },
7710
- "industries.alerts": {
7711
- summary: "Currently active industry alerts",
7712
- description: "Currently active industry alerts (big_move / limit_up_cluster). Same rule set as concepts.",
7713
- example: "echopai industries alerts"
7714
- },
7715
- "industries.daily-bars": {
7716
- summary: "Industry index daily bars",
7717
- description: "Industry index daily OHLC (chain-linked equal-weight, base = 1000). Path key format `<sw_industry_code>:<sw_level>` (e.g. `401001:1`).",
7718
- example: "echopai industries daily-bars 401001:1"
7719
- },
7720
- "industries.list": {
7721
- summary: "List industries (申万 L1/L2) with hot/warm fallback",
7722
- description: "Industry index list joining CH `v_industry_meta` with Redis snapshot.\n申万一级 (level=1) ≈ 127 industries, 申万二级 (level=2) ≈ 348.\nChain-linked equal-weight algorithm identical to concepts (PLAN §5.4).\n\nRead order per industry (mirrors `/v1/concepts`, PR #442):\n 1. `industry_snapshots:latest` (hot, TTL 5min)\n 2. `industry_snapshots:last_close` (warm, TTL 7d)\n 3. CH `v_industry_daily_bars` argMax\n9:00–9:14 reset window suppresses hot+warm (loading state).\nEach item carries `_source` ∈ {`latest`, `last_close`, `ch_daily`, `miss`}.\n",
7723
- example: "echopai industries list"
7724
- },
7725
- "industries.show": {
7726
- summary: "Industry detail (meta + today members)",
7727
- description: "Path key format: `<sw_industry_code>:<sw_level>` (e.g. `401001:1`).",
7728
- example: "echopai industries show 401001:1"
7729
- },
7730
- "industries.snapshot": {
7731
- summary: "Bulk Redis snapshot of industries (hot/warm union)",
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",
7733
- example: "echopai industries snapshot"
7734
- },
7735
7333
  "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',
7334
+ summary: "Get LLM attribution for limit-up stocks.",
7335
+ description: "Returns LLM-generated attribution for each limit-up stock: the leading theme (`leading_concept`), concept tags, and a reason explaining why it hit the limit. Themes are inferred by an LLM from the latest research, institutional views, and news, so they may include brand-new themes; results refresh automatically at six points each trading day, with later runs refining earlier ones. This complements the limit-up pool, which tells you which stocks limited up; this endpoint tells you why and under which theme. Note: when `trade_date` is omitted, the latest attributed trading day is returned — before today's first run completes (around 09:47 Beijing time) this may be the previous trading day rather than empty, so pass an explicit `trade_date` for today and verify the `trade_date` / `generated_at` fields. Requires the `market:read` scope or any `quote:*` scope.",
7738
7336
  example: "echopai limit-up analysis"
7739
7337
  },
7740
7338
  "limit-up.history": {
7741
- summary: "A-share limit-up daily trend (last N days)",
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",
7339
+ summary: "Get the A-share limit-up daily trend.",
7340
+ description: "Returns the daily limit-up trend over the last N trading days: per-day limit-up count, highest consecutive-board height, and seal-break count. Sorted by trade date ascending (oldest first). Requires a `quote:*` scope.",
7743
7341
  example: "echopai limit-up history"
7744
7342
  },
7745
7343
  "limit-up.pool": {
7746
- summary: "A-share limit-up pool (per-stock detail) for a given trade date",
7747
- description: "当日(或指定 `trade_date`)涨停股池逐股明细:首/末次封板时间、最终涨幅、\n封单金额/封单量、炸板次数、连板数、N 日板内统计、一字板标记、当前 status\n(limit_up / broken)、板块分类。\n\n盘前回退规则(与所有行情类页面一致):\n - `< 9:00`:返回 max(trade_date) 的最新一日数据(昨日)\n - `9:00-9:14`:集合竞价前空窗期,items=[] / trade_date=null\n - `>= 9:15`:collector 已写入今日数据\n\n排序:连板数 desc 板内板数 desc 封单金额 desc。需要 `quote:*` scope。\n",
7344
+ summary: "List the A-share limit-up pool for a trade date.",
7345
+ description: "Returns per-stock detail for the limit-up pool on the current (or specified `trade_date`) trading day: first/last seal time, final percent change, seal amount and seal volume, number of times the seal broke, consecutive limit-up days, in-pool statistics over N days, one-line-board flag, current status (limit_up / broken), and sector classification. Sorted by consecutive limit-up days, then in-pool board count, then seal amount (all descending). Before 9:00 the previous trading day's data is returned; during 9:009:14 an empty result is returned; from 9:15 today's data is available. Requires a `quote:*` scope.",
7748
7346
  example: "echopai limit-up pool"
7749
7347
  },
7750
7348
  "limit-up.summary": {
7751
- summary: "A-share limit-up summary (counts + height ladder) for a given trade date",
7752
- description: "涨停股池聚合统计:涨停数、炸板数、跌停数、最高连板高度、连板梯队\n`ladder: [{ height, count }, ...]`。\n\n炸板数 / 跌停数 与情绪页同源(`market_breadth_intraday` 当日最新分钟行);\n连板梯队仅统计当前封板 status=limit_up 的个股。\n\n盘前回退规则与 `limit-up/pool` 一致。需要 `quote:*` scope。\n",
7349
+ summary: "Summarize A-share limit-up activity for a trade date.",
7350
+ description: "Returns aggregate limit-up statistics for the trade date: limit-up count, seal-break count, limit-down count, the highest consecutive-board height, and a height ladder (`ladder: [{ height, count }, ...]`). The height ladder counts only stocks currently sealed at limit-up. Pre-open fallback follows the same rules as the limit-up pool. Requires a `quote:*` scope.",
7753
7351
  example: "echopai limit-up summary"
7754
7352
  },
7755
7353
  "market.status": {
7756
- summary: "Current A-share market session state",
7757
- description: "Current A-share market session: pre-open / open / lunch / closed; current and next trading day; holiday flag. Requires `market:read` or any `quote:*` scope.",
7354
+ summary: "Get the current A-share market session state.",
7355
+ description: "Returns the current A-share trading session (pre-open / open / lunch / closed), the current and next trading day, and a holiday flag. Requires the `market:read` scope or any `quote:*` scope.",
7758
7356
  example: "echopai market status"
7759
7357
  },
7760
7358
  "news.feed": {
7761
- summary: "List recent news (alias of /v1/news/list)",
7762
- description: " `/v1/news/list` 行为一致,旧客户端兼容用。",
7359
+ summary: "List recent news.",
7360
+ description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility.",
7763
7361
  example: "echopai news feed"
7764
7362
  },
7765
7363
  "news.get": {
7766
- summary: "Fetch a single news item by id",
7767
- description: "返回新闻完整内容:title、content snippet、tagged securities。(采集端字段 source / source_id / source_url 对外不暴露,admin 路径 /v1/admin/news/{id} 可见。)",
7364
+ summary: "Get a single news item by id.",
7365
+ description: "Returns the full content of one news item: title, content snippet, and tagged securities.",
7768
7366
  example: "echopai news get VALUE"
7769
7367
  },
7770
7368
  "news.list": {
7771
- summary: "List news mentioning a specific security",
7772
- description: "List news mentioning a specific A-share security, ordered by published_at desc. Requires `news:read` scope.",
7369
+ summary: "List news mentioning one security.",
7370
+ description: "Returns news items mentioning a specific A-share security, ordered by publication date descending. Requires the `news:read` scope.",
7773
7371
  example: "echopai news list --security HK:00700"
7774
7372
  },
7775
7373
  "news.search": {
7776
- summary: "Full-text search recent news",
7777
- description: "Full-text search recent news / market briefs. Returns title, published_at, snippet, tagged securities. (Internal collector identifiers — `source`/`source_id`/`source_url` — are NOT exposed publicly; admin tools see them via /v1/admin/*.) Requires `news:read` scope. Note: news fields are user-generated; meta.untrusted_text_fields lists fields that need sanitization before passing to an LLM.",
7374
+ summary: "Full-text search recent news.",
7375
+ description: "Full-text search over recent news and market briefs. Returns title, publication date, snippet, and tagged securities. Requires the `news:read` scope. Note: news fields are user-generated; `meta.untrusted_text_fields` lists fields that should be sanitized before being passed to an LLM.",
7778
7376
  example: "echopai news search --query 光伏龙头"
7779
7377
  },
7780
7378
  "ownership.lockups": {
7781
- summary: "Scan upcoming share-lockup expirations across the whole market",
7782
- description: `全市场未来 days 天即将解禁扫描,按解禁日升序 + 占比降序。前瞻抛压预警,
7783
- digest 给不了(digest 是单 code 维度)。min_pct 过滤小额解禁,limit 控量。
7784
- `,
7379
+ summary: "Scan upcoming share-lockup expirations market-wide.",
7380
+ description: "Scans the whole market for share lockups expiring within the next `days` days, sorted by unlock date ascending then by unlocking ratio descending — a forward-looking selling-pressure screen across all securities. Use `min_pct` to filter out small unlocks and `limit` to cap the result size. T+1 data.",
7785
7381
  example: "echopai ownership lockups"
7786
7382
  },
7787
7383
  "ownership.show": {
7788
- summary: "Ownership events for one A-share (holder trades / repurchases / lockups)",
7789
- description: `单股股权事件三合一结构化字段,给研究 agent 一眼可读的「近 30 天增减持/回购
7790
- + 未来 30 天限售解禁」。源 Tushare(T+1),落 PG 专表幂等同步。
7791
-
7792
- - shareholder_transactions:增减持回看 txn_days 天,含净增减股数/比例 +
7793
- 增减笔数 + 明细(方向/股东/均价/区间)。
7794
- - share_repurchases:回购回看 repurchase_days 天,含在进行笔数 + 已执行
7795
- 累计金额/股数(仅计 in_progress/completed)+ 最新进度 + 明细。
7796
- - lockup_expirations:解禁前瞻 lockup_days 天(unlock_date ∈ [today,
7797
- today+N]),含合计解禁股数/占比 + 最近解禁日 + 明细。前瞻抛压预警。
7798
-
7799
- 枚举 slug(direction / holder_category / status / lockup_type)见
7800
- docs/OWNERSHIP_EVENTS.md。
7801
- `,
7384
+ summary: "Get ownership events for one security.",
7385
+ description: "Returns a structured three-in-one view of ownership events for one A-share security (canonical code, e.g. `SSE:600519`): recent shareholder transactions, recent share repurchases, and upcoming lockup expirations. T+1 data. Shareholder transactions look back `txn_days` days and include net change in shares/ratio, transaction count, and per-transaction detail (direction, holder, average price, date range). Repurchases look back `repurchase_days` days and include in-progress count, cumulative executed amount/shares, latest progress, and detail. Lockup expirations look ahead `lockup_days` days and include total unlocking shares/ratio, the nearest unlock date, and detail — useful as a forward-looking selling-pressure warning. Enum values (direction, holder category, status, lockup type) are documented in the response schema.",
7802
7386
  example: "echopai ownership show --code SSE:600519"
7803
7387
  },
7804
- "payment.plans": {
7805
- summary: "List subscription plans",
7806
- description: "返回订阅计划清单(plan_id + 价格 + 时长 + 描述)。",
7807
- example: "echopai payment plans"
7808
- },
7809
7388
  quote: {
7810
- summary: "Real-time quote for one or more A-share securities",
7811
- description: "Real-time quote for one or more A-share securities. Returns last price, volume, change %, bid/ask. Up to 200 codes per call. Requires `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
7389
+ summary: "Get real-time quotes for one or more securities.",
7390
+ description: "Returns real-time quotes for one or more A-share securities (canonical codes, e.g. `SSE:600519`): last price, volume, percent change, and bid/ask. Up to 200 codes per call. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
7812
7391
  example: "echopai quote --codes ['SSE:600519', 'SZSE:000001']"
7813
7392
  },
7814
7393
  "quote.scan": {
7815
- summary: "Full-market real-time quote scan (~5800 A-share securities)",
7816
- description: 'Dumps the entire Redis snapshot of A-share real-time quotes in a single\nround-trip. Use for "全市场扫描" / agent universe-of-interest discovery\nwhen you don\'t yet know which codes to query.\n\nFiltering:\n- `exchange=SSE|SZSE|BSE` narrows to one exchange (canonical_code prefix)\n- `app.allowed_securities` is ALWAYS applied; codes outside drop silently\n\nResponse size: ~3 MB unfiltered (~5800 items). CLI users should pair\nwith `--max-bytes` / `--fields` / `--query` from Phase 1.4.\n\n9:00–9:14 集合竞价时段返回空 items + `note` 字段说明。\n\nEach item also includes `main_net_in` (元, may be negative) — current-day\ncumulative main-capital net inflow merged from fundflow snapshot via\nhot/warm two-key pattern (PR #438):\n - `stockpulse:fundflow:latest:{code}` (hot, TTL 180s) for fresh\n - `stockpulse:fundflow:last_close:{code}` (warm, TTL 7d) for post-close\nCompanion field `main_net_in_stale` (boolean): false = fresh from hot,\ntrue = served from warm (post-close / weekend / collector down).\nTreated as a public field (same tier as `amount`); not scrubbed under\nany scope.\n\nRequires `quote:l1`, `quote:l2`, or `quote:delayed` scope.\n',
7394
+ summary: "Snapshot every A-share real-time quote in one call.",
7395
+ description: "Returns a real-time quote for every A-share in a single call — use it for full-market scans or to discover a universe of interest when you do not yet know which codes to query. Filter with `exchange=SSE|SZSE|BSE` to narrow to one exchange. The payload is large (~3 MB, ~5,800 instruments), so pair it with `--fields`, `--jq`, or `--max-bytes` to trim it. During the 9:00–9:14 pre-open auction an empty result is returned with an explanatory note. Each item also includes `main_net_in` (current-day cumulative net main-capital inflow in CNY, which may be negative) and a companion `main_net_in_stale` flag (true when served from the last-close fallback). Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
7817
7396
  example: "echopai quote scan"
7818
7397
  },
7819
7398
  "search.semantic": {
7820
- summary: "Hybrid semantic search across news + analyst views",
7821
- description: `语义 + 模糊泛化搜索。结合 entity 精确匹配(公司代码/分析师)、向量召回(pgvector
7822
- + DashScope embedding)、trigram 模糊、ILIKE 精确四路召回,RRF 融合后用 reranker
7823
- 精排,叠时间衰减 + views 加权(views 主源优先)。
7824
-
7825
- - 搜"锂电池"会带出新能源产业链(正极/负极/碳酸锂/宁德时代…)
7826
- - 搜"芯片"会带出存储芯片/洁净室/晶圆代工…
7827
- - mode=exact 兼容老 ILIKE 行为,仅在精确关键词命中时返回。
7828
- `,
7399
+ summary: "Semantic search across news and analyst views.",
7400
+ description: 'Hybrid semantic search across news and analyst views that generalizes beyond exact keywords. It combines exact entity matching (security codes, analysts), semantic (embedding-based) recall, and fuzzy matching, then fuses and re-ranks results with time decay and a weighting toward analyst views as the primary source. For example, searching "锂电池" surfaces the broader new-energy supply chain (cathode/anode materials, lithium carbonate, leading battery makers). Pass `mode=exact` for legacy exact-keyword behavior.',
7829
7401
  example: "echopai search semantic --q 锂电池"
7830
7402
  },
7831
- "securities.industry": {
7832
- summary: "TDX + 申万 industry classification for one A-share security",
7833
- description: `返回该股票的 TDX 自定义行业代码(T1001 食品饮料等)与申万行业三级代码
7834
- L1/L2/L3(X5001 食品饮料 / X500102 白酒 等)。
7835
-
7836
- 用法:
7837
- - 股票详情页"申万行业:白酒 / 食品饮料"标签
7838
- - 筛选器交叉过滤:行业 + ROE/营收增长率
7839
- - AI agent \`find_peers(code)\` 用申万二级行业找同业
7840
- `,
7841
- example: "echopai securities industry --code SSE:600519"
7842
- },
7843
7403
  "semantic.find": {
7844
- summary: "Find A-share securities by name / code / pinyin",
7845
- description: "Find A-share securities matching a name (Chinese), code (e.g. 600519), or pinyin initials (e.g. gzmt). Use this when an agent has a description and needs canonical codes.",
7404
+ summary: "Resolve A-share securities by name, code, or pinyin.",
7405
+ description: "Finds A-share securities matching a Chinese name, a code (e.g. `600519`), or pinyin initials (e.g. `gzmt`). Use it to turn a free-form description into canonical codes (e.g. `SSE:600519`).",
7846
7406
  example: "echopai semantic find --query 贵州茅台"
7847
7407
  },
7848
7408
  "sentiment.breadth": {
7849
- summary: "Intraday breadth time series",
7850
- description: "Intraday breadth time series for one trading day.\n`trade_date` 缺省 今日(盘前 9:00 前返回空 items);显式传 YYYY-MM-DD 可\n拉历史任意交易日(来源 ClickHouse `market_breadth_intraday`,剔除 13:00\n稀疏分钟,单日最多 241 行)。Requires `sentiment:read` scope.\n",
7409
+ summary: "Get the intraday market-breadth time series.",
7410
+ description: "Returns the intraday market-breadth time series for one trading day. Omit `trade_date` for today (returns empty before 9:00); pass `YYYY-MM-DD` for any historical trading day (sparse minutes around the lunch break are excluded; up to 241 rows per day). Requires the `sentiment:read` scope.",
7851
7411
  example: "echopai sentiment breadth"
7852
7412
  },
7853
7413
  "sentiment.overview": {
7854
- summary: "Aggregate market sentiment indicators",
7855
- description: "Aggregate sentiment snapshot for one trading day's latest minute:\nlimit-up/down counts, breadth, divergence index, MA / 52w breadth.\n`trade_date` 缺省 今日(Redis 实时,盘前 9:00 前返空结构);显式传\nYYYY-MM-DD 拉历史任意日(最后一分钟聚合行,来源 ClickHouse\n`market_breadth_intraday`)。Requires `sentiment:read` scope.\n",
7414
+ summary: "Get aggregate market sentiment indicators.",
7415
+ description: "Returns an aggregate sentiment snapshot for the latest minute of one trading day: limit-up/down counts, breadth, divergence index, and moving-average / 52-week breadth. Omit `trade_date` for today's real-time snapshot (returns an empty structure before 9:00); pass `YYYY-MM-DD` for any historical day (the day's final aggregated minute). Requires the `sentiment:read` scope.",
7856
7416
  example: "echopai sentiment overview --scope main_board"
7857
7417
  },
7858
7418
  "sentiment.pct-distribution": {
7859
- summary: "Distribution of intraday percent change across the universe",
7860
- description: "返回 A 股全市场涨跌幅分桶(≤-10% / -9% / ... / +10% / 涨停)的家数分布,用于市场宽度可视化。",
7419
+ summary: "Get the intraday percent-change distribution.",
7420
+ description: "Returns the distribution of intraday percent change across the full A-share universe, bucketed (≤-10% / -9% / ... / +10% / limit-up), for visualizing market breadth.",
7861
7421
  example: "echopai sentiment pct-distribution"
7862
7422
  },
7863
7423
  "sentiment.turnover": {
7864
- summary: "Intraday turnover time series",
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",
7424
+ summary: "Get the intraday turnover time series.",
7425
+ description: "Returns the intraday turnover time series across one or more trading days. Omit `trade_date` for today (returns empty before 9:00); pass `YYYY-MM-DD` for that day plus the preceding `days-1` days. The response includes headline fields `current_turnover`, `predicted_total`, and `delta_vs_yesterday`. Requires the `sentiment:read` scope.",
7866
7426
  example: "echopai sentiment turnover"
7867
7427
  },
7868
7428
  "stocks.hot": {
7869
- summary: "Today's hot-stock leaderboard",
7870
- description: "今日最热股票榜(来源 East-Money),按搜索 / 关注 / 评论综合分排序。",
7429
+ summary: "Get today's hot-stock leaderboard.",
7430
+ description: "Returns today's most-popular A-share stocks, ranked by a composite score of searches, follows, and comments.",
7871
7431
  example: "echopai stocks hot"
7872
7432
  },
7873
7433
  "stocks.social-hot": {
7874
- summary: "Cross-platform social-hot stocks",
7875
- description: "跨平台(微博 / 雪球 / Twitter)社交媒体提及聚合榜,按提及量 / 情感打分。",
7434
+ summary: "Get cross-platform social-hot stocks.",
7435
+ description: "Returns a leaderboard of A-share stocks aggregated by social-media mentions across multiple platforms, ranked by mention volume and sentiment score.",
7876
7436
  example: "echopai stocks social-hot"
7877
7437
  },
7878
7438
  "views.document": {
7879
- summary: "Fetch the full research-report text behind a view",
7880
- description: "取一条研报 view 背后的全文(mineru 解析的 parsed_md)。仅 `full_text_available=true` view 可用(gdrive 研报)。大文档默认 format=outline 只回标题目录 + total_chars;format=full offset/max_chars 切片正文并给 next_offset 游标翻页。需要 `views:read` scope。CLI 顶层 curated 别名:`report`(`echopai report <view_id>`)。",
7439
+ summary: "Get the full research-report text behind a view.",
7440
+ description: "Returns the full parsed text of the research report behind one view. Available only for views with `full_text_available=true`. Large documents default to `format=outline`, returning a heading-only table of contents plus total character count; use `format=full` with `offset` / `max_chars` to page through the body via the `next_offset` cursor. Requires the `views:read` scope.",
7881
7441
  example: "echopai views document VALUE"
7882
7442
  },
7883
7443
  "views.feed": {
7884
- summary: "List analyst views",
7885
- description: "分析师观点 / 卖方研报 / 长文本观点流。可按机构 / 分析师 / 证券 / hours 过滤。需要 `views:read` scope",
7444
+ summary: "List analyst views.",
7445
+ description: "Returns a stream of analyst views, sell-side research, and long-form opinion. Filter by institution, analyst, security, or recency (`hours`). Requires the `views:read` scope.",
7886
7446
  example: "echopai views feed"
7887
7447
  },
7888
7448
  "views.get": {
7889
- summary: "Fetch a single analyst view",
7890
- description: "返回单条 view 完整内容、附件图片、tagged securitiesAI 摘要。",
7449
+ summary: "Get a single analyst view.",
7450
+ description: "Returns the full content of one analyst view: body, attached images, tagged securities, and AI summary.",
7891
7451
  example: "echopai views get VALUE"
7892
7452
  },
7893
7453
  "views.recent": {
7894
- summary: "Recent broker / analyst views",
7895
- description: "List recent broker / analyst views (research notes, price targets, ratings). Filter by analyst, institution, or a specific security. Requires `views:read` scope.",
7454
+ summary: "List recent analyst views.",
7455
+ description: "Returns recent broker / analyst views (research notes, price targets, ratings). Filter by analyst, institution, or a specific security. Requires the `views:read` scope.",
7896
7456
  example: "echopai views recent --security HK:00700"
7897
7457
  }
7898
7458
  };
@@ -8395,15 +7955,14 @@ if (process.env._ECHOPAI_UPDATE_WORKER === "1") {
8395
7955
  try {
8396
7956
  const optedOut = process.env.CI || process.env.ECHOPAI_DISABLE_UPDATE_CHECK || !process.stderr.isTTY;
8397
7957
  if (!optedOut && !process.env._ECHOPAI_UPDATE_WORKER) {
8398
- let scriptPath;
8399
- try {
8400
- const p = fileURLToPath2(import.meta.url);
8401
- if (existsSync4(p))
8402
- scriptPath = p;
8403
- } catch {
8404
- scriptPath = undefined;
7958
+ let reexecArgs = [];
7959
+ if (!isStandaloneBinary()) {
7960
+ try {
7961
+ reexecArgs = [fileURLToPath(import.meta.url)];
7962
+ } catch {
7963
+ reexecArgs = [];
7964
+ }
8405
7965
  }
8406
- const reexecArgs = scriptPath ? [scriptPath] : [];
8407
7966
  const child = spawn(process.execPath, reexecArgs, {
8408
7967
  detached: true,
8409
7968
  stdio: "ignore",
@@ -8414,26 +7973,27 @@ try {
8414
7973
  }
8415
7974
  } catch {}
8416
7975
  var program = new Command31;
8417
- program.name("echopai").description(`EchoPai CLI v1 — partner-grade access to stock-market data, news, sentiment,
8418
- ` + `and analyst views.
7976
+ program.name("echopai").description(`Command-line access to the EchoPai market-data platform: quotes, news,
7977
+ ` + `analyst research, fundamentals, and sentiment.
8419
7978
 
8420
- ` + `AI-First: JSON envelope on stdout, JSON errors on stderr, three-state exit
8421
- ` + `codes (0 success / 1 user error / 2 service error). Set ECHOPAI_KEY env
8422
- ` + "var or run `echopai login`.").version(CLI_VERSION, "-V, --version").addOption(new Option23("--debug", "Print HTTP wire trace to stderr (Bearer redacted)")).addOption(new Option23("--raw", "Pass through raw response body (skip envelope wrap)")).addOption(new Option23("--jq <jmespath>", "Transform data with JMESPath expression (renamed from --query to avoid clash with news.search etc.)")).addOption(new Option23("--fields <a,b,c>", "Keep only listed top-level fields per item")).addOption(new Option23("--max-bytes <n>", "Truncate serialized envelope to N bytes (sets meta.truncated)")).addOption(new Option23("--yes", "Confirm a write op in non-TTY mode (required for sideEffect=write in agents / CI)")).addOption(new Option23("--dry-run", "Send X-Dry-Run:1 on write ops where the server advertises dry-run support")).addHelpText("after", `
8423
- Authentication: ECHOPAI_KEY=<eps_live_<lookup>_<secret>> echopai <cmd>
8424
- ` + `Raw mirror: echopai raw <noun> <verb> # all OpenAPI ops, e.g. raw news search
8425
- ` + `Curated verbs: echopai <verb> # task-level entry (Phase 3.6+)
8426
- ` + `Market session: echopai market status # pre-open/open/lunch/closed + next trading day
8427
- ` + `Market movers: echopai market movers --sort pct --top 20 # /market 行情榜(pct / pct_asc / speed / amount / turnover / total_mv)
8428
- ` + `Announcements: echopai announcements stock --code SSE:600519 # cninfo A-share disclosures
8429
- ` + `Concepts: echopai concepts alerts # 当前激活的概念异动(big_move / limit_up_cluster)
8430
- ` + `Limit-up: echopai limit-up summary # 涨停数 / 炸板数 / 连板梯队
8431
- ` + `Fundamentals: echopai financials quote-snapshot --code SSE:600519 # PE/PB/PS/换手率/股息率/量比 14-field snapshot
7979
+ ` + `Machine-readable by design — a JSON envelope on stdout, JSON errors on
7980
+ ` + `stderr, and three-state exit codes (0 success / 1 user error / 2 service
7981
+ ` + `error) built for AI agents and scripts. Authenticate with the
7982
+ ` + "ECHOPAI_KEY environment variable or `echopai login`.").version(CLI_VERSION, "-V, --version").addOption(new Option23("--debug", "Print HTTP wire trace to stderr (Bearer redacted)")).addOption(new Option23("--raw", "Pass through raw response body (skip envelope wrap)")).addOption(new Option23("--jq <jmespath>", "Filter or transform output with a JMESPath expression.")).addOption(new Option23("--fields <a,b,c>", "Keep only listed top-level fields per item")).addOption(new Option23("--max-bytes <n>", "Truncate serialized envelope to N bytes (sets meta.truncated)")).addOption(new Option23("--yes", "Confirm a write op in non-TTY mode (required for sideEffect=write in agents / CI)")).addOption(new Option23("--dry-run", "Send X-Dry-Run:1 on write ops where the server advertises dry-run support")).addHelpText("after", `
7983
+ Authentication: ECHOPAI_KEY=<eps_live_...> echopai <command>
7984
+ ` + `Curated verbs: echopai <verb> # task-level commands (recommended)
7985
+ ` + `Raw operations: echopai raw <noun> <verb> # low-level access to every API operation
7986
+ ` + `Market session: echopai market status # trading phase + next trading day
7987
+ ` + `Market movers: echopai market movers --sort pct --top 20 # ranked movers (pct / speed / amount / turnover / total_mv)
7988
+ ` + `Announcements: echopai announcements stock --code SSE:600519 # listed-company disclosures
7989
+ ` + `Concepts: echopai concepts alerts # active concept-board alerts
7990
+ ` + `Limit-up: echopai limit-up summary # limit-up / failed-board / consecutive-board ladder
7991
+ ` + `Fundamentals: echopai financials quote-snapshot --code SSE:600519 # 14-field valuation snapshot
8432
7992
  ` + `Capability: echopai whoami | echopai doctor
8433
- ` + `Schema dump: echopai schema export # AI agents pipe this for command surface
7993
+ ` + `Schema dump: echopai schema export # machine-readable command surface for agents
8434
7994
  ` + `Profile: echopai config profile add prod --base-url https://api.echopai.com
8435
- ` + `Self-update: echopai upgrade [--check] [--exec] # npm 装查 registry / 二进制装查 CDN;--exec 自动升级
8436
- ` + `MCP: echopai mcp install # 注册进 Claude Code(claude mcp add,幂等)
7995
+ ` + `Self-update: echopai upgrade [--check] [--exec]
7996
+ ` + `MCP: echopai mcp install # register with Claude Code
8437
7997
  `);
8438
7998
  var dispatch = async (op, args) => {
8439
7999
  const globals = program.opts();
@@ -8462,7 +8022,7 @@ function applyAiFirstHooks(cmd) {
8462
8022
  for (const sub of cmd.commands)
8463
8023
  applyAiFirstHooks(sub);
8464
8024
  }
8465
- var rawCmd = new Command31("raw").description("Raw 1:1 mirror of OpenAPI operations (escape hatch for power users / scripts)");
8025
+ var rawCmd = new Command31("raw").description("Low-level access to every API operation (advanced; bypasses the curated verbs).");
8466
8026
  buildCommandTree(rawCmd, dispatch);
8467
8027
  rawCmd.addCommand(buildRawCallCommand());
8468
8028
  program.addCommand(rawCmd);