echopai 2.3.0 → 2.4.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 (54) hide show
  1. package/README.md +63 -348
  2. package/dist/bin.js +8298 -190
  3. package/package.json +11 -13
  4. package/dist/_generated/commands.js +0 -378
  5. package/dist/_generated/help.js +0 -295
  6. package/dist/_generated/operations.js +0 -2385
  7. package/dist/runtime/auth.js +0 -95
  8. package/dist/runtime/envelope.js +0 -52
  9. package/dist/runtime/errors.js +0 -186
  10. package/dist/runtime/filters.js +0 -153
  11. package/dist/runtime/format.js +0 -143
  12. package/dist/runtime/http.js +0 -65
  13. package/dist/runtime/idempotency.js +0 -18
  14. package/dist/runtime/invoker.js +0 -391
  15. package/dist/runtime/io.js +0 -16
  16. package/dist/runtime/paginator.js +0 -146
  17. package/dist/runtime/trace.js +0 -99
  18. package/dist/runtime/tty.js +0 -51
  19. package/dist/runtime/update_check.js +0 -120
  20. package/dist/runtime/update_worker.js +0 -63
  21. package/dist/runtime/verb_cmd.js +0 -72
  22. package/dist/runtime/verb_runner.js +0 -152
  23. package/dist/runtime/whoami_cache.js +0 -109
  24. package/dist/tools/api.js +0 -81
  25. package/dist/tools/completion.js +0 -116
  26. package/dist/tools/config.js +0 -123
  27. package/dist/tools/doctor.js +0 -183
  28. package/dist/tools/login.js +0 -99
  29. package/dist/tools/mcp.js +0 -141
  30. package/dist/tools/raw.js +0 -96
  31. package/dist/tools/schema.js +0 -58
  32. package/dist/tools/trace.js +0 -54
  33. package/dist/tools/upgrade.js +0 -103
  34. package/dist/tools/welcome.js +0 -225
  35. package/dist/tools/whoami.js +0 -132
  36. package/dist/verbs/_spec.js +0 -15
  37. package/dist/verbs/announcements.js +0 -195
  38. package/dist/verbs/bars_batch.js +0 -66
  39. package/dist/verbs/chart.js +0 -110
  40. package/dist/verbs/concepts.js +0 -393
  41. package/dist/verbs/digest.js +0 -351
  42. package/dist/verbs/financials.js +0 -212
  43. package/dist/verbs/hot.js +0 -29
  44. package/dist/verbs/index.js +0 -88
  45. package/dist/verbs/limit_up.js +0 -156
  46. package/dist/verbs/lookup.js +0 -72
  47. package/dist/verbs/market.js +0 -185
  48. package/dist/verbs/news.js +0 -81
  49. package/dist/verbs/quote.js +0 -53
  50. package/dist/verbs/scan.js +0 -42
  51. package/dist/verbs/search.js +0 -105
  52. package/dist/verbs/sentiment.js +0 -231
  53. package/dist/verbs/views.js +0 -85
  54. package/dist/version.js +0 -5
