echopai 2.7.0 → 2.8.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 +220 -78
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -574,6 +574,28 @@ var OPERATIONS = {
574
574
  ]
575
575
  }
576
576
  },
577
+ "capabilities.show": {
578
+ cliKey: "capabilities.show",
579
+ cliName: "capabilities show",
580
+ method: "GET",
581
+ path: "/v1/capabilities",
582
+ description: "Returns the current API spec version plus a machine-readable changelog of capability changes (new endpoints, new fields, behavior fixes), newest first. Check it at session start — or after an unexpected response shape — to discover capabilities added since your integration was written. Free and unauthenticated.",
583
+ summary: "Capability changelog and API version discovery.",
584
+ positional: [],
585
+ outputDefault: "json",
586
+ pagination: "none",
587
+ stream: false,
588
+ billable: false,
589
+ idempotencyRequired: false,
590
+ scopesAny: [],
591
+ sideEffect: "read",
592
+ dryRunSupported: false,
593
+ inputSchema: {
594
+ type: "object",
595
+ properties: {},
596
+ additionalProperties: false
597
+ }
598
+ },
577
599
  "concepts.alerts": {
578
600
  cliKey: "concepts.alerts",
579
601
  cliName: "concepts alerts",
@@ -1437,7 +1459,7 @@ var OPERATIONS = {
1437
1459
  include_broken: {
1438
1460
  type: "boolean",
1439
1461
  default: false,
1440
- description: "true 时连炸板(status=broken)也一起返回;默认只返回当前封板 status=limit_up。"
1462
+ description: "true 时连真炸板(盘中封板后跌出、当前未回封,status=broken)也一起返回;默认只返回当前封板 status=limit_up。"
1441
1463
  }
1442
1464
  },
1443
1465
  additionalProperties: false
@@ -1516,7 +1538,7 @@ var OPERATIONS = {
1516
1538
  cliName: "news feed",
1517
1539
  method: "GET",
1518
1540
  path: "/v1/news",
1519
- description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility.",
1541
+ description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility. Partner/agent callers receive `fields=compact` by default (id, title, ai_summary, tagged securities, first ~300 chars of content); pass `fields=full` for complete content.",
1520
1542
  summary: "List recent news.",
1521
1543
  positional: [],
1522
1544
  outputDefault: "json",
@@ -1529,7 +1551,16 @@ var OPERATIONS = {
1529
1551
  dryRunSupported: false,
1530
1552
  inputSchema: {
1531
1553
  type: "object",
1532
- properties: {},
1554
+ properties: {
1555
+ fields: {
1556
+ type: "string",
1557
+ enum: [
1558
+ "compact",
1559
+ "full"
1560
+ ],
1561
+ description: "Response shape: `compact` (the default for partner/agent callers) returns id, title, ai_summary, tagged securities, and the first ~300 chars of content with a `content_truncated` flag; `full` returns complete content."
1562
+ }
1563
+ },
1533
1564
  additionalProperties: false
1534
1565
  }
1535
1566
  },
@@ -1621,6 +1652,14 @@ var OPERATIONS = {
1621
1652
  minimum: 0,
1622
1653
  default: 0,
1623
1654
  description: "Pagination offset (page through long history ranges)."
1655
+ },
1656
+ fields: {
1657
+ type: "string",
1658
+ enum: [
1659
+ "compact",
1660
+ "full"
1661
+ ],
1662
+ description: "Response shape: `compact` (the default for partner/agent callers) returns id, title/ai_summary, attribution, tagged securities, and the first ~300 chars of content with a `content_truncated` flag; `full` returns complete content. Prefer compact for scanning; fetch full text per item only when needed."
1624
1663
  }
1625
1664
  },
1626
1665
  additionalProperties: false,
@@ -1686,6 +1725,14 @@ var OPERATIONS = {
1686
1725
  minimum: 0,
1687
1726
  default: 0,
1688
1727
  description: "Pagination offset (page through long history ranges)."
1728
+ },
1729
+ fields: {
1730
+ type: "string",
1731
+ enum: [
1732
+ "compact",
1733
+ "full"
1734
+ ],
1735
+ description: "Response shape: `compact` (the default for partner/agent callers) returns id, title/ai_summary, attribution, tagged securities, and the first ~300 chars of content with a `content_truncated` flag; `full` returns complete content. Prefer compact for scanning; fetch full text per item only when needed."
1689
1736
  }
1690
1737
  },
1691
1738
  additionalProperties: false,
@@ -1800,7 +1847,7 @@ var OPERATIONS = {
1800
1847
  cliName: "quote",
1801
1848
  method: "GET",
1802
1849
  path: "/v1/quote/realtime",
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.",
1850
+ 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. The response carries a `session` label (`pre_open` / `auction` / `open` / `break` / `closed`) and, outside continuous trading, an explanatory `note`: during 9:00–9:14 pre-open the previous session's snapshot is returned; during the 9:15–9:29 call auction, prices are indicative auction match prices. Rows whose feed has stopped updating (suspension / feed drop) keep their last snapshot and are flagged `stale: true` with `stale_since`. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
1804
1851
  summary: "Get real-time quotes for one or more securities.",
1805
1852
  positional: [],
1806
1853
  outputDefault: "json",
@@ -1851,7 +1898,7 @@ var OPERATIONS = {
1851
1898
  cliName: "quote scan",
1852
1899
  method: "GET",
1853
1900
  path: "/v1/quote/scan",
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.",
1901
+ 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. The response carries a `session` label (`pre_open` / `auction` / `open` / `break` / `closed`) plus an explanatory `note` outside continuous trading: during 9:00–9:14 pre-open the previous session's snapshot is returned; during the 9:15–9:29 call auction, prices are indicative auction match prices. Instruments whose feed has stopped updating (suspension / feed drop) are excluded from the default scan; pass `include=all` to keep them, flagged with `stale: true` and `stale_since`. 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
1902
  summary: "Snapshot every A-share real-time quote in one call.",
1856
1903
  positional: [],
1857
1904
  outputDefault: "json",
@@ -1880,6 +1927,15 @@ var OPERATIONS = {
1880
1927
  "bse"
1881
1928
  ],
1882
1929
  description: "Filter by exchange: SSE / SZSE / BSE (case-insensitive). Omit for all."
1930
+ },
1931
+ include: {
1932
+ type: "string",
1933
+ enum: [
1934
+ "active",
1935
+ "all"
1936
+ ],
1937
+ default: "active",
1938
+ description: "`active` (default) excludes ST, delisted, and stale (feed-dropped / suspended) instruments; `all` returns the full set with stale rows flagged."
1883
1939
  }
1884
1940
  },
