echopai 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/_generated/commands.js +46 -38
- package/dist/_generated/help.js +46 -41
- package/dist/_generated/operations.js +390 -167
- package/dist/bin.js +18 -7
- package/dist/tools/welcome.js +190 -0
- package/dist/verbs/digest.js +16 -14
- package/dist/verbs/financials.js +212 -0
- package/dist/verbs/index.js +14 -6
- package/dist/verbs/search.js +105 -0
- package/dist/verbs/views.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/verbs/research.js +0 -44
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
}
|
package/dist/verbs/views.js
CHANGED
|
@@ -10,7 +10,7 @@ import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
|
10
10
|
import { callOp } from "../runtime/verb_runner.js";
|
|
11
11
|
export const viewsSpec = {
|
|
12
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
|
|
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.",
|
|
14
14
|
inputSchema: {
|
|
15
15
|
code: z
|
|
16
16
|
.string()
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|
package/dist/verbs/research.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai research [--entity <id>]` + MCP tool `research`.
|
|
3
|
-
*
|
|
4
|
-
* Views quality signal layer.
|
|
5
|
-
*/
|
|
6
|
-
import { Command, Option } from "commander";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { OPERATIONS } from "../_generated/operations.js";
|
|
9
|
-
import { executeVerb } from "../runtime/verb_cmd.js";
|
|
10
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
11
|
-
export const researchSpec = {
|
|
12
|
-
name: "research",
|
|
13
|
-
description: "Research-entity performance — hit rate / coverage / avg excess return. Pair with `views` (which returns research_entity_id per row) to assess analyst quality. Omit entity_id for aggregate top-performers list.",
|
|
14
|
-
inputSchema: {
|
|
15
|
-
entity_id: z
|
|
16
|
-
.string()
|
|
17
|
-
.optional()
|
|
18
|
-
.describe("Specific research_entity_id; omit for aggregate top-performers list"),
|
|
19
|
-
},
|
|
20
|
-
handler: async (args, ctx) => {
|
|
21
|
-
if (args.entity_id) {
|
|
22
|
-
const op = OPERATIONS["research.entity-performance"];
|
|
23
|
-
if (!op)
|
|
24
|
-
throw new Error("research.entity-performance op missing");
|
|
25
|
-
return callOp(op, { entity_id: args.entity_id }, ctx);
|
|
26
|
-
}
|
|
27
|
-
const op = OPERATIONS["research.entity-performance-list"];
|
|
28
|
-
if (!op)
|
|
29
|
-
throw new Error("research.entity-performance-list op missing");
|
|
30
|
-
return callOp(op, {}, ctx);
|
|
31
|
-
},
|
|
32
|
-
backingOps: ["research.entity-performance-list", "research.entity-performance"],
|
|
33
|
-
};
|
|
34
|
-
export function buildResearchCommand() {
|
|
35
|
-
const cmd = new Command(researchSpec.name).description(researchSpec.description);
|
|
36
|
-
cmd.addOption(new Option("--entity <id>", "Specific research_entity_id; omit for aggregate list"));
|
|
37
|
-
cmd.action(async (opts) => {
|
|
38
|
-
const args = {};
|
|
39
|
-
if (opts.entity)
|
|
40
|
-
args.entity_id = opts.entity;
|
|
41
|
-
await executeVerb(async (ctx) => researchSpec.handler(args, ctx));
|
|
42
|
-
});
|
|
43
|
-
return cmd;
|
|
44
|
-
}
|