echopai 2.5.0 → 2.6.1
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/dist/bin.js +266 -154
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/bin.ts
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
7
6
|
import { Command as Command31, Option as Option23 } from "commander";
|
|
8
7
|
|
|
9
8
|
// src/_generated/commands.ts
|
|
@@ -229,6 +228,66 @@ var OPERATIONS = {
|
|
|
229
228
|
additionalProperties: false
|
|
230
229
|
}
|
|
231
230
|
},
|
|
231
|
+
"announcements.search": {
|
|
232
|
+
cliKey: "announcements.search",
|
|
233
|
+
cliName: "announcements search",
|
|
234
|
+
method: "GET",
|
|
235
|
+
path: "/v1/announcements/search",
|
|
236
|
+
description: "公告混合搜索:名称 / 代码做结构化过滤 + 关键词 BM25 全文(ParadeDB jieba,覆盖 title / ai_summary / content_md 前 1500 字)。q 含 6 位代码或内嵌 A 股名 → 按代码精确过滤;残余或纯文本 → BM25 按相关度×时间衰减排序;纯代码/名称则按 published_at 倒序列出。需要 `announcements:read` scope。",
|
|
237
|
+
summary: "Hybrid search over A-share announcements",
|
|
238
|
+
positional: [],
|
|
239
|
+
outputDefault: "json",
|
|
240
|
+
pagination: "offset",
|
|
241
|
+
stream: false,
|
|
242
|
+
billable: true,
|
|
243
|
+
idempotencyRequired: false,
|
|
244
|
+
scopesAny: [
|
|
245
|
+
"announcements:read"
|
|
246
|
+
],
|
|
247
|
+
sideEffect: "read",
|
|
248
|
+
dryRunSupported: false,
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
q: {
|
|
253
|
+
type: "string",
|
|
254
|
+
minLength: 1,
|
|
255
|
+
maxLength: 100,
|
|
256
|
+
description: '名称 / 代码 / 关键词,可混合(如 "新华医疗 回购"、"600587"、"回购")。',
|
|
257
|
+
example: "新华医疗 回购"
|
|
258
|
+
},
|
|
259
|
+
type: {
|
|
260
|
+
type: "string",
|
|
261
|
+
maxLength: 64,
|
|
262
|
+
description: "分类 slug 过滤(与 /v1/announcements/feed 同集合)。"
|
|
263
|
+
},
|
|
264
|
+
since_days: {
|
|
265
|
+
type: "integer",
|
|
266
|
+
minimum: 1,
|
|
267
|
+
maximum: 1825,
|
|
268
|
+
default: 90,
|
|
269
|
+
description: "回看天数(最多 5 年)。"
|
|
270
|
+
},
|
|
271
|
+
limit: {
|
|
272
|
+
type: "integer",
|
|
273
|
+
minimum: 1,
|
|
274
|
+
maximum: 200,
|
|
275
|
+
default: 50,
|
|
276
|
+
description: "Max items per page."
|
|
277
|
+
},
|
|
278
|
+
offset: {
|
|
279
|
+
type: "integer",
|
|
280
|
+
minimum: 0,
|
|
281
|
+
default: 0,
|
|
282
|
+
description: "Pagination offset (items to skip)."
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
additionalProperties: false,
|
|
286
|
+
required: [
|
|
287
|
+
"q"
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
},
|
|
232
291
|
"announcements.stock": {
|
|
233
292
|
cliKey: "announcements.stock",
|
|
234
293
|
cliName: "announcements stock",
|
|
@@ -2694,6 +2753,10 @@ function buildCommandTree(program, dispatch) {
|
|
|
2694
2753
|
const cmd = noun.command("feed");
|
|
2695
2754
|
attachOperation(cmd, OPERATIONS["announcements.feed"], dispatch);
|
|
2696
2755
|
}
|
|
2756
|
+
{
|
|
2757
|
+
const cmd = noun.command("search");
|
|
2758
|
+
attachOperation(cmd, OPERATIONS["announcements.search"], dispatch);
|
|
2759
|
+
}
|
|
2697
2760
|
{
|
|
2698
2761
|
const cmd = noun.command("stock");
|
|
2699
2762
|
attachOperation(cmd, OPERATIONS["announcements.stock"], dispatch);
|
|
@@ -2993,6 +3056,137 @@ function buildCommandTree(program, dispatch) {
|
|
|
2993
3056
|
}
|
|
2994
3057
|
}
|
|
2995
3058
|
|
|
3059
|
+
// src/runtime/dist.ts
|
|
3060
|
+
import { spawnSync } from "node:child_process";
|
|
3061
|
+
import { createHash } from "node:crypto";
|
|
3062
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3063
|
+
var RELEASE_BASE = process.env.ECHOPAI_RELEASE_BASE?.replace(/\/+$/, "") || "https://downloads.echopai.com/echopai-cli-releases";
|
|
3064
|
+
var VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+(-\S+)?$/;
|
|
3065
|
+
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
3066
|
+
var FETCH_TIMEOUT_MS = 8000;
|
|
3067
|
+
function isStandaloneFromUrl(url) {
|
|
3068
|
+
return url.includes("$bunfs") || url.includes("~BUN");
|
|
3069
|
+
}
|
|
3070
|
+
function isStandaloneBinary() {
|
|
3071
|
+
return isStandaloneFromUrl(import.meta.url);
|
|
3072
|
+
}
|
|
3073
|
+
function computePlatformKey(i) {
|
|
3074
|
+
let arch = i.arch === "x64" || i.arch === "amd64" ? "x64" : i.arch === "arm64" ? "arm64" : null;
|
|
3075
|
+
if (!arch)
|
|
3076
|
+
return null;
|
|
3077
|
+
if (i.platform === "darwin") {
|
|
3078
|
+
if (arch === "x64" && i.isRosetta)
|
|
3079
|
+
arch = "arm64";
|
|
3080
|
+
return `darwin-${arch}`;
|
|
3081
|
+
}
|
|
3082
|
+
if (i.platform === "win32") {
|
|
3083
|
+
return arch === "x64" ? "windows-x64" : null;
|
|
3084
|
+
}
|
|
3085
|
+
if (i.platform === "linux") {
|
|
3086
|
+
if (arch === "x64") {
|
|
3087
|
+
if (i.isMusl)
|
|
3088
|
+
return "linux-x64-musl";
|
|
3089
|
+
return i.hasAvx2 ? "linux-x64" : "linux-x64-baseline";
|
|
3090
|
+
}
|
|
3091
|
+
return i.isMusl ? "linux-arm64-musl" : "linux-arm64";
|
|
3092
|
+
}
|
|
3093
|
+
return null;
|
|
3094
|
+
}
|
|
3095
|
+
function detectMusl() {
|
|
3096
|
+
return existsSync("/lib/libc.musl-x86_64.so.1") || existsSync("/lib/libc.musl-aarch64.so.1");
|
|
3097
|
+
}
|
|
3098
|
+
function detectAvx2() {
|
|
3099
|
+
try {
|
|
3100
|
+
return /\bavx2\b/.test(readFileSync("/proc/cpuinfo", "utf8"));
|
|
3101
|
+
} catch {
|
|
3102
|
+
return false;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
function detectRosetta() {
|
|
3106
|
+
try {
|
|
3107
|
+
const r = spawnSync("sysctl", ["-n", "sysctl.proc_translated"], {
|
|
3108
|
+
encoding: "utf8",
|
|
3109
|
+
timeout: 1000
|
|
3110
|
+
});
|
|
3111
|
+
return r.stdout.trim() === "1";
|
|
3112
|
+
} catch {
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
function detectPlatformKey() {
|
|
3117
|
+
return computePlatformKey({
|
|
3118
|
+
platform: process.platform,
|
|
3119
|
+
arch: process.arch,
|
|
3120
|
+
isMusl: process.platform === "linux" ? detectMusl() : false,
|
|
3121
|
+
hasAvx2: process.platform === "linux" && process.arch === "x64" ? detectAvx2() : true,
|
|
3122
|
+
isRosetta: process.platform === "darwin" && process.arch === "x64" ? detectRosetta() : false
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
function parseManifestChecksum(manifest, platform) {
|
|
3126
|
+
if (!manifest || typeof manifest !== "object")
|
|
3127
|
+
return null;
|
|
3128
|
+
const platforms = manifest.platforms;
|
|
3129
|
+
if (!platforms || typeof platforms !== "object")
|
|
3130
|
+
return null;
|
|
3131
|
+
const entry = platforms[platform];
|
|
3132
|
+
if (!entry || typeof entry !== "object")
|
|
3133
|
+
return null;
|
|
3134
|
+
const cs = entry.checksum;
|
|
3135
|
+
return typeof cs === "string" && SHA256_RE.test(cs) ? cs : null;
|
|
3136
|
+
}
|
|
3137
|
+
async function fetchText(url) {
|
|
3138
|
+
const ac = new AbortController;
|
|
3139
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
3140
|
+
try {
|
|
3141
|
+
const res = await fetch(url, { signal: ac.signal });
|
|
3142
|
+
if (!res.ok)
|
|
3143
|
+
return null;
|
|
3144
|
+
return await res.text();
|
|
3145
|
+
} catch {
|
|
3146
|
+
return null;
|
|
3147
|
+
} finally {
|
|
3148
|
+
clearTimeout(timer);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
async function resolveLatestFromCdn() {
|
|
3152
|
+
const txt = await fetchText(`${RELEASE_BASE}/latest`);
|
|
3153
|
+
if (txt == null)
|
|
3154
|
+
return null;
|
|
3155
|
+
const v = txt.trim();
|
|
3156
|
+
return VERSION_RE.test(v) ? v : null;
|
|
3157
|
+
}
|
|
3158
|
+
async function fetchManifest(version) {
|
|
3159
|
+
const txt = await fetchText(`${RELEASE_BASE}/${version}/manifest.json`);
|
|
3160
|
+
if (txt == null)
|
|
3161
|
+
return null;
|
|
3162
|
+
try {
|
|
3163
|
+
return JSON.parse(txt);
|
|
3164
|
+
} catch {
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
function binaryUrl(version, platform) {
|
|
3169
|
+
const name = platform.startsWith("windows-") ? "echopai.exe" : "echopai";
|
|
3170
|
+
return `${RELEASE_BASE}/${version}/${platform}/${name}`;
|
|
3171
|
+
}
|
|
3172
|
+
async function downloadAndVerify(url, expectedSha) {
|
|
3173
|
+
const ac = new AbortController;
|
|
3174
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS * 4);
|
|
3175
|
+
try {
|
|
3176
|
+
const res = await fetch(url, { signal: ac.signal });
|
|
3177
|
+
if (!res.ok)
|
|
3178
|
+
throw new Error(`下载失败 HTTP ${res.status}: ${url}`);
|
|
3179
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
3180
|
+
const actual = createHash("sha256").update(buf).digest("hex");
|
|
3181
|
+
if (actual !== expectedSha) {
|
|
3182
|
+
throw new Error(`校验和不匹配(期望 ${expectedSha},实际 ${actual})`);
|
|
3183
|
+
}
|
|
3184
|
+
return buf;
|
|
3185
|
+
} finally {
|
|
3186
|
+
clearTimeout(timer);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
2996
3190
|
// src/runtime/invoker.ts
|
|
2997
3191
|
import AjvPkg from "ajv";
|
|
2998
3192
|
import addFormatsPkg from "ajv-formats";
|
|
@@ -3499,9 +3693,9 @@ function resolveRequestId(serverHeader, clientGenerated) {
|
|
|
3499
3693
|
// src/runtime/trace.ts
|
|
3500
3694
|
import {
|
|
3501
3695
|
appendFileSync,
|
|
3502
|
-
existsSync as
|
|
3696
|
+
existsSync as existsSync3,
|
|
3503
3697
|
mkdirSync as mkdirSync2,
|
|
3504
|
-
readFileSync as
|
|
3698
|
+
readFileSync as readFileSync3,
|
|
3505
3699
|
renameSync,
|
|
3506
3700
|
statSync
|
|
3507
3701
|
} from "node:fs";
|
|
@@ -3522,7 +3716,7 @@ function appendTrace(record, opts) {
|
|
|
3522
3716
|
const maxBytes = opts?.ringMaxBytes ?? RING_MAX_BYTES;
|
|
3523
3717
|
try {
|
|
3524
3718
|
mkdirSync2(dirname(file), { recursive: true });
|
|
3525
|
-
if (
|
|
3719
|
+
if (existsSync3(file)) {
|
|
3526
3720
|
const size = statSync(file).size;
|
|
3527
3721
|
if (size >= maxBytes) {
|
|
3528
3722
|
renameSync(file, file + ".1");
|
|
@@ -3534,9 +3728,9 @@ function appendTrace(record, opts) {
|
|
|
3534
3728
|
}
|
|
3535
3729
|
function readNdjsonLines(file) {
|
|
3536
3730
|
try {
|
|
3537
|
-
if (!
|
|
3731
|
+
if (!existsSync3(file))
|
|
3538
3732
|
return [];
|
|
3539
|
-
const raw =
|
|
3733
|
+
const raw = readFileSync3(file, "utf-8");
|
|
3540
3734
|
const out = [];
|
|
3541
3735
|
for (const line of raw.split(`
|
|
3542
3736
|
`)) {
|
|
@@ -3760,12 +3954,12 @@ function renderError(env) {
|
|
|
3760
3954
|
}
|
|
3761
3955
|
|
|
3762
3956
|
// src/runtime/update_check.ts
|
|
3763
|
-
import { readFileSync as
|
|
3957
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
|
|
3764
3958
|
import os2 from "node:os";
|
|
3765
3959
|
import path2 from "node:path";
|
|
3766
3960
|
|
|
3767
3961
|
// src/version.ts
|
|
3768
|
-
var CLI_VERSION = "2.
|
|
3962
|
+
var CLI_VERSION = "2.6.1";
|
|
3769
3963
|
|
|
3770
3964
|
// src/runtime/update_check.ts
|
|
3771
3965
|
var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
|
|
@@ -3800,7 +3994,7 @@ function isNewer(latest, current) {
|
|
|
3800
3994
|
}
|
|
3801
3995
|
function readCachedUpdate() {
|
|
3802
3996
|
try {
|
|
3803
|
-
const raw =
|
|
3997
|
+
const raw = readFileSync4(UPDATE_CACHE_PATH, "utf-8");
|
|
3804
3998
|
const obj = JSON.parse(raw);
|
|
3805
3999
|
if (typeof obj.checked_at === "string" && typeof obj.current === "string" && typeof obj.latest === "string") {
|
|
3806
4000
|
return obj;
|
|
@@ -4215,139 +4409,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4215
4409
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4216
4410
|
import { Command as Command20, Option as Option18 } from "commander";
|
|
4217
4411
|
|
|
4218
|
-
// src/runtime/dist.ts
|
|
4219
|
-
import { spawnSync } from "node:child_process";
|
|
4220
|
-
import { createHash } from "node:crypto";
|
|
4221
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
4222
|
-
import { fileURLToPath } from "node:url";
|
|
4223
|
-
var RELEASE_BASE = process.env.ECHOPAI_RELEASE_BASE?.replace(/\/+$/, "") || "https://downloads.echopai.com/echopai-cli-releases";
|
|
4224
|
-
var VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+(-\S+)?$/;
|
|
4225
|
-
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
4226
|
-
var FETCH_TIMEOUT_MS = 8000;
|
|
4227
|
-
function isStandaloneBinary() {
|
|
4228
|
-
try {
|
|
4229
|
-
return !existsSync3(fileURLToPath(import.meta.url));
|
|
4230
|
-
} catch {
|
|
4231
|
-
return true;
|
|
4232
|
-
}
|
|
4233
|
-
}
|
|
4234
|
-
function computePlatformKey(i) {
|
|
4235
|
-
let arch = i.arch === "x64" || i.arch === "amd64" ? "x64" : i.arch === "arm64" ? "arm64" : null;
|
|
4236
|
-
if (!arch)
|
|
4237
|
-
return null;
|
|
4238
|
-
if (i.platform === "darwin") {
|
|
4239
|
-
if (arch === "x64" && i.isRosetta)
|
|
4240
|
-
arch = "arm64";
|
|
4241
|
-
return `darwin-${arch}`;
|
|
4242
|
-
}
|
|
4243
|
-
if (i.platform === "win32") {
|
|
4244
|
-
return arch === "x64" ? "windows-x64" : null;
|
|
4245
|
-
}
|
|
4246
|
-
if (i.platform === "linux") {
|
|
4247
|
-
if (arch === "x64") {
|
|
4248
|
-
if (i.isMusl)
|
|
4249
|
-
return "linux-x64-musl";
|
|
4250
|
-
return i.hasAvx2 ? "linux-x64" : "linux-x64-baseline";
|
|
4251
|
-
}
|
|
4252
|
-
return i.isMusl ? "linux-arm64-musl" : "linux-arm64";
|
|
4253
|
-
}
|
|
4254
|
-
return null;
|
|
4255
|
-
}
|
|
4256
|
-
function detectMusl() {
|
|
4257
|
-
return existsSync3("/lib/libc.musl-x86_64.so.1") || existsSync3("/lib/libc.musl-aarch64.so.1");
|
|
4258
|
-
}
|
|
4259
|
-
function detectAvx2() {
|
|
4260
|
-
try {
|
|
4261
|
-
return /\bavx2\b/.test(readFileSync4("/proc/cpuinfo", "utf8"));
|
|
4262
|
-
} catch {
|
|
4263
|
-
return false;
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
function detectRosetta() {
|
|
4267
|
-
try {
|
|
4268
|
-
const r = spawnSync("sysctl", ["-n", "sysctl.proc_translated"], {
|
|
4269
|
-
encoding: "utf8",
|
|
4270
|
-
timeout: 1000
|
|
4271
|
-
});
|
|
4272
|
-
return r.stdout.trim() === "1";
|
|
4273
|
-
} catch {
|
|
4274
|
-
return false;
|
|
4275
|
-
}
|
|
4276
|
-
}
|
|
4277
|
-
function detectPlatformKey() {
|
|
4278
|
-
return computePlatformKey({
|
|
4279
|
-
platform: process.platform,
|
|
4280
|
-
arch: process.arch,
|
|
4281
|
-
isMusl: process.platform === "linux" ? detectMusl() : false,
|
|
4282
|
-
hasAvx2: process.platform === "linux" && process.arch === "x64" ? detectAvx2() : true,
|
|
4283
|
-
isRosetta: process.platform === "darwin" && process.arch === "x64" ? detectRosetta() : false
|
|
4284
|
-
});
|
|
4285
|
-
}
|
|
4286
|
-
function parseManifestChecksum(manifest, platform) {
|
|
4287
|
-
if (!manifest || typeof manifest !== "object")
|
|
4288
|
-
return null;
|
|
4289
|
-
const platforms = manifest.platforms;
|
|
4290
|
-
if (!platforms || typeof platforms !== "object")
|
|
4291
|
-
return null;
|
|
4292
|
-
const entry = platforms[platform];
|
|
4293
|
-
if (!entry || typeof entry !== "object")
|
|
4294
|
-
return null;
|
|
4295
|
-
const cs = entry.checksum;
|
|
4296
|
-
return typeof cs === "string" && SHA256_RE.test(cs) ? cs : null;
|
|
4297
|
-
}
|
|
4298
|
-
async function fetchText(url) {
|
|
4299
|
-
const ac = new AbortController;
|
|
4300
|
-
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
4301
|
-
try {
|
|
4302
|
-
const res = await fetch(url, { signal: ac.signal });
|
|
4303
|
-
if (!res.ok)
|
|
4304
|
-
return null;
|
|
4305
|
-
return await res.text();
|
|
4306
|
-
} catch {
|
|
4307
|
-
return null;
|
|
4308
|
-
} finally {
|
|
4309
|
-
clearTimeout(timer);
|
|
4310
|
-
}
|
|
4311
|
-
}
|
|
4312
|
-
async function resolveLatestFromCdn() {
|
|
4313
|
-
const txt = await fetchText(`${RELEASE_BASE}/latest`);
|
|
4314
|
-
if (txt == null)
|
|
4315
|
-
return null;
|
|
4316
|
-
const v = txt.trim();
|
|
4317
|
-
return VERSION_RE.test(v) ? v : null;
|
|
4318
|
-
}
|
|
4319
|
-
async function fetchManifest(version) {
|
|
4320
|
-
const txt = await fetchText(`${RELEASE_BASE}/${version}/manifest.json`);
|
|
4321
|
-
if (txt == null)
|
|
4322
|
-
return null;
|
|
4323
|
-
try {
|
|
4324
|
-
return JSON.parse(txt);
|
|
4325
|
-
} catch {
|
|
4326
|
-
return null;
|
|
4327
|
-
}
|
|
4328
|
-
}
|
|
4329
|
-
function binaryUrl(version, platform) {
|
|
4330
|
-
const name = platform.startsWith("windows-") ? "echopai.exe" : "echopai";
|
|
4331
|
-
return `${RELEASE_BASE}/${version}/${platform}/${name}`;
|
|
4332
|
-
}
|
|
4333
|
-
async function downloadAndVerify(url, expectedSha) {
|
|
4334
|
-
const ac = new AbortController;
|
|
4335
|
-
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS * 4);
|
|
4336
|
-
try {
|
|
4337
|
-
const res = await fetch(url, { signal: ac.signal });
|
|
4338
|
-
if (!res.ok)
|
|
4339
|
-
throw new Error(`下载失败 HTTP ${res.status}: ${url}`);
|
|
4340
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
4341
|
-
const actual = createHash("sha256").update(buf).digest("hex");
|
|
4342
|
-
if (actual !== expectedSha) {
|
|
4343
|
-
throw new Error(`校验和不匹配(期望 ${expectedSha},实际 ${actual})`);
|
|
4344
|
-
}
|
|
4345
|
-
return buf;
|
|
4346
|
-
} finally {
|
|
4347
|
-
clearTimeout(timer);
|
|
4348
|
-
}
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
4412
|
// src/runtime/whoami_cache.ts
|
|
4352
4413
|
var DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
4353
4414
|
var cache = null;
|
|
@@ -4592,6 +4653,32 @@ function buildQueryString3(params, method) {
|
|
|
4592
4653
|
var CODE_RE = /^(SSE|SZSE|BSE|SH|SZ|BJ):[0-9]{6}$/;
|
|
4593
4654
|
var ISO_DATETIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/;
|
|
4594
4655
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4656
|
+
var announcementsSearchSpec = {
|
|
4657
|
+
name: "announcements_search",
|
|
4658
|
+
description: 'Hybrid search over A-share announcements (cninfo): mix Chinese company name / 6-digit code / free-text keyword in one query. Name or code → structured filter by stock; keyword → BM25 full-text (ParadeDB jieba over title + ai_summary + first 1500 chars of body), ranked by relevance × recency. Use when you DON\'T already have a canonical_code — e.g. "新华医疗 回购", "600587", or just "回购". For a known stock\'s full disclosure history prefer `announcements_stock`; for the market-wide recent feed prefer `announcements_feed`.',
|
|
4659
|
+
inputSchema: {
|
|
4660
|
+
q: z.string().min(1).max(100).describe('名称 / 代码 / 关键词,可混合(如 "新华医疗 回购"、"600587"、"回购")'),
|
|
4661
|
+
type: z.string().max(64).optional().describe("分类 slug 过滤(与 feed 同集合)"),
|
|
4662
|
+
since_days: z.number().int().min(1).max(1825).default(90).describe("回看天数(1-1825,默认 90)"),
|
|
4663
|
+
limit: z.number().int().min(1).max(200).default(50).describe("Max items (1-200, default 50)"),
|
|
4664
|
+
offset: z.number().int().min(0).default(0).describe("Pagination offset")
|
|
4665
|
+
},
|
|
4666
|
+
handler: async (args, ctx) => {
|
|
4667
|
+
const op = OPERATIONS["announcements.search"];
|
|
4668
|
+
if (!op)
|
|
4669
|
+
throw new Error("announcements.search op missing");
|
|
4670
|
+
const callArgs = {
|
|
4671
|
+
q: args.q,
|
|
4672
|
+
since_days: args.since_days,
|
|
4673
|
+
limit: args.limit,
|
|
4674
|
+
offset: args.offset
|
|
4675
|
+
};
|
|
4676
|
+
if (args.type)
|
|
4677
|
+
callArgs.type = args.type;
|
|
4678
|
+
return callOp(op, callArgs, ctx);
|
|
4679
|
+
},
|
|
4680
|
+
backingOps: ["announcements.search"]
|
|
4681
|
+
};
|
|
4595
4682
|
var announcementsFeedSpec = {
|
|
4596
4683
|
name: "announcements_feed",
|
|
4597
4684
|
description: "Recent A-share announcement feed (cninfo source, sorted by published_at desc). Filter by `type` slug (e.g. annual_report / equity_change / restructuring) or `since` lower bound. Use when surveying market-wide disclosures over a recent window — for a specific stock prefer `announcements_stock`.",
|
|
@@ -4668,7 +4755,27 @@ function clampInt(raw, min, max, fallback) {
|
|
|
4668
4755
|
return n;
|
|
4669
4756
|
}
|
|
4670
4757
|
function buildAnnouncementsCommand() {
|
|
4671
|
-
const cmd = new Command3("announcements").description("A-share announcements (cninfo) — recent feed, per-stock history, single-item detail.");
|
|
4758
|
+
const cmd = new Command3("announcements").description("A-share announcements (cninfo) — hybrid search, recent feed, per-stock history, single-item detail.");
|
|
4759
|
+
const search = cmd.command("search").description(announcementsSearchSpec.description);
|
|
4760
|
+
search.addOption(new Option2("--q <text>", "名称 / 代码 / 关键词,可混合").makeOptionMandatory(true));
|
|
4761
|
+
search.addOption(new Option2("--type <slug>", "Filter by 分类 slug"));
|
|
4762
|
+
search.addOption(new Option2("--since-days <n>", "Lookback window in days (1-1825)").default("90"));
|
|
4763
|
+
search.addOption(new Option2("--limit <n>", "Max items (1-200)").default("50"));
|
|
4764
|
+
search.addOption(new Option2("--offset <n>", "Pagination offset").default("0"));
|
|
4765
|
+
search.action(async (opts) => {
|
|
4766
|
+
if (!OPERATIONS["announcements.search"]) {
|
|
4767
|
+
emitVerbError("internal_error", "announcements.search missing", undefined, 2);
|
|
4768
|
+
}
|
|
4769
|
+
const args = {
|
|
4770
|
+
q: opts.q,
|
|
4771
|
+
since_days: clampInt(opts.sinceDays, 1, 1825, 90),
|
|
4772
|
+
limit: clampInt(opts.limit, 1, 200, 50),
|
|
4773
|
+
offset: Math.max(0, Math.floor(Number(opts.offset)) || 0)
|
|
4774
|
+
};
|
|
4775
|
+
if (opts.type)
|
|
4776
|
+
args.type = opts.type;
|
|
4777
|
+
await executeVerb(async (ctx) => announcementsSearchSpec.handler(args, ctx));
|
|
4778
|
+
});
|
|
4672
4779
|
const feed = cmd.command("feed").description(announcementsFeedSpec.description);
|
|
4673
4780
|
feed.addOption(new Option2("--type <slug>", "Filter by 分类 slug (annual_report, equity_change, ...)"));
|
|
4674
4781
|
feed.addOption(new Option2("--since <ISO-datetime>", "Lower bound published_at (RFC3339)"));
|
|
@@ -6209,11 +6316,11 @@ import { Command as Command17, Option as Option15 } from "commander";
|
|
|
6209
6316
|
import { z as z14 } from "zod";
|
|
6210
6317
|
var searchSpec = {
|
|
6211
6318
|
name: "search",
|
|
6212
|
-
description: "
|
|
6319
|
+
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.",
|
|
6213
6320
|
inputSchema: {
|
|
6214
6321
|
q: z14.string().min(1).max(200).describe("Free-text query (Chinese or English)"),
|
|
6215
6322
|
type: z14.enum(["news", "views", "all"]).default("all").describe("Search scope (views weighted higher in 'all')"),
|
|
6216
|
-
mode: z14.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid =
|
|
6323
|
+
mode: z14.enum(["hybrid", "exact"]).default("hybrid").describe("hybrid = entity + concept-graph + BM25 fused; exact = BM25 keyword only"),
|
|
6217
6324
|
hours: z14.number().int().min(1).max(720).optional().describe("Lookback window in hours; omit for no time limit"),
|
|
6218
6325
|
limit: z14.number().int().min(1).max(50).default(20).describe("Max items returned")
|
|
6219
6326
|
},
|
|
@@ -6596,6 +6703,7 @@ var ALL_VERB_SPECS = [
|
|
|
6596
6703
|
viewsSpec,
|
|
6597
6704
|
reportSpec,
|
|
6598
6705
|
newsSpec,
|
|
6706
|
+
announcementsSearchSpec,
|
|
6599
6707
|
announcementsFeedSpec,
|
|
6600
6708
|
announcementsStockSpec,
|
|
6601
6709
|
announcementsDetailSpec,
|
|
@@ -7442,6 +7550,11 @@ var HELP = {
|
|
|
7442
7550
|
description: "A 股公告 feed,按 published_at 降序、同日按 cninfo source_id 降序。可按 type / since 过滤。需要 `announcements:read` scope。",
|
|
7443
7551
|
example: "echopai announcements feed"
|
|
7444
7552
|
},
|
|
7553
|
+
"announcements.search": {
|
|
7554
|
+
summary: "Hybrid search over A-share announcements",
|
|
7555
|
+
description: "公告混合搜索:名称 / 代码做结构化过滤 + 关键词 BM25 全文(ParadeDB jieba,覆盖 title / ai_summary / content_md 前 1500 字)。q 含 6 位代码或内嵌 A 股名 → 按代码精确过滤;残余或纯文本 → BM25 按相关度×时间衰减排序;纯代码/名称则按 published_at 倒序列出。需要 `announcements:read` scope。",
|
|
7556
|
+
example: "echopai announcements search --q 新华医疗 回购"
|
|
7557
|
+
},
|
|
7445
7558
|
"announcements.stock": {
|
|
7446
7559
|
summary: "List announcements for a specific A-share security",
|
|
7447
7560
|
description: "指定股票代码的公告列表(含历史窗口)。代码接受 6 位纯数字或 SSE:600519 / SZSE:000001 / BSE:430000 形式。需要 `announcements:read` scope。",
|
|
@@ -8279,15 +8392,14 @@ if (process.env._ECHOPAI_UPDATE_WORKER === "1") {
|
|
|
8279
8392
|
try {
|
|
8280
8393
|
const optedOut = process.env.CI || process.env.ECHOPAI_DISABLE_UPDATE_CHECK || !process.stderr.isTTY;
|
|
8281
8394
|
if (!optedOut && !process.env._ECHOPAI_UPDATE_WORKER) {
|
|
8282
|
-
let
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8395
|
+
let reexecArgs = [];
|
|
8396
|
+
if (!isStandaloneBinary()) {
|
|
8397
|
+
try {
|
|
8398
|
+
reexecArgs = [fileURLToPath(import.meta.url)];
|
|
8399
|
+
} catch {
|
|
8400
|
+
reexecArgs = [];
|
|
8401
|
+
}
|
|
8289
8402
|
}
|
|
8290
|
-
const reexecArgs = scriptPath ? [scriptPath] : [];
|
|
8291
8403
|
const child = spawn(process.execPath, reexecArgs, {
|
|
8292
8404
|
detached: true,
|
|
8293
8405
|
stdio: "ignore",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
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",
|