1885
1941
  additionalProperties: false
@@ -2259,7 +2315,7 @@ var OPERATIONS = {
2259
2315
  cliName: "views feed",
2260
2316
  method: "GET",
2261
2317
  path: "/v1/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.",
2318
+ description: "Returns a stream of analyst views, sell-side research, and long-form opinion. Filter by institution, analyst, security, or recency (`hours`). Partner/agent callers receive `fields=compact` by default (id, ai_summary, attribution, tagged securities, first ~300 chars of content); pass `fields=full` for complete content. Requires the `views:read` scope.",
2263
2319
  summary: "List analyst views.",
2264
2320
  positional: [],
2265
2321
  outputDefault: "json",
@@ -2272,7 +2328,16 @@ var OPERATIONS = {
2272
2328
  dryRunSupported: false,
2273
2329
  inputSchema: {
2274
2330
  type: "object",
2275
- properties: {},
2331
+ properties: {
2332
+ fields: {
2333
+ type: "string",
2334
+ enum: [
2335
+ "compact",
2336
+ "full"
2337
+ ],
2338
+ description: "Response shape: `compact` (the default for partner/agent callers) returns id, ai_summary, attribution, tagged securities, and the first ~300 chars of content with a `content_truncated` flag; `full` returns complete content."
2339
+ }
2340
+ },
2276
2341
  additionalProperties: false
2277
2342
  }
2278
2343
  },
@@ -2372,6 +2437,14 @@ var OPERATIONS = {
2372
2437
  minimum: 0,
2373
2438
  default: 0,
2374
2439
  description: "Pagination offset (page through long history ranges)."
2440
+ },
2441
+ fields: {
2442
+ type: "string",
2443
+ enum: [
2444
+ "compact",
2445
+ "full"
2446
+ ],
2447
+ description: "Response shape: `compact` (the default for partner/agent callers) returns id, title/ai_summary, attribution, tagged securities, and the first ~300 chars of content with a `content_truncated` flag; `full` returns complete content. Prefer compact for scanning; fetch full text per item only when needed."
2375
2448
  }
2376
2449
  },
2377
2450
  additionalProperties: false
@@ -2490,6 +2563,14 @@ function buildCommandTree(program, dispatch) {
2490
2563
  attachOperation(cmd, OPERATIONS["bars.minute-batch"], dispatch);
2491
2564
  }
2492
2565
  }
2566
+ {
2567
+ const noun = program.command("capabilities");
2568
+ noun.description("capabilities commands");
2569
+ {
2570
+ const cmd = noun.command("show");
2571
+ attachOperation(cmd, OPERATIONS["capabilities.show"], dispatch);
2572
+ }
2573
+ }
2493
2574
  {
2494
2575
  const noun = program.command("concepts");
2495
2576
  noun.description("Thematic concept boards: constituents, quotes, and alerts.");
@@ -3619,7 +3700,7 @@ import os2 from "node:os";
3619
3700
  import path2 from "node:path";
3620
3701
 
3621
3702
  // src/version.ts
3622
- var CLI_VERSION = "2.7.0";
3703
+ var CLI_VERSION = "2.8.0";
3623
3704
 
3624
3705
  // src/runtime/update_check.ts
3625
3706
  var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
@@ -3752,9 +3833,10 @@ async function invoke(op, args, ctx) {
3752
3833
  }
3753
3834
  throw e;
3754
3835
  }
3836
+ const opOwnedFlags = new Set(Object.keys(op.inputSchema.properties).filter((k) => SYSTEM_FLAGS.has(k)));
3755
3837
  const apiParams = {};
3756
3838
  for (const [k, v] of Object.entries(args)) {
3757
- if (SYSTEM_FLAGS.has(k))
3839
+ if (SYSTEM_FLAGS.has(k) && !opOwnedFlags.has(k))
3758
3840
  continue;
3759
3841
  if (v === undefined || v === "")
3760
3842
  continue;
@@ -3921,9 +4003,10 @@ async function invoke(op, args, ctx) {
3921
4003
  let envelope = rawEnvelope;
3922
4004
  try {
3923
4005
  const jq = typeof args.jq === "string" ? args.jq : undefined;
4006
+ const fieldsProjection = opOwnedFlags.has("fields") ? undefined : parseFieldsFlag(args.fields);
3924
4007
  envelope = applyFilters(rawEnvelope, {
3925
4008
  ...jq ? { query: jq } : {},
3926
- ...parseFieldsFlag(args.fields) ? { fields: parseFieldsFlag(args.fields) } : {},
4009
+ ...fieldsProjection ? { fields: fieldsProjection } : {},
3927
4010
  ...parseMaxBytesFlag(args.max_bytes) ? { maxBytes: parseMaxBytesFlag(args.max_bytes) } : {}
3928
4011
  });
3929
4012
  } catch (e) {
@@ -4533,6 +4616,19 @@ function buildBarsBatchCommand() {
4533
4616
  });
4534
4617
  return cmd;
4535
4618
  }
4619
+ // src/verbs/capabilities.ts
4620
+ var capabilitiesSpec = {
4621
+ name: "capabilities",
4622
+ description: "API capability changelog and spec-version discovery. Returns the current spec version plus a newest-first changelog of capability changes (new endpoints, new fields, behavior fixes). Call once at session start — or when a response shape surprises you — to learn what changed since your integration was written. Free, unauthenticated, non-billable.",
4623
+ inputSchema: {},
4624
+ handler: async (_args, ctx) => {
4625
+ const op = OPERATIONS["capabilities.show"];
4626
+ if (!op)
4627
+ throw new Error("capabilities.show op missing from codegen");
4628
+ return callOp(op, {}, ctx);
4629
+ },
4630
+ backingOps: ["capabilities.show"]
4631
+ };
4536
4632
  // src/verbs/chart.ts
4537
4633
  import { Command as Command5, Option as Option4 } from "commander";
4538
4634
  import { z as z3 } from "zod";
@@ -5478,9 +5574,29 @@ function buildHotCommand() {
5478
5574
  });