@@ -1,105 +0,0 @@
1
- /**
2
- * `echopai search ...` + MCP tool `search`.
3
- *
4
- * HYBRID semantic search across news + analyst views.
5
- *
6
- * Use this verb when:
7
- * - the user is exploring a THEME / 概念 ("锂电池", "芯片", "AI 算力") — pure
8
- * vector + concept expansion brings out the full supply chain even when
9
- * the query word never appears verbatim in the doc.
10
- * - the user gives a non-canonical company name or analyst name — entity
11
- * lane resolves it through securities / research_entities tables.
12
- *
13
- * Prefer this over `news` / `views` for open-ended discovery. For strict
14
- * time-window listing or filtered feed, keep using `views` / `news`.
15
- */
16
- import { Command, Option } from "commander";
17
- import { z } from "zod";
18
- import { OPERATIONS } from "../_generated/operations.js";
19
- import { executeVerb } from "../runtime/verb_cmd.js";
20
- import { callOp } from "../runtime/verb_runner.js";
21
- export const searchSpec = {
22
- name: "search",
23
- description: "HYBRID semantic search across analyst views + news (views weighted higher per feedback_views_over_news). Combines entity lookup (company codes, analyst names), pgvector kNN, trigram fuzzy and exact ILIKE; RRF-fused then reranked. Best for theme/concept queries: '锂电池' brings out 正极材料/碳酸锂/宁德时代; '芯片' brings out 存储芯片/洁净室/晶圆代工. Use `news` or `views` instead if you need strict time-window listing.",
24
- inputSchema: {
25
- q: z
26
- .string()
27
- .min(1)
28
- .max(200)
29
- .describe("Free-text query (Chinese or English)"),
30
- type: z
31
- .enum(["news", "views", "all"])
32
- .default("all")
33
- .describe("Search scope (views weighted higher in 'all')"),
34
- mode: z
35
- .enum(["hybrid", "exact"])
36
- .default("hybrid")
37
- .describe("hybrid = semantic + concept expansion + rerank; exact = ILIKE only"),
38
- hours: z
39
- .number()
40
- .int()
41
- .min(1)
42
- .max(720)
43
- .optional()
44
- .describe("Lookback window in hours; omit for no time limit"),
45
- limit: z
46
- .number()
47
- .int()
48
- .min(1)
49
- .max(50)
50
- .default(20)
51
- .describe("Max items returned"),
52
- },
53
- handler: async (args, ctx) => {
54
- const op = OPERATIONS["search.semantic"];
55
- if (!op)
56
- throw new Error("search.semantic op missing from codegen");
57
- const callArgs = {
58
- q: args.q,
59
- type: args.type,
60
- mode: args.mode,
61
- limit: args.limit,
62
- };
63
- if (args.hours !== undefined)
64
- callArgs.hours = args.hours;
65
- return callOp(op, callArgs, ctx);
66
- },
67
- backingOps: ["search.semantic"],
68
- };
69
- function clamp(raw, min, max, fallback) {
70
- const n = Math.floor(Number(raw));
71
- if (!Number.isFinite(n))
72
- return fallback;
73
- if (n < min)
74
- return min;
75
- if (n > max)
76
- return max;
77
- return n;
78
- }
79
- export function buildSearchCommand() {
80
- const cmd = new Command(searchSpec.name).description(searchSpec.description);
81
- cmd.argument("<query...>", "Search query (Chinese or English)");
82
- cmd.addOption(new Option("--type <scope>", "Search scope")
83
- .choices(["news", "views", "all"])
84
- .default("all"));
85
- cmd.addOption(new Option("--mode <mode>", "hybrid | exact")
86
- .choices(["hybrid", "exact"])
87
- .default("hybrid"));
88
- cmd.addOption(new Option("--exact", "shortcut for --mode exact"));
89
- cmd.addOption(new Option("--hours <n>", "Lookback window (1-720 hours)"));
90
- cmd.addOption(new Option("--limit <n>", "Max items (1-50)").default("20"));
91
- cmd.action(async (queryTokens, opts) => {
92
- const q = queryTokens.join(" ").trim();
93
- const args = {
94
- q,
95
- type: opts.type,
96
- mode: opts.exact ? "exact" : opts.mode,
97
- limit: clamp(opts.limit, 1, 50, 20),
98
- };
99
- if (opts.hours !== undefined) {
100
- args.hours = clamp(opts.hours, 1, 720, 24);
101
- }
102
- await executeVerb(async (ctx) => searchSpec.handler(args, ctx));
103
- });
104
- return cmd;
105
- }
@@ -1,231 +0,0 @@
1
- /**
2
- * `echopai sentiment ...` + MCP tools `sentiment` (overview alias, BC) /
3
- * `sentiment_overview` / `sentiment_breadth` / `sentiment_turnover` /
4
- * `sentiment_pct_distribution`.
5
- *
6
- * 四个子端点全部支持 `--scope` 切换股票池;`overview` / `breadth` /
7
- * `turnover` 进一步支持 `--date YYYY-MM-DD` 取历史任意交易日的当日聚合
8
- * (盘中实时为 Redis cache,历史走 ClickHouse `market_breadth_intraday` /
9
- * `market_turnover_intraday`)。`turnover` 还支持 `--days 1-5` 拉同分钟同比。
10
- *
11
- * 顶层 `echopai sentiment` 无子命令时仍走 overview(保持 v2.2.0 兼容)。
12
- */
13
- import { Command, Option } from "commander";
14
- import { z } from "zod";
15
- import { OPERATIONS } from "../_generated/operations.js";
16
- import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
17
- import { callOp } from "../runtime/verb_runner.js";
18
- const SCOPE_VALUES = [
19
- "all_a",
20
- "all_a_ex_st",
21
- "main_board",
22
- "star_market",
23
- "chinext",
24
- "bse",
25
- ];
26
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
27
- function clampInt(raw, min, max, fallback) {
28
- const n = Math.floor(Number(raw));
29
- if (!Number.isFinite(n))
30
- return fallback;
31
- if (n < min)
32
- return min;
33
- if (n > max)
34
- return max;
35
- return n;
36
- }
37
- /**
38
- * Legacy `sentiment` spec — kept as alias of overview for v2.2.0 backward
39
- * compatibility (MCP clients that registered `sentiment` keep working).
40
- */
41
- export const sentimentSpec = {
42
- name: "sentiment",
43
- 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.",
44
- inputSchema: {
45
- scope: z
46
- .enum(SCOPE_VALUES)
47
- .default("all_a_ex_st")
48
- .describe("Universe filter"),
49
- at_date: z
50
- .string()
51
- .regex(DATE_RE)
52
- .optional()
53
- .describe("Trade date YYYY-MM-DD; omit for today's realtime snapshot"),
54
- },
55
- handler: async (args, ctx) => {
56
- const op = OPERATIONS["sentiment.overview"];
57
- if (!op)
58
- throw new Error("sentiment.overview op missing");
59
- const callArgs = { scope: args.scope };
60
- if (args.at_date)
61
- callArgs.trade_date = args.at_date;
62
- return callOp(op, callArgs, ctx);
63
- },
64
- backingOps: ["sentiment.overview"],
65
- };
66
- export const sentimentOverviewSpec = {
67
- name: "sentiment_overview",
68
- 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.",
69
- inputSchema: {
70
- scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
71
- at_date: z
72
- .string()
73
- .regex(DATE_RE)
74
- .optional()
75
- .describe("Trade date YYYY-MM-DD; omit for today realtime"),
76
- },
77
- handler: async (args, ctx) => {
78
- const op = OPERATIONS["sentiment.overview"];
79
- if (!op)
80
- throw new Error("sentiment.overview op missing");
81
- const callArgs = { scope: args.scope };
82
- if (args.at_date)
83
- callArgs.trade_date = args.at_date;
84
- return callOp(op, callArgs, ctx);
85
- },
86
- backingOps: ["sentiment.overview"],
87
- };
88
- export const sentimentBreadthSpec = {
89
- name: "sentiment_breadth",
90
- 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.",
91
- inputSchema: {
92
- scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
93
- at_date: z
94
- .string()
95
- .regex(DATE_RE)
96
- .optional()
97
- .describe("Trade date YYYY-MM-DD; omit for today"),
98
- },
99
- handler: async (args, ctx) => {
100
- const op = OPERATIONS["sentiment.breadth"];
101
- if (!op)
102
- throw new Error("sentiment.breadth op missing");
103
- const callArgs = { scope: args.scope };
104
- if (args.at_date)
105
- callArgs.trade_date = args.at_date;
106
- return callOp(op, callArgs, ctx);
107
- },
108
- backingOps: ["sentiment.breadth"],
109
- };
110
- export const sentimentTurnoverSpec = {
111
- name: "sentiment_turnover",
112
- 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.",
113
- inputSchema: {
114
- scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
115
- at_date: z
116
- .string()
117
- .regex(DATE_RE)
118
- .optional()
119
- .describe("Trade date YYYY-MM-DD; omit for today"),
120
- days: z
121
- .number()
122
- .int()
123
- .min(1)
124
- .max(5)
125
- .default(1)
126
- .describe("at_date 当日 + 之前 days-1 天(1-5,默认 1)"),
127
- },
128
- handler: async (args, ctx) => {
129
- const op = OPERATIONS["sentiment.turnover"];
130
- if (!op)
131
- throw new Error("sentiment.turnover op missing");
132
- const callArgs = { scope: args.scope, days: args.days };
133
- if (args.at_date)
134
- callArgs.trade_date = args.at_date;
135
- return callOp(op, callArgs, ctx);
136
- },
137
- backingOps: ["sentiment.turnover"],
138
- };
139
- export const sentimentPctDistributionSpec = {
140
- name: "sentiment_pct_distribution",
141
- description: "Real-time A-share market pct-change distribution: bucket counts at ≤-10% / -9% / ... / +10% / 涨停, plus universe total. Snapshot only — historical replay not yet exposed.",
142
- inputSchema: {},
143
- handler: async (_args, ctx) => {
144
- const op = OPERATIONS["sentiment.pct-distribution"];
145
- if (!op)
146
- throw new Error("sentiment.pct-distribution op missing");
147
- return callOp(op, {}, ctx);
148
- },
149
- backingOps: ["sentiment.pct-distribution"],
150
- };
151
- function addScope(cmd) {
152
- cmd.addOption(new Option("--scope <universe>", "Universe filter")
153
- .choices([...SCOPE_VALUES])
154
- .default("all_a_ex_st"));
155
- }
156
- function addDate(cmd) {
157
- cmd.addOption(new Option("--date <YYYY-MM-DD>", "Trade date; omit for today's realtime"));
158
- }
159
- export function buildSentimentCommand() {
160
- const cmd = new Command("sentiment").description("Market sentiment — overview / breadth / turnover / pct-distribution; supports any historical trade_date via --date.");
161
- // No-subcommand path: `echopai sentiment [--scope X]` → overview (BC with
162
- // v2.2.0 where sentiment was a single-shot verb). NOTE: `--date` is
163
- // intentionally NOT declared at parent level — commander would consume
164
- // it from `sentiment overview --date X` and never propagate to the
165
- // subcommand. For historical date queries, always use the explicit
166
- // subcommand form: `echopai sentiment overview --date 2026-05-20`.
167
- addScope(cmd);
168
- cmd.action(async (opts, command) => {
169
- // commander invokes parent action even when a subcommand runs unless
170
- // we guard. Skip if any argv token after the noun is a registered
171
- // subcommand name.
172
- const subNames = new Set(command.commands.map((c) => c.name()));
173
- const argv = process.argv.slice(2);
174
- if (argv.some((tok) => subNames.has(tok)))
175
- return;
176
- if (!OPERATIONS["sentiment.overview"]) {
177
- emitVerbError("internal_error", "sentiment.overview missing", undefined, 2);
178
- }
179
- await executeVerb(async (ctx) => sentimentSpec.handler({ scope: opts.scope }, ctx));
180
- });
181
- const overview = cmd.command("overview").description(sentimentOverviewSpec.description);
182
- addScope(overview);
183
- addDate(overview);
184
- overview.action(async (opts) => {
185
- if (!OPERATIONS["sentiment.overview"]) {
186
- emitVerbError("internal_error", "sentiment.overview missing", undefined, 2);
187
- }
188
- const args = { scope: opts.scope };
189
- if (opts.date)
190
- args.at_date = opts.date;
191
- await executeVerb(async (ctx) => sentimentOverviewSpec.handler(args, ctx));
192
- });
193
- const breadth = cmd.command("breadth").description(sentimentBreadthSpec.description);
194
- addScope(breadth);
195
- addDate(breadth);
196
- breadth.action(async (opts) => {
197
- if (!OPERATIONS["sentiment.breadth"]) {
198
- emitVerbError("internal_error", "sentiment.breadth missing", undefined, 2);
199
- }
200
- const args = { scope: opts.scope };
201
- if (opts.date)
202
- args.at_date = opts.date;
203
- await executeVerb(async (ctx) => sentimentBreadthSpec.handler(args, ctx));
204
- });
205
- const turnover = cmd.command("turnover").description(sentimentTurnoverSpec.description);
206
- addScope(turnover);
207
- addDate(turnover);
208
- turnover.addOption(new Option("--days <n>", "at_date 当日 + 之前 days-1 天 (1-5)").default("1"));
209
- turnover.action(async (opts) => {
210
- if (!OPERATIONS["sentiment.turnover"]) {
211
- emitVerbError("internal_error", "sentiment.turnover missing", undefined, 2);
212
- }
213
- const args = {
214
- scope: opts.scope,
215
- days: clampInt(opts.days, 1, 5, 1),
216
- };
217
- if (opts.date)
218
- args.at_date = opts.date;
219
- await executeVerb(async (ctx) => sentimentTurnoverSpec.handler(args, ctx));
220
- });
221
- const pctDist = cmd
222
- .command("pct-distribution")
223
- .description(sentimentPctDistributionSpec.description);
224
- pctDist.action(async () => {
225
- if (!OPERATIONS["sentiment.pct-distribution"]) {
226
- emitVerbError("internal_error", "sentiment.pct-distribution missing", undefined, 2);
227
- }
228
- await executeVerb(async (ctx) => sentimentPctDistributionSpec.handler({}, ctx));
229
- });
230
- return cmd;
231
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * `echopai views ...` + MCP tool `views`.
3
- *
4
- * PRIMARY research source (feedback_views_over_news.md).
5
- */
6
- import { Command, Option } from "commander";
7
- import { z } from "zod";
8
- import { OPERATIONS } from "../_generated/operations.js";
9
- import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
10
- import { callOp } from "../runtime/verb_runner.js";
11
- export const viewsSpec = {
12
- name: "views",
13
- 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. 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.",
14
- inputSchema: {
15
- code: z
16
- .string()
17
- // Any-market:与 openapi components.schemas.CanonicalCodeAny 一致
18
- // 研究/观点类 endpoint 不限 A 股;HK:00700 / US:AAPL 也允许(PR #D 拉齐)
19
- .regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/)
20
- .optional()
21
- .describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700, US:AAPL)"),
22
- analyst: z.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
23
- institution: z.string().optional().describe("Broker / institution Chinese name"),
24
- since_days: z
25
- .number()
26
- .int()
27
- .min(1)
28
- .max(90)
29
- .default(7)
30
- .describe("Lookback window in days (research time-horizon is longer than news)"),
31
- limit: z.number().int().min(1).max(100).default(30).describe("Max items"),
32
- },
33
- handler: async (args, ctx) => {
34
- const op = OPERATIONS["views.recent"];
35
- if (!op)
36
- throw new Error("views.recent op missing from codegen");
37
- const callArgs = {
38
- since_days: args.since_days,
39
- limit: args.limit,
40
- };
41
- if (args.code)
42
- callArgs.security = args.code;
43
- if (args.analyst)
44
- callArgs.analyst = args.analyst;
45
- if (args.institution)
46
- callArgs.institution = args.institution;
47
- return callOp(op, callArgs, ctx);
48
- },
49
- backingOps: ["views.recent"],
50
- };
51
- function clamp(raw, min, max, fallback) {
52
- const n = Math.floor(Number(raw));
53
- if (!Number.isFinite(n))
54
- return fallback;
55
- if (n < min)
56
- return min;
57
- if (n > max)
58
- return max;
59
- return n;
60
- }
61
- export function buildViewsCommand() {
62
- const cmd = new Command(viewsSpec.name).description(viewsSpec.description);
63
- cmd.addOption(new Option("--code <canonical_code>", "Filter by security canonical_code"));
64
- cmd.addOption(new Option("--analyst <name>", "Analyst Chinese name (exact / fuzzy)"));
65
- cmd.addOption(new Option("--institution <name>", "Broker / institution Chinese name"));
66
- cmd.addOption(new Option("--since-days <n>", "Lookback window (1-90 days)").default("7"));
67
- cmd.addOption(new Option("--limit <n>", "Max items (1-100)").default("30"));
68
- cmd.action(async (opts) => {
69
- if (!OPERATIONS["views.recent"]) {
70
- emitVerbError("internal_error", "views.recent missing from codegen", undefined, 2);
71
- }
72
- const args = {
73
- since_days: clamp(opts.sinceDays, 1, 90, 7),
74
- limit: clamp(opts.limit, 1, 100, 30),
75
- };
76
- if (opts.code)
77
- args.code = opts.code;
78
- if (opts.analyst)
79
- args.analyst = opts.analyst;
80
- if (opts.institution)
81
- args.institution = opts.institution;
82
- await executeVerb(async (ctx) => viewsSpec.handler(args, ctx));
83
- });
84
- return cmd;
85
- }
package/dist/version.js DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * Pinned at build time. Don't read package.json at runtime — adds startup latency
3
- * and breaks bundling.
4
- */
5
- export const CLI_VERSION = "2.3.0";