echopai 2.2.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.
- package/README.md +63 -348
- package/dist/bin.js +8302 -149
- package/package.json +11 -13
- package/dist/_generated/commands.js +0 -282
- package/dist/_generated/help.js +0 -195
- package/dist/_generated/operations.js +0 -1529
- package/dist/runtime/auth.js +0 -95
- package/dist/runtime/envelope.js +0 -52
- package/dist/runtime/errors.js +0 -186
- package/dist/runtime/filters.js +0 -153
- package/dist/runtime/format.js +0 -143
- package/dist/runtime/http.js +0 -65
- package/dist/runtime/idempotency.js +0 -18
- package/dist/runtime/invoker.js +0 -387
- package/dist/runtime/io.js +0 -16
- package/dist/runtime/paginator.js +0 -146
- package/dist/runtime/trace.js +0 -99
- package/dist/runtime/tty.js +0 -51
- package/dist/runtime/verb_cmd.js +0 -70
- package/dist/runtime/verb_runner.js +0 -152
- package/dist/runtime/whoami_cache.js +0 -109
- package/dist/tools/api.js +0 -81
- package/dist/tools/completion.js +0 -116
- package/dist/tools/config.js +0 -123
- package/dist/tools/doctor.js +0 -183
- package/dist/tools/login.js +0 -99
- package/dist/tools/mcp.js +0 -141
- package/dist/tools/raw.js +0 -96
- package/dist/tools/schema.js +0 -58
- package/dist/tools/trace.js +0 -54
- package/dist/tools/welcome.js +0 -190
- package/dist/tools/whoami.js +0 -132
- package/dist/verbs/_spec.js +0 -15
- package/dist/verbs/bars_batch.js +0 -66
- package/dist/verbs/chart.js +0 -110
- package/dist/verbs/digest.js +0 -344
- package/dist/verbs/financials.js +0 -212
- package/dist/verbs/hot.js +0 -29
- package/dist/verbs/index.js +0 -57
- package/dist/verbs/lookup.js +0 -72
- package/dist/verbs/news.js +0 -67
- package/dist/verbs/quote.js +0 -53
- package/dist/verbs/scan.js +0 -42
- package/dist/verbs/search.js +0 -105
- package/dist/verbs/sentiment.js +0 -46
- package/dist/verbs/views.js +0 -83
- package/dist/version.js +0 -5
package/dist/verbs/lookup.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai lookup --text <q> [--limit N]` + MCP tool `lookup`.
|
|
3
|
-
*
|
|
4
|
-
* Curated verb: 解析中文名 / 代码 / 拼音 → canonical_code 列表。Agent 几乎
|
|
5
|
-
* 每个会话的第一步。底层调 raw OpenAPI op `semantic.find`。
|
|
6
|
-
*/
|
|
7
|
-
import { Command, Option } from "commander";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import { OPERATIONS } from "../_generated/operations.js";
|
|
10
|
-
import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
11
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
12
|
-
export function mapLookupResponse(raw) {
|
|
13
|
-
if (!Array.isArray(raw))
|
|
14
|
-
return [];
|
|
15
|
-
const out = [];
|
|
16
|
-
for (const item of raw) {
|
|
17
|
-
if (!item || typeof item !== "object")
|
|
18
|
-
continue;
|
|
19
|
-
const r = item;
|
|
20
|
-
if (typeof r.canonical_code !== "string")
|
|
21
|
-
continue;
|
|
22
|
-
out.push({
|
|
23
|
-
canonical_code: r.canonical_code,
|
|
24
|
-
name_cn: typeof r.name_cn === "string" ? r.name_cn : "",
|
|
25
|
-
ticker: typeof r.ticker === "string" ? r.ticker : "",
|
|
26
|
-
exchange: typeof r.exchange === "string" ? r.exchange : "",
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
export const lookupSpec = {
|
|
32
|
-
name: "lookup",
|
|
33
|
-
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.",
|
|
34
|
-
inputSchema: {
|
|
35
|
-
text: z
|
|
36
|
-
.string()
|
|
37
|
-
.min(1)
|
|
38
|
-
.max(50)
|
|
39
|
-
.describe("Search text: Chinese name (贵州茅台), code (600519), or pinyin (gzmt)"),
|
|
40
|
-
limit: z.number().int().min(1).max(30).default(10).describe("Max matches (1-30)"),
|
|
41
|
-
},
|
|
42
|
-
handler: async (args, ctx) => {
|
|
43
|
-
const op = OPERATIONS["semantic.find"];
|
|
44
|
-
if (!op)
|
|
45
|
-
throw new Error("semantic.find op missing from codegen");
|
|
46
|
-
const env = await callOp(op, { query: args.text, limit: args.limit }, ctx);
|
|
47
|
-
const matches = mapLookupResponse(env.data);
|
|
48
|
-
return { data: { matches, count: matches.length }, meta: env.meta };
|
|
49
|
-
},
|
|
50
|
-
backingOps: ["semantic.find"],
|
|
51
|
-
};
|
|
52
|
-
function clampLimit(raw, min = 1, max = 30) {
|
|
53
|
-
const n = Math.floor(Number(raw));
|
|
54
|
-
if (!Number.isFinite(n) || n < min)
|
|
55
|
-
return min;
|
|
56
|
-
if (n > max)
|
|
57
|
-
return max;
|
|
58
|
-
return n;
|
|
59
|
-
}
|
|
60
|
-
export function buildLookupCommand() {
|
|
61
|
-
const cmd = new Command(lookupSpec.name).description(lookupSpec.description);
|
|
62
|
-
cmd.addOption(new Option("--text <text>", "Search text (Chinese name / code / pinyin)")
|
|
63
|
-
.makeOptionMandatory(true));
|
|
64
|
-
cmd.addOption(new Option("--limit <n>", "Max matches (1-30)").default("10"));
|
|
65
|
-
cmd.action(async (opts) => {
|
|
66
|
-
if (!OPERATIONS["semantic.find"]) {
|
|
67
|
-
emitVerbError("internal_error", "semantic.find missing from codegen", undefined, 2);
|
|
68
|
-
}
|
|
69
|
-
await executeVerb(async (ctx) => lookupSpec.handler({ text: opts.text, limit: clampLimit(opts.limit) }, ctx));
|
|
70
|
-
});
|
|
71
|
-
return cmd;
|
|
72
|
-
}
|
package/dist/verbs/news.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai news ...` + MCP tool `news`.
|
|
3
|
-
*
|
|
4
|
-
* SUPPLEMENTARY breadth 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 } from "../runtime/verb_cmd.js";
|
|
10
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
11
|
-
export const newsSpec = {
|
|
12
|
-
name: "news",
|
|
13
|
-
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. With `query` → full-text search; without `query` → time-window feed.",
|
|
14
|
-
inputSchema: {
|
|
15
|
-
query: z
|
|
16
|
-
.string()
|
|
17
|
-
.optional()
|
|
18
|
-
.describe("Free-text query (Chinese or English); omit for time-window feed"),
|
|
19
|
-
hours: z
|
|
20
|
-
.number()
|
|
21
|
-
.int()
|
|
22
|
-
.min(1)
|
|
23
|
-
.max(168)
|
|
24
|
-
.default(24)
|
|
25
|
-
.describe("Lookback window in hours (default 24 — much narrower than views)"),
|
|
26
|
-
limit: z.number().int().min(1).max(100).default(20).describe("Max items"),
|
|
27
|
-
},
|
|
28
|
-
handler: async (args, ctx) => {
|
|
29
|
-
if (args.query) {
|
|
30
|
-
const op = OPERATIONS["news.search"];
|
|
31
|
-
if (!op)
|
|
32
|
-
throw new Error("news.search op missing");
|
|
33
|
-
return callOp(op, { query: args.query, since_hours: args.hours, limit: args.limit }, ctx);
|
|
34
|
-
}
|
|
35
|
-
const op = OPERATIONS["news.feed"];
|
|
36
|
-
if (!op)
|
|
37
|
-
throw new Error("news.feed op missing");
|
|
38
|
-
return callOp(op, {}, ctx);
|
|
39
|
-
},
|
|
40
|
-
backingOps: ["news.search", "news.feed"],
|
|
41
|
-
};
|
|
42
|
-
function clamp(raw, min, max, fallback) {
|
|
43
|
-
const n = Math.floor(Number(raw));
|
|
44
|
-
if (!Number.isFinite(n))
|
|
45
|
-
return fallback;
|
|
46
|
-
if (n < min)
|
|
47
|
-
return min;
|
|
48
|
-
if (n > max)
|
|
49
|
-
return max;
|
|
50
|
-
return n;
|
|
51
|
-
}
|
|
52
|
-
export function buildNewsCommand() {
|
|
53
|
-
const cmd = new Command(newsSpec.name).description(newsSpec.description);
|
|
54
|
-
cmd.addOption(new Option("--query <text>", "Free-text query; omit for time-window feed"));
|
|
55
|
-
cmd.addOption(new Option("--hours <n>", "Lookback window (1-168 hours)").default("24"));
|
|
56
|
-
cmd.addOption(new Option("--limit <n>", "Max items").default("20"));
|
|
57
|
-
cmd.action(async (opts) => {
|
|
58
|
-
const args = {
|
|
59
|
-
hours: clamp(opts.hours, 1, 168, 24),
|
|
60
|
-
limit: clamp(opts.limit, 1, 100, 20),
|
|
61
|
-
};
|
|
62
|
-
if (opts.query)
|
|
63
|
-
args.query = opts.query;
|
|
64
|
-
await executeVerb(async (ctx) => newsSpec.handler(args, ctx));
|
|
65
|
-
});
|
|
66
|
-
return cmd;
|
|
67
|
-
}
|
package/dist/verbs/quote.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai quote --codes A,B,C` + MCP tool `quote`.
|
|
3
|
-
*/
|
|
4
|
-
import { Command, Option } from "commander";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { OPERATIONS } from "../_generated/operations.js";
|
|
7
|
-
import { CallApiError } from "../runtime/errors.js";
|
|
8
|
-
import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
9
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
10
|
-
export const quoteSpec = {
|
|
11
|
-
name: "quote",
|
|
12
|
-
description: "Real-time quote for 1-200 A-share securities (last price, volume, change %, bid/ask). For >200 codes use `scan` instead.",
|
|
13
|
-
inputSchema: {
|
|
14
|
-
codes: z
|
|
15
|
-
.array(z.string().regex(/^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/))
|
|
16
|
-
.min(1)
|
|
17
|
-
.max(200)
|
|
18
|
-
.describe("Array of canonical codes (e.g. ['SSE:600519', 'SZSE:000001'])"),
|
|
19
|
-
include_l2: z
|
|
20
|
-
.boolean()
|
|
21
|
-
.optional()
|
|
22
|
-
.describe("Include L2 5-level order book (requires quote:l2 scope)"),
|
|
23
|
-
},
|
|
24
|
-
handler: async (args, ctx) => {
|
|
25
|
-
const op = OPERATIONS["quote"];
|
|
26
|
-
if (!op)
|
|
27
|
-
throw new Error("quote op missing from codegen");
|
|
28
|
-
return callOp(op, args, ctx);
|
|
29
|
-
},
|
|
30
|
-
backingOps: ["quote"],
|
|
31
|
-
};
|
|
32
|
-
export function buildQuoteCommand() {
|
|
33
|
-
const cmd = new Command(quoteSpec.name).description(quoteSpec.description);
|
|
34
|
-
cmd.addOption(new Option("--codes <csv>", "Canonical codes, comma-separated (e.g. SSE:600519,SZSE:000001)")
|
|
35
|
-
.makeOptionMandatory(true));
|
|
36
|
-
cmd.addOption(new Option("--include-l2", "Include L2 order book (requires quote:l2 scope)"));
|
|
37
|
-
cmd.action(async (opts) => {
|
|
38
|
-
const codes = opts.codes.split(",").map((s) => s.trim()).filter(Boolean);
|
|
39
|
-
if (codes.length === 0 || codes.length > 200) {
|
|
40
|
-
emitVerbError("invalid_args", `codes count ${codes.length} out of range; expected 1-200`, "Use `echopai scan` for full-market snapshot (>200 codes).", 1);
|
|
41
|
-
}
|
|
42
|
-
if (!OPERATIONS["quote"]) {
|
|
43
|
-
emitVerbError("internal_error", "quote op missing from codegen", undefined, 2);
|
|
44
|
-
}
|
|
45
|
-
const args = { codes };
|
|
46
|
-
if (opts.includeL2)
|
|
47
|
-
args.include_l2 = true;
|
|
48
|
-
await executeVerb(async (ctx) => quoteSpec.handler(args, ctx));
|
|
49
|
-
});
|
|
50
|
-
return cmd;
|
|
51
|
-
}
|
|
52
|
-
// Re-export CallApiError so handler call sites have a typed throw signature.
|
|
53
|
-
export { CallApiError };
|
package/dist/verbs/scan.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai scan` + MCP tool `scan`.
|
|
3
|
-
*/
|
|
4
|
-
import { Command, Option } from "commander";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { OPERATIONS } from "../_generated/operations.js";
|
|
7
|
-
import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
8
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
9
|
-
export const scanSpec = {
|
|
10
|
-
name: "scan",
|
|
11
|
-
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.",
|
|
12
|
-
inputSchema: {
|
|
13
|
-
exchange: z
|
|
14
|
-
.enum(["SSE", "SZSE", "BSE"])
|
|
15
|
-
.optional()
|
|
16
|
-
.describe("Filter by exchange (omit for all)"),
|
|
17
|
-
},
|
|
18
|
-
handler: async (args, ctx) => {
|
|
19
|
-
const op = OPERATIONS["quote.scan"];
|
|
20
|
-
if (!op)
|
|
21
|
-
throw new Error("quote.scan op missing");
|
|
22
|
-
const callArgs = {};
|
|
23
|
-
if (args.exchange)
|
|
24
|
-
callArgs.exchange = args.exchange;
|
|
25
|
-
return callOp(op, callArgs, ctx);
|
|
26
|
-
},
|
|
27
|
-
backingOps: ["quote.scan"],
|
|
28
|
-
};
|
|
29
|
-
export function buildScanCommand() {
|
|
30
|
-
const cmd = new Command(scanSpec.name).description(scanSpec.description);
|
|
31
|
-
cmd.addOption(new Option("--exchange <code>", "Filter by exchange").choices(["SSE", "SZSE", "BSE"]));
|
|
32
|
-
cmd.action(async (opts) => {
|
|
33
|
-
if (!OPERATIONS["quote.scan"]) {
|
|
34
|
-
emitVerbError("internal_error", "quote.scan missing", undefined, 2);
|
|
35
|
-
}
|
|
36
|
-
const args = {};
|
|
37
|
-
if (opts.exchange)
|
|
38
|
-
args.exchange = opts.exchange;
|
|
39
|
-
await executeVerb(async (ctx) => scanSpec.handler(args, ctx));
|
|
40
|
-
});
|
|
41
|
-
return cmd;
|
|
42
|
-
}
|
package/dist/verbs/search.js
DELETED
|
@@ -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
|
-
}
|
package/dist/verbs/sentiment.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `echopai sentiment [--scope <universe>]` + MCP tool `sentiment`.
|
|
3
|
-
*/
|
|
4
|
-
import { Command, Option } from "commander";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { OPERATIONS } from "../_generated/operations.js";
|
|
7
|
-
import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
8
|
-
import { callOp } from "../runtime/verb_runner.js";
|
|
9
|
-
const SCOPE_VALUES = [
|
|
10
|
-
"all_a",
|
|
11
|
-
"all_a_ex_st",
|
|
12
|
-
"main_board",
|
|
13
|
-
"star_market",
|
|
14
|
-
"chinext",
|
|
15
|
-
"bse",
|
|
16
|
-
];
|
|
17
|
-
export const sentimentSpec = {
|
|
18
|
-
name: "sentiment",
|
|
19
|
-
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).",
|
|
20
|
-
inputSchema: {
|
|
21
|
-
scope: z
|
|
22
|
-
.enum(SCOPE_VALUES)
|
|
23
|
-
.default("all_a_ex_st")
|
|
24
|
-
.describe("Universe filter"),
|
|
25
|
-
},
|
|
26
|
-
handler: async (args, ctx) => {
|
|
27
|
-
const op = OPERATIONS["sentiment.overview"];
|
|
28
|
-
if (!op)
|
|
29
|
-
throw new Error("sentiment.overview op missing");
|
|
30
|
-
return callOp(op, { scope: args.scope }, ctx);
|
|
31
|
-
},
|
|
32
|
-
backingOps: ["sentiment.overview"],
|
|
33
|
-
};
|
|
34
|
-
export function buildSentimentCommand() {
|
|
35
|
-
const cmd = new Command(sentimentSpec.name).description(sentimentSpec.description);
|
|
36
|
-
cmd.addOption(new Option("--scope <universe>", "Universe filter")
|
|
37
|
-
.choices([...SCOPE_VALUES])
|
|
38
|
-
.default("all_a_ex_st"));
|
|
39
|
-
cmd.action(async (opts) => {
|
|
40
|
-
if (!OPERATIONS["sentiment.overview"]) {
|
|
41
|
-
emitVerbError("internal_error", "sentiment.overview missing", undefined, 2);
|
|
42
|
-
}
|
|
43
|
-
await executeVerb(async (ctx) => sentimentSpec.handler({ scope: opts.scope }, ctx));
|
|
44
|
-
});
|
|
45
|
-
return cmd;
|
|
46
|
-
}
|
package/dist/verbs/views.js
DELETED
|
@@ -1,83 +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.",
|
|
14
|
-
inputSchema: {
|
|
15
|
-
code: z
|
|
16
|
-
.string()
|
|
17
|
-
.regex(/^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/)
|
|
18
|
-
.optional()
|
|
19
|
-
.describe("Filter by security canonical_code (e.g. SSE:600519)"),
|
|
20
|
-
analyst: z.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
|
|
21
|
-
institution: z.string().optional().describe("Broker / institution Chinese name"),
|
|
22
|
-
since_days: z
|
|
23
|
-
.number()
|
|
24
|
-
.int()
|
|
25
|
-
.min(1)
|
|
26
|
-
.max(90)
|
|
27
|
-
.default(7)
|
|
28
|
-
.describe("Lookback window in days (research time-horizon is longer than news)"),
|
|
29
|
-
limit: z.number().int().min(1).max(100).default(30).describe("Max items"),
|
|
30
|
-
},
|
|
31
|
-
handler: async (args, ctx) => {
|
|
32
|
-
const op = OPERATIONS["views.recent"];
|
|
33
|
-
if (!op)
|
|
34
|
-
throw new Error("views.recent op missing from codegen");
|
|
35
|
-
const callArgs = {
|
|
36
|
-
since_days: args.since_days,
|
|
37
|
-
limit: args.limit,
|
|
38
|
-
};
|
|
39
|
-
if (args.code)
|
|
40
|
-
callArgs.security = args.code;
|
|
41
|
-
if (args.analyst)
|
|
42
|
-
callArgs.analyst = args.analyst;
|
|
43
|
-
if (args.institution)
|
|
44
|
-
callArgs.institution = args.institution;
|
|
45
|
-
return callOp(op, callArgs, ctx);
|
|
46
|
-
},
|
|
47
|
-
backingOps: ["views.recent"],
|
|
48
|
-
};
|
|
49
|
-
function clamp(raw, min, max, fallback) {
|
|
50
|
-
const n = Math.floor(Number(raw));
|
|
51
|
-
if (!Number.isFinite(n))
|
|
52
|
-
return fallback;
|
|
53
|
-
if (n < min)
|
|
54
|
-
return min;
|
|
55
|
-
if (n > max)
|
|
56
|
-
return max;
|
|
57
|
-
return n;
|
|
58
|
-
}
|
|
59
|
-
export function buildViewsCommand() {
|
|
60
|
-
const cmd = new Command(viewsSpec.name).description(viewsSpec.description);
|
|
61
|
-
cmd.addOption(new Option("--code <canonical_code>", "Filter by security canonical_code"));
|
|
62
|
-
cmd.addOption(new Option("--analyst <name>", "Analyst Chinese name (exact / fuzzy)"));
|
|
63
|
-
cmd.addOption(new Option("--institution <name>", "Broker / institution Chinese name"));
|
|
64
|
-
cmd.addOption(new Option("--since-days <n>", "Lookback window (1-90 days)").default("7"));
|
|
65
|
-
cmd.addOption(new Option("--limit <n>", "Max items (1-100)").default("30"));
|
|
66
|
-
cmd.action(async (opts) => {
|
|
67
|
-
if (!OPERATIONS["views.recent"]) {
|
|
68
|
-
emitVerbError("internal_error", "views.recent missing from codegen", undefined, 2);
|
|
69
|
-
}
|
|
70
|
-
const args = {
|
|
71
|
-
since_days: clamp(opts.sinceDays, 1, 90, 7),
|
|
72
|
-
limit: clamp(opts.limit, 1, 100, 30),
|
|
73
|
-
};
|
|
74
|
-
if (opts.code)
|
|
75
|
-
args.code = opts.code;
|
|
76
|
-
if (opts.analyst)
|
|
77
|
-
args.analyst = opts.analyst;
|
|
78
|
-
if (opts.institution)
|
|
79
|
-
args.institution = opts.institution;
|
|
80
|
-
await executeVerb(async (ctx) => viewsSpec.handler(args, ctx));
|
|
81
|
-
});
|
|
82
|
-
return cmd;
|
|
83
|
-
}
|