5479
5575
  return cmd;
5480
5576
  }
5577
+ // src/verbs/indices.ts
5578
+ import { z as z8 } from "zod";
5579
+ var indexSnapshotSpec = {
5580
+ name: "index_snapshot",
5581
+ description: "Real-time snapshot for the 172 A-share indices with a live quote feed (e.g. 上证指数 SSE:000001, 深证成指 SZSE:399001, 创业板指 SZSE:399006, 科创50 SSE:000688, 北证50 BSE:899050): price, pct_change, amount, speed_3min per index, refreshed ~15s during market hours (last intraday snapshot outside trading hours). Pass `codes` to narrow to a subset; omit for all 172. Use this for 'how is the broad market doing right now' — `quote` covers individual stocks only. CSI-series indices (CSI:*) have no live feed and are not returned.",
5582
+ inputSchema: {
5583
+ codes: z8.array(z8.string().regex(/^(SSE|SZSE|BSE):[0-9]{6}$/)).min(1).max(172).optional().describe("Canonical index codes to narrow the snapshot (e.g. ['SSE:000001', 'BSE:899050']); omit for all 172")
5584
+ },
5585
+ handler: async (args, ctx) => {
5586
+ const op = OPERATIONS["index.snapshot"];
5587
+ if (!op)
5588
+ throw new Error("index.snapshot op missing from codegen");
5589
+ const callArgs = {};
5590
+ if (Array.isArray(args.codes) && args.codes.length > 0) {
5591
+ callArgs.codes = args.codes.join(",");
5592
+ }
5593
+ return callOp(op, callArgs, ctx);
5594
+ },
5595
+ backingOps: ["index.snapshot"]
5596
+ };
5481
5597
  // src/verbs/limit_up.ts
5482
5598
  import { Command as Command11, Option as Option9 } from "commander";
5483
- import { z as z8 } from "zod";
5599
+ import { z as z9 } from "zod";
5484
5600
  var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
5485
5601
  var INCLUDE_VALUES = ["active", "all"];
5486
5602
  function clampInt5(raw, min, max, fallback) {
@@ -5497,9 +5613,9 @@ var limitUpPoolSpec = {
5497
5613
  name: "limit_up_pool",
5498
5614
  description: "A-share limit-up pool for a given trade date — per-stock detail: first/last seal time, final pct, seal amount/volume, broken count, consecutive_days (连板数), board_count_within (N-day 板数), one-word-board flag, current status (limit_up / broken), board classification. Sort: consecutive_days desc → board_count_within desc → seal_amount desc. Pre-open fallback applies. Use when an agent needs the concrete list of '今天涨停了哪些票'.",
5499
5615
  inputSchema: {
5500
- trade_date: z8.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for latest (pre-open fallback)"),
5501
- include: z8.enum(INCLUDE_VALUES).default("active").describe("active = 排除 ST/退市(默认); all = 全部"),
5502
- include_broken: z8.boolean().default(false).describe("true 时连炸板(status=broken)也一起返回;默认只返回当前封板 status=limit_up")
5616
+ trade_date: z9.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for latest (pre-open fallback)"),
5617
+ include: z9.enum(INCLUDE_VALUES).default("active").describe("active = 排除 ST/退市(默认); all = 全部"),
5618
+ include_broken: z9.boolean().default(false).describe("true 时连炸板(status=broken)也一起返回;默认只返回当前封板 status=limit_up")
5503
5619
  },
5504
5620
  handler: async (args, ctx) => {
5505
5621
  const op = OPERATIONS["limit-up.pool"];
@@ -5519,8 +5635,8 @@ var limitUpSummarySpec = {
5519
5635
  name: "limit_up_summary",
5520
5636
  description: "Limit-up summary for a given trade date: 涨停数 (limit_up_count) / 炸板数 (broken_count) / 跌停数 (limit_down_count) / 最高连板 (max_height) / 连板梯队 ladder[{height, count}]. 炸板/跌停 与情绪页同源 (market_breadth_intraday 当日最新分钟行). Pre-open fallback applies.",
5521
5637
  inputSchema: {
5522
- trade_date: z8.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for latest"),
5523
- include: z8.enum(INCLUDE_VALUES).default("active").describe("active = all_a_ex_st 口径(默认); all = all_a 口径")
5638
+ trade_date: z9.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for latest"),
5639
+ include: z9.enum(INCLUDE_VALUES).default("active").describe("active = all_a_ex_st 口径(默认); all = all_a 口径")
5524
5640
  },
5525
5641
  handler: async (args, ctx) => {
5526
5642
  const op = OPERATIONS["limit-up.summary"];
@@ -5537,7 +5653,7 @@ var limitUpHistorySpec = {
5537
5653
  name: "limit_up_history",
5538
5654
  description: "Daily limit-up trend over the last N trading days: { trade_date, limit_up_count, broken_count, max_height }. 涨停数/最高连板 from market_limit_up_pool; 炸板数 from market_breadth_intraday (all_a_ex_st). Sorted by trade_date asc (oldest first).",
5539
5655
  inputSchema: {
5540
- days: z8.number().int().min(1).max(250).default(30).describe("Lookback window in trading days (1-250, default 30)")
5656
+ days: z9.number().int().min(1).max(250).default(30).describe("Lookback window in trading days (1-250, default 30)")
5541
5657
  },
5542
5658
  handler: async (args, ctx) => {
5543
5659
  const op = OPERATIONS["limit-up.history"];
@@ -5551,7 +5667,7 @@ var limitUpAnalysisSpec = {
5551
5667
  name: "limit_up_analysis",
5552
5668
  description: "Per-stock limit-up LLM attribution for a given trade date: leading_concept (主导题材) + concept_tags (相关标签) + reason (涨停理由). Themes are judged by an LLM from the latest research / views / news (NOT from the concept graph, so brand-new themes are covered) and refreshed across 6 Beijing slots daily. Complements limit-up pool (which says '哪些票涨停'); this says '为什么涨停/属什么题材'. STALE-DATA WARNING: omitting trade_date returns the latest *attributed* trading day, NOT necessarily today — before today's first slot lands (~09:47 Beijing) it falls back to the PREVIOUS day's attribution. For today's data pass trade_date explicitly and verify the returned trade_date / generated_at fields. An explicit trade_date with no attribution yet returns an empty analyses list.",
5553
5669
  inputSchema: {
5554
- trade_date: z8.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for the latest *attributed* day (falls back to the previous day until today's first slot lands ~09:47 Beijing — pass today explicitly for today's data)")
5670
+ trade_date: z9.string().regex(DATE_RE5).optional().describe("ISO date YYYY-MM-DD; omit for the latest *attributed* day (falls back to the previous day until today's first slot lands ~09:47 Beijing — pass today explicitly for today's data)")
5555
5671
  },
5556
5672
  handler: async (args, ctx) => {
5557
5673
  const op = OPERATIONS["limit-up.analysis"];
@@ -5617,7 +5733,7 @@ function buildLimitUpCommand() {
5617
5733
  }
5618
5734
  // src/verbs/lookup.ts
5619
5735
  import { Command as Command12, Option as Option10 } from "commander";
5620
- import { z as z9 } from "zod";
5736
+ import { z as z10 } from "zod";
5621
5737
  function mapLookupResponse(raw) {
5622
5738
  if (!Array.isArray(raw))
5623
5739
  return [];
@@ -5641,8 +5757,8 @@ var lookupSpec = {
5641
5757
  name: "lookup",
5642
5758
  description: "Resolve a Chinese name / A-share code / pinyin initials to canonical codes (e.g. SSE:600519). Use this first whenever the agent has a description but needs a canonical_code for downstream calls. AH dual-listing: when a company is listed on both A-share and HK (e.g. 工商银行 SSE:601398 / HK:01398), prefer the A-share canonical for all downstream analysis unless the user explicitly asks for the HK side.",
5643
5759
  inputSchema: {
5644
- text: z9.string().min(1).max(50).describe("Search text: Chinese name (贵州茅台), code (600519), or pinyin (gzmt)"),
5645
- limit: z9.number().int().min(1).max(30).default(10).describe("Max matches (1-30)")
5760
+ text: z10.string().min(1).max(50).describe("Search text: Chinese name (贵州茅台), code (600519), or pinyin (gzmt)"),
5761
+ limit: z10.number().int().min(1).max(30).default(10).describe("Max matches (1-30)")
5646
5762
  },
5647
5763
  handler: async (args, ctx) => {
5648
5764
  const op = OPERATIONS["semantic.find"];
@@ -5676,7 +5792,7 @@ function buildLookupCommand() {
5676
5792
  }
5677
5793
  // src/verbs/market.ts
5678
5794
  import { Command as Command13, Option as Option11 } from "commander";
5679
- import { z as z10 } from "zod";
5795
+ import { z as z11 } from "zod";
5680
5796
  var marketStatusSpec = {
5681
5797
  name: "market_status",
5682
5798
  description: "Current A-share market session: pre-open / open / lunch / closed; current and next trading day; holiday flag. Cheap and non-billable — call it before any quote / bars fetch when the agent needs to decide pre-market fallback vs intraday vs after-close behavior.",
@@ -5729,10 +5845,10 @@ var marketMoversSpec = {
5729
5845
  name: "market_movers",
5730
5846
  description: "A-share market movers — top N from the full real-time snapshot, sorted by chosen field. Sort keys: `pct` (涨幅榜) / `pct_asc` (跌幅榜) / `speed` (3-min 涨速 speed_3min) / `amount` (成交额) / `turnover` (换手率 turnover_rate) / `total_mv` (总市值). Backed by `quote.scan` so 9:00-9:14 集合竞价时段会返空 (with `note`). Defaults: sort=pct, top=20, include=active (排除 ST / 退市). Use this for /market-style 'who's running today' instead of pulling the full 3 MB scan and sorting client-side.",
5731
5847
  inputSchema: {
5732
- sort: z10.enum(MOVERS_SORT_VALUES).default("pct").describe("Sort field: pct / pct_asc / speed / amount / turnover / total_mv"),
5733
- top: z10.number().int().min(1).max(500).default(20).describe("Top N items (1-500, default 20)"),
5734
- exchange: z10.enum(EXCHANGE_VALUES).optional().describe("Narrow to one exchange: SSE / SZSE / BSE"),
5735
- include: z10.enum(INCLUDE_VALUES2).default("active").describe("active = 排除 ST/退市(默认); all = 全集(含 ST + 退市 + 未知 status)")
5848
+ sort: z11.enum(MOVERS_SORT_VALUES).default("pct").describe("Sort field: pct / pct_asc / speed / amount / turnover / total_mv"),
5849
+ top: z11.number().int().min(1).max(500).default(20).describe("Top N items (1-500, default 20)"),
5850
+ exchange: z11.enum(EXCHANGE_VALUES).optional().describe("Narrow to one exchange: SSE / SZSE / BSE"),
5851
+ include: z11.enum(INCLUDE_VALUES2).default("active").describe("active = 排除 ST/退市(默认); all = 全集(含 ST + 退市 + 未知 status)")
5736
5852
  },
5737
5853
  handler: async (args, ctx) => {
5738
5854
  const op = OPERATIONS["quote.scan"];
@@ -5816,18 +5932,19 @@ function buildMarketCommand() {
5816
5932
  }
5817
5933
  // src/verbs/news.ts
5818
5934
  import { Command as Command14, Option as Option12 } from "commander";
5819
- import { z as z11 } from "zod";
5935
+ import { z as z12 } from "zod";
5820
5936
  var newsSpec = {
5821
5937
  name: "news",
5822
- description: "SUPPLEMENTARY news / market briefs (short time-horizon event stream). Use ONLY to fill gaps not covered by `views`; prefer `views` as the primary research source. Three modes: `code` (canonical, works across A-share | HK | US — e.g. SSE:600519, HK:00700 腾讯, US:BABA) → news mentioning that security; `query` (free text) → full-text search; neither → recent time-window feed. AH dual-listing: when filtering by `code` for an A+H listed company, pass the A-share canonical (e.g. SSE:601398) for consolidated coverage; HK side only if the user explicitly asks. Note: `query` is matched against news text, so passing a canonical_code as `query` will not work — use `code` for security filtering.",
5938
+ description: "SUPPLEMENTARY news / market briefs (short time-horizon event stream). Use ONLY to fill gaps not covered by `views`; prefer `views` as the primary research source. Returns `fields=compact` by default (title + ai_summary + first ~300 chars of content, `content_truncated` flag) — typically 20-50x smaller than full; pass fields='full' only when you need complete article text. Three modes: `code` (canonical, works across A-share | HK | US — e.g. SSE:600519, HK:00700 腾讯, US:BABA) → news mentioning that security; `query` (free text) → full-text search; neither → recent time-window feed. AH dual-listing: when filtering by `code` for an A+H listed company, pass the A-share canonical (e.g. SSE:601398) for consolidated coverage; HK side only if the user explicitly asks. Note: `query` is matched against news text, so passing a canonical_code as `query` will not work — use `code` for security filtering.",
5823
5939
  inputSchema: {
5824
- code: z11.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700). Takes precedence over `query`."),
5825
- query: z11.string().optional().describe("Free-text query (Chinese or English) matched against news content; omit for time-window feed. Do NOT pass canonical_code here — use `code` instead."),
5826
- hours: z11.number().int().min(1).max(168).default(24).describe("Rolling-feed lookback in hours (default 24 — much narrower than views). For longer history use from/to with code or query."),
5827
- from: z11.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs code or query; Pro-only up to 365 days back."),
5828
- to: z11.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
5829
- offset: z11.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
5830
- limit: z11.number().int().min(1).max(100).default(20).describe("Max items per page")
5940
+ code: z12.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700). Takes precedence over `query`."),
5941
+ query: z12.string().optional().describe("Free-text query (Chinese or English) matched against news content; omit for time-window feed. Do NOT pass canonical_code here — use `code` instead."),
5942
+ hours: z12.number().int().min(1).max(168).default(24).describe("Rolling-feed lookback in hours (default 24 — much narrower than views). For longer history use from/to with code or query."),
5943
+ from: z12.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs code or query; Pro-only up to 365 days back."),
5944
+ to: z12.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
5945
+ offset: z12.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
5946
+ limit: z12.number().int().min(1).max(100).default(20).describe("Max items per page"),
5947
+ fields: z12.enum(["compact", "full"]).optional().describe("Response shape: compact (default — title + ai_summary + first ~300 chars) or full (complete content)")
5831
5948
  },
5832
5949
  handler: async (args, ctx) => {
5833
5950
  const { opKey, callArgs } = buildNewsCall(args);
@@ -5840,11 +5957,14 @@ var newsSpec = {
5840
5957
  };
5841
5958
  function buildNewsCall(args) {
5842
5959
  const hasRange = typeof args.from === "string" && args.from.length > 0;
5960
+ const fields = args.fields === "compact" || args.fields === "full" ? args.fields : undefined;
5843
5961
  const withWindow = (a) => {
5844
5962
  if (typeof args.limit === "number")
5845
5963
  a.limit = args.limit;
5846
5964
  if (typeof args.offset === "number")
5847
5965
  a.offset = args.offset;
5966
+ if (fields)
5967
+ a.fields = fields;
5848
5968
  if (hasRange) {
5849
5969
  a.from = args.from;
5850
5970
  if (typeof args.to === "string" && args.to.length > 0)
@@ -5861,7 +5981,7 @@ function buildNewsCall(args) {
5861
5981
  if (hasRange) {
5862
5982
  throw new Error("range history (from/to) requires --code or --query; the bare feed has no narrowing filter");
5863
5983
  }
5864
- return { opKey: "news.feed", callArgs: {} };
5984
+ return { opKey: "news.feed", callArgs: fields ? { fields } : {} };
5865
5985
  }
5866
5986
  function clamp3(raw, min, max, fallback) {
5867
5987
  const n = Math.floor(Number(raw));
@@ -5882,6 +6002,10 @@ function buildNewsCommand() {
5882
6002
  cmd.addOption(new Option12("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
5883
6003
  cmd.addOption(new Option12("--offset <n>", "Pagination offset for long history").default("0"));
5884
6004
  cmd.addOption(new Option12("--limit <n>", "Max items").default("20"));
6005
+ cmd.addOption(new Option12("--fields <mode>", "Response shape: compact (default) or full").choices([
6006
+ "compact",
6007
+ "full"
6008
+ ]));
5885
6009
  cmd.action(async (opts) => {
5886
6010
  const args = {
5887
6011
  hours: clamp3(opts.hours, 1, 168, 24),
@@ -5896,19 +6020,21 @@ function buildNewsCommand() {
5896
6020
  args.from = opts.from;
5897
6021
  if (opts.to)
5898
6022
  args.to = opts.to;
6023
+ if (opts.fields)
6024
+ args.fields = opts.fields;
5899
6025
  await executeVerb(async (ctx) => newsSpec.handler(args, ctx));
5900
6026
  });
5901
6027
  return cmd;
5902
6028
  }
5903
6029
  // src/verbs/quote.ts
5904
6030
  import { Command as Command15, Option as Option13 } from "commander";
5905
- import { z as z12 } from "zod";
6031
+ import { z as z13 } from "zod";
5906
6032
  var quoteSpec = {
5907
6033
  name: "quote",
5908
6034
  description: "Real-time quote for 1-200 A-share securities (last price, volume, change %, bid/ask). For >200 codes use `scan` instead. AH dual-listing: HK codes are not supported here; for an A+H listed company use the A-share canonical (e.g. SSE:601398 not HK:01398).",
5909
6035
  inputSchema: {
5910
- codes: z12.array(z12.string().regex(/^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/)).min(1).max(200).describe("Array of canonical codes (e.g. ['SSE:600519', 'SZSE:000001'])"),
5911
- include_l2: z12.boolean().optional().describe("Include L2 5-level order book (requires quote:l2 scope)")
6036
+ codes: z13.array(z13.string().regex(/^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/)).min(1).max(200).describe("Array of canonical codes (e.g. ['SSE:600519', 'SZSE:000001'])"),
6037
+ include_l2: z13.boolean().optional().describe("Include L2 5-level order book (requires quote:l2 scope)")
5912
6038
  },
5913
6039
  handler: async (args, ctx) => {
5914
6040
  const op = OPERATIONS["quote"];
@@ -5939,12 +6065,12 @@ function buildQuoteCommand() {
5939
6065
  }
5940
6066
  // src/verbs/scan.ts
5941
6067
  import { Command as Command16, Option as Option14 } from "commander";
5942
- import { z as z13 } from "zod";
6068
+ import { z as z14 } from "zod";
5943
6069
  var scanSpec = {
5944
6070
  name: "scan",
5945
6071
  description: "Full-market real-time quote scan (~5800 A-share securities in one round-trip). Use for universe discovery; agents should slim output with field/byte limits in their tool host or post-processing.",
5946
6072
  inputSchema: {
5947
- exchange: z13.enum(["SSE", "SZSE", "BSE"]).optional().describe("Filter by exchange (omit for all)")
6073
+ exchange: z14.enum(["SSE", "SZSE", "BSE"]).optional().describe("Filter by exchange (omit for all)")
5948
6074
  },
5949
6075
  handler: async (args, ctx) => {
5950
6076
  const op = OPERATIONS["quote.scan"];
@@ -5973,16 +6099,16 @@ function buildScanCommand() {
5973
6099
  }
5974
6100
  // src/verbs/search.ts
5975
6101
  import { Command as Command17, Option as Option15 } from "commander";
5976
- import { z as z14 } from "zod";
6102
+ import { z as z15 } from "zod";
5977
6103
  var searchSpec = {
5978
6104
  name: "search",
5979
6105
  description: "Structured-first hybrid search across analyst views + news (views weighted higher per feedback_views_over_news). Three lanes — entity (company codes / names, analyst names), concept-graph hub (theme alias → concept → constituents & research), and ParadeDB BM25 full-text (jieba) — fused with RRF. Deterministic & explainable: no vector kNN, no LLM rerank. Best for theme/concept queries: 'CPO' brings out 光模块/光通信/光芯片; '减肥药' brings out 创新药; '智驾' brings out 智能驾驶. Use `news` or `views` instead if you need strict time-window listing.",
5980
6106
  inputSchema: {
5981
- q: z14.string().min(1).max(200).describe("Free-text query (Chinese or English)"),
5982
- type: z14.enum(["news", "views", "all"]).default("all").describe("Search scope (views weighted higher in 'all')"),
5983
- mode: z14.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid = entity + concept-graph + BM25 fused; exact = BM25 keyword only"),
5984
- hours: z14.number().int().min(1).max(720).optional().describe("Lookback window in hours; omit for no time limit"),
5985
- limit: z14.number().int().min(1).max(50).default(20).describe("Max items returned")
6107
+ q: z15.string().min(1).max(200).describe("Free-text query (Chinese or English)"),
6108
+ type: z15.enum(["news", "views", "all"]).default("all").describe("Search scope (views weighted higher in 'all')"),
6109
+ mode: z15.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid = entity + concept-graph + BM25 fused; exact = BM25 keyword only"),
6110
+ hours: z15.number().int().min(1).max(720).optional().describe("Lookback window in hours; omit for no time limit"),
6111
+ limit: z15.number().int().min(1).max(50).default(20).describe("Max items returned")
5986
6112
  },
5987
6113
  handler: async (args, ctx) => {
5988
6114
  const op = OPERATIONS["search.semantic"];
@@ -6035,7 +6161,7 @@ function buildSearchCommand() {
6035
6161
  }
6036
6162
  // src/verbs/sentiment.ts
6037
6163
  import { Command as Command18, Option as Option16 } from "commander";
6038
- import { z as z15 } from "zod";
6164
+ import { z as z16 } from "zod";
6039
6165
  var SCOPE_VALUES = [
6040
6166
  "all_a",
6041
6167
  "all_a_ex_st",
@@ -6059,8 +6185,8 @@ var sentimentSpec = {
6059
6185
  name: "sentiment",
6060
6186
  description: "Aggregate market sentiment indicators (limit-up/down counts, breadth, divergence index, top movers). Defaults to all_a_ex_st (all A-shares ex-ST). Pass `at_date` (YYYY-MM-DD) for any historical trading day; omit for today's latest snapshot. Backward-compat alias of `sentiment_overview` — prefer the latter in new agent code.",
6061
6187
  inputSchema: {
6062
- scope: z15.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6063
- at_date: z15.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today's realtime snapshot")
6188
+ scope: z16.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6189
+ at_date: z16.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today's realtime snapshot")
6064
6190
  },
6065
6191
  handler: async (args, ctx) => {
6066
6192
  const op = OPERATIONS["sentiment.overview"];
@@ -6077,8 +6203,8 @@ var sentimentOverviewSpec = {
6077
6203
  name: "sentiment_overview",
6078
6204
  description: "Aggregate sentiment snapshot for one trading day's latest minute: up/down/flat counts, limit-up/down, gt3/lt3 breadth, divergence index + label, MA20/50/200 breadth, 52w new high/low. `at_date` omitted = today realtime (Redis); explicit YYYY-MM-DD = historical from ClickHouse `market_breadth_intraday` last minute.",
6079
6205
  inputSchema: {
6080
- scope: z15.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6081
- at_date: z15.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today realtime")
6206
+ scope: z16.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6207
+ at_date: z16.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today realtime")
6082
6208
  },
6083
6209
  handler: async (args, ctx) => {
6084
6210
  const op = OPERATIONS["sentiment.overview"];
@@ -6095,8 +6221,8 @@ var sentimentBreadthSpec = {
6095
6221
  name: "sentiment_breadth",
6096
6222
  description: "Intraday breadth time series for one trading day (up to 241 minute bars; 13:00 excluded due to sparse Sina ticks). `at_date` omitted = today (pre-open returns empty); explicit YYYY-MM-DD = historical replay. Returns per-minute rows of up/down/flat/limit_up/limit_down/breadth/divergence fields — same shape as `sentiment_overview` per row.",
6097
6223
  inputSchema: {
6098
- scope: z15.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6099
- at_date: z15.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today")
6224
+ scope: z16.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6225
+ at_date: z16.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today")
6100
6226
  },
6101
6227
  handler: async (args, ctx) => {
6102
6228
  const op = OPERATIONS["sentiment.breadth"];
@@ -6113,9 +6239,9 @@ var sentimentTurnoverSpec = {
6113
6239
  name: "sentiment_turnover",
6114
6240
  description: "Intraday turnover time series with `current_turnover` / `predicted_total` / `delta_vs_yesterday` headline fields. `at_date` omitted = today (pre-open returns nulls); explicit YYYY-MM-DD = historical. `days` (1-5) pulls that day + N-1 prior trading days for same-minute YoY comparison.",
6115
6241
  inputSchema: {
6116
- scope: z15.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6117
- at_date: z15.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today"),
6118
- days: z15.number().int().min(1).max(5).default(1).describe("at_date 当日 + 之前 days-1 天(1-5,默认 1)")
6242
+ scope: z16.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
6243
+ at_date: z16.string().regex(DATE_RE6).optional().describe("Trade date YYYY-MM-DD; omit for today"),
6244
+ days: z16.number().int().min(1).max(5).default(1).describe("at_date 当日 + 之前 days-1 天(1-5,默认 1)")
6119
6245
  },
6120
6246
  handler: async (args, ctx) => {
6121
6247
  const op = OPERATIONS["sentiment.turnover"];
@@ -6210,19 +6336,20 @@ function buildSentimentCommand() {
6210
6336
  }
6211
6337
  // src/verbs/views.ts
6212
6338
  import { Command as Command19, Option as Option17 } from "commander";
6213
- import { z as z16 } from "zod";
6339
+ import { z as z17 } from "zod";
6214
6340
  var viewsSpec = {
6215
6341
  name: "views",
6216
- description: "PRIMARY research source for stock judgement: analyst views / sell-side reports / long-form opinion stream, with research_entity_id attribution. Prefer this over `news` when forming an investment opinion. Each item carries `full_text_available`; when true the item is a parsed research report and you can pull its full text with the `report` verb. AH dual-listing: for an A+H listed company pass the A-share canonical to get the consolidated coverage (most domestic analyst views attribute to the A-share security); use HK code only if the user explicitly asks for the HK perspective.",
6342
+ description: "PRIMARY research source for stock judgement: analyst views / sell-side reports / long-form opinion stream, with research_entity_id attribution. Prefer this over `news` when forming an investment opinion. Returns `fields=compact` by default (ai_summary + first ~300 chars of content, `content_truncated` flag) — typically 20-50x smaller than full; pass fields='full' only when summaries are not enough. Recommended reading path: `digest` for a one-shot overview this verb (compact) to scan → `report` for the full text of a single item (`full_text_available=true` means a parsed research report). AH dual-listing: for an A+H listed company pass the A-share canonical to get the consolidated coverage (most domestic analyst views attribute to the A-share security); use HK code only if the user explicitly asks for the HK perspective.",
6217
6343
  inputSchema: {
6218
- code: z16.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700, US:AAPL)"),
6219
- analyst: z16.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
6220
- institution: z16.string().optional().describe("Broker / institution Chinese name"),
6221
- since_days: z16.number().int().min(1).max(365).default(7).describe("Lookback in days. ≤30 = rolling feed; >30 auto-switches to range history " + "(needs a code/analyst/institution filter; Pro-only up to 365). For an explicit window use from/to."),
6222
- from: z16.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs a filter; Pro-only up to 365 days back."),
6223
- to: z16.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
6224
- offset: z16.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
6225
- limit: z16.number().int().min(1).max(100).default(30).describe("Max items per page")
6344
+ code: z17.string().regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/).optional().describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700, US:AAPL)"),
6345
+ analyst: z17.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
6346
+ institution: z17.string().optional().describe("Broker / institution Chinese name"),
6347
+ since_days: z17.number().int().min(1).max(365).default(7).describe("Lookback in days. ≤30 = rolling feed; >30 auto-switches to range history " + "(needs a code/analyst/institution filter; Pro-only up to 365). For an explicit window use from/to."),
6348
+ from: z17.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range start (China date YYYY-MM-DD). Range mode: needs a filter; Pro-only up to 365 days back."),
6349
+ to: z17.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("History range end (inclusive, China date; defaults to today). Use with from."),
6350
+ offset: z17.number().int().min(0).optional().describe("Pagination offset for paging through long history ranges."),
6351
+ limit: z17.number().int().min(1).max(100).default(30).describe("Max items per page"),
6352
+ fields: z17.enum(["compact", "full"]).optional().describe("Response shape: compact (default — ai_summary + first ~300 chars) or full (complete content)")
6226
6353
  },
6227
6354
  handler: async (args, ctx) => {
6228
6355
  const op = OPERATIONS["views.recent"];
@@ -6246,6 +6373,8 @@ function buildViewsCallArgs(args) {
6246
6373
  callArgs.institution = args.institution;
6247
6374
  if (typeof args.offset === "number")
6248
6375
  callArgs.offset = args.offset;
6376
+ if (args.fields === "compact" || args.fields === "full")
6377
+ callArgs.fields = args.fields;
6249
6378
  const explicitRange = typeof args.from === "string" && args.from.length > 0;
6250
6379
  if (explicitRange || sinceDays > 30) {
6251
6380
  if (!hasFilter) {
@@ -6267,10 +6396,10 @@ var reportSpec = {
6267
6396
  name: "report",
6268
6397
  description: "Fetch the FULL research-report text behind a view (mineru-parsed long-form), for when the `views` summary is not enough. Only works on views where `full_text_available=true` (gdrive sell-side reports). Two-step usage: default `format=outline` returns a few-KB heading map + total_chars + each heading's char_offset; then call `format=full` with `offset` (e.g. an outline char_offset) to read the body in pages — follow `next_offset` to page through large docs instead of pulling everything at once.",
6269
6398
  inputSchema: {
6270
- view_id: z16.string().regex(/^[0-9]+$/).describe("View id from `views` items[].id (must have full_text_available=true)"),
6271
- format: z16.enum(["outline", "full"]).default("outline").describe("outline=heading map (default); full=body slice"),
6272
- offset: z16.number().int().min(0).default(0).describe("full mode: start char offset (use an outline char_offset)"),
6273
- max_chars: z16.number().int().min(1).max(80000).default(40000).describe("full mode: max chars per page (hard cap 80000)")
6399
+ view_id: z17.string().regex(/^[0-9]+$/).describe("View id from `views` items[].id (must have full_text_available=true)"),
6400
+ format: z17.enum(["outline", "full"]).default("outline").describe("outline=heading map (default); full=body slice"),
6401
+ offset: z17.number().int().min(0).default(0).describe("full mode: start char offset (use an outline char_offset)"),
6402
+ max_chars: z17.number().int().min(1).max(80000).default(40000).describe("full mode: max chars per page (hard cap 80000)")
6274
6403
  },
6275
6404
  handler: async (args, ctx) => {
6276
6405
  const op = OPERATIONS["views.document"];
@@ -6308,6 +6437,10 @@ function buildViewsCommand() {
6308
6437
  cmd.addOption(new Option17("--to <date>", "History range end YYYY-MM-DD (inclusive; default today)"));
6309
6438
  cmd.addOption(new Option17("--offset <n>", "Pagination offset for long history").default("0"));
6310
6439
  cmd.addOption(new Option17("--limit <n>", "Max items (1-100)").default("30"));
6440
+ cmd.addOption(new Option17("--fields <mode>", "Response shape: compact (default) or full").choices([
6441
+ "compact",
6442
+ "full"
6443
+ ]));
6311
6444
  cmd.action(async (opts) => {
6312
6445
  if (!OPERATIONS["views.recent"]) {
6313
6446
  emitVerbError("internal_error", "views.recent missing from codegen", undefined, 2);
@@ -6327,6 +6460,8 @@ function buildViewsCommand() {
6327
6460
  args.from = opts.from;
6328
6461
  if (opts.to)
6329
6462
  args.to = opts.to;
6463
+ if (opts.fields)
6464
+ args.fields = opts.fields;
6330
6465
  await executeVerb(async (ctx) => viewsSpec.handler(args, ctx));
6331
6466
  });
6332
6467
  return cmd;
@@ -6360,6 +6495,7 @@ var ALL_VERB_SPECS = [
6360
6495
  quoteSpec,
6361
6496
  marketStatusSpec,
6362
6497
  marketMoversSpec,
6498
+ indexSnapshotSpec,
6363
6499
  viewsSpec,
6364
6500
  reportSpec,
6365
6501
  newsSpec,
@@ -6394,7 +6530,8 @@ var ALL_VERB_SPECS = [
6394
6530
  financialsReportsSpec,
6395
6531
  financialsSeriesSpec,
6396
6532
  ownershipShowSpec,
6397
- ownershipLockupsSpec
6533
+ ownershipLockupsSpec,
6534
+ capabilitiesSpec
6398
6535
  ];
6399
6536
 
6400
6537
  // src/tools/mcp.ts
@@ -7181,7 +7318,7 @@ function buildDoctorCommand() {
7181
7318
 
7182
7319
  // src/tools/schema.ts
7183
7320
  import { Command as Command27 } from "commander";
7184
- import { z as z17 } from "zod";
7321
+ import { z as z18 } from "zod";
7185
7322
 
7186
7323
  // src/_generated/help.ts
7187
7324
  var HELP = {
@@ -7245,6 +7382,11 @@ var HELP = {
7245
7382
  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.",
7246
7383
  example: "echopai bars minute-batch --codes ['SSE:600519', 'SZSE:000001']"
7247
7384
  },
7385
+ "capabilities.show": {
7386
+ summary: "Capability changelog and API version discovery.",
7387
+ description: "Returns the current API spec version plus a machine-readable changelog of capability changes (new endpoints, new fields, behavior fixes), newest first. Check it at session start — or after an unexpected response shape — to discover capabilities added since your integration was written. Free and unauthenticated.",
7388
+ example: "echopai capabilities show"
7389
+ },
7248
7390
  "concepts.alerts": {
7249
7391
  summary: "List currently active concept alerts.",
7250
7392
  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.",
@@ -7357,7 +7499,7 @@ var HELP = {
7357
7499
  },
7358
7500
  "news.feed": {
7359
7501
  summary: "List recent news.",
7360
- description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility.",
7502
+ description: "Returns recent news. Equivalent to `/v1/news/list`; retained for backward compatibility. Partner/agent callers receive `fields=compact` by default (id, title, ai_summary, tagged securities, first ~300 chars of content); pass `fields=full` for complete content.",
7361
7503
  example: "echopai news feed"
7362
7504
  },
7363
7505
  "news.get": {
@@ -7387,12 +7529,12 @@ var HELP = {
7387
7529
  },
7388
7530
  quote: {
7389
7531
  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.",
7532
+ 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. The response carries a `session` label (`pre_open` / `auction` / `open` / `break` / `closed`) and, outside continuous trading, an explanatory `note`: during 9:00–9:14 pre-open the previous session's snapshot is returned; during the 9:15–9:29 call auction, prices are indicative auction match prices. Rows whose feed has stopped updating (suspension / feed drop) keep their last snapshot and are flagged `stale: true` with `stale_since`. Requires a `quote:l1`, `quote:l2`, or `quote:delayed` scope.",
7391
7533
  example: "echopai quote --codes ['SSE:600519', 'SZSE:000001']"
7392
7534
  },
7393
7535
  "quote.scan": {
7394
7536
  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.",
7537
+ 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. The response carries a `session` label (`pre_open` / `auction` / `open` / `break` / `closed`) plus an explanatory `note` outside continuous trading: during 9:00–9:14 pre-open the previous session's snapshot is returned; during the 9:15–9:29 call auction, prices are indicative auction match prices. Instruments whose feed has stopped updating (suspension / feed drop) are excluded from the default scan; pass `include=all` to keep them, flagged with `stale: true` and `stale_since`. 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.",
7396
7538
  example: "echopai quote scan"
7397
7539
  },
7398
7540
  "search.semantic": {
@@ -7442,7 +7584,7 @@ var HELP = {
7442
7584
  },
7443
7585
  "views.feed": {
7444
7586
  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.",
7587
+ description: "Returns a stream of analyst views, sell-side research, and long-form opinion. Filter by institution, analyst, security, or recency (`hours`). Partner/agent callers receive `fields=compact` by default (id, ai_summary, attribution, tagged securities, first ~300 chars of content); pass `fields=full` for complete content. Requires the `views:read` scope.",
7446
7588
  example: "echopai views feed"
7447
7589
  },
7448
7590
  "views.get": {
@@ -7461,7 +7603,7 @@ var HELP = {
7461
7603
  function verbToRecord(spec) {
7462
7604
  let inputSchema;
7463
7605
  try {
7464
- inputSchema = z17.toJSONSchema(z17.object(spec.inputSchema));
7606
+ inputSchema = z18.toJSONSchema(z18.object(spec.inputSchema));
7465
7607
  } catch {
7466
7608
  inputSchema = {
7467
7609
  type: "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echopai",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Command-line interface for the EchoPai Open Platform: stock-market data, news, analyst views, sentiment, signals, backtests.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://echopai.com",