@morehao/dsh-github-trending 0.1.0 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ > 0.1.1 从未发布到 npm(tag 推上去后 CI 在 `npm ci` 就失败),0.1.2 是它的第一次真实发布。
6
+
7
+ ### 修复
8
+
9
+ - lockfile 里 57 个 `resolved` 指向 `registry.npmmirror.com`,npm 11 在 CI 拒绝拉取(`EALLOWREMOTE`);统一为 `registry.npmjs.org`,并加 `scripts/check-lockfile.mjs`(`npm run check` 会跑),把这类只在发版暴露的错误前移到 PR。
10
+
11
+ ### 发布链路
12
+
13
+ - 新增 tag 触发的发布:`.github/workflows/release.yml`(OIDC Trusted Publishing,无 NPM_TOKEN)+ `scripts/ci-publish.sh`(版本守卫 / 幂等 / 失败回读后重试)+ `scripts/release-notes.mjs`(只投影注记 tag 正文)+ `docs/publishing.md`。
14
+ - `check.yml` 在 PR/main 上跑 `npm run check` 与 AC 机械检查。
15
+
16
+ ## 0.1.1
17
+
18
+ 分发方式变更:核心二进制不再走 npm 平台分包,改为 **GitHub Release 下载 + sha256 校验 + 本地缓存**(2026-10-30 决策 B)。
19
+
20
+ ### 变更
21
+
22
+ - **获取核心**:`TRENDING_DIGEST_CORE` / 设置里的 `corePath`(覆盖,仅排障)→ 缓存 `$DSH_HOME/github-trending/core/v<version>/` → 按 `os/arch` 下载 `trendradar_<goos>_<goarch>`,用同目录 `checksums.txt` 校验(流式写入 + 原子 rename + 0755),失败不留半个文件;缓存命中后完全离线。
23
+ - **版本来源**:`package.json` 的 `dsh.core.{repo,version}`;`scripts/sync-contract.mjs --ref vX.Y.Z` 会同步写入,保证「契约来源 tag」与「下载的核心版本」是同一个值。
24
+ - **新增配置**:`coreAutoDownload`(默认 `true`);关掉后只报 `CORE_NOT_DOWNLOADED`,由设置页显式下载。
25
+ - **设置页**:显示 Release 版本 / 资产名 / 缓存状态 / 下载 URL,并提供「下载核心 / 重新下载」按钮(`POST /core/download`)。
26
+ - **新增错误码**:`CORE_NOT_DOWNLOADED`(未下载,附下载地址)、`CORE_CHECKSUM_MISMATCH`(校验失败且已丢弃);`CORE_UNAVAILABLE` 现在覆盖 404(版本未发布)、网络失败与平台不在矩阵。
27
+ - **移除**:`optionalDependencies` 里的 6 个 `@morehao/trendradar-core-*`(从未发布,安装时只是 6 次无效探测)。
28
+
29
+ ### 修复(实测 v0.1.0 Release 后发现)
30
+
31
+ - Windows 资产名带 `.exe`(goreleaser `formats: [binary]` 保留后缀):`assetName()` 按平台补后缀,避免 Windows 上下载 404。
32
+
33
+ ### 测试
34
+
35
+ - 新增 `test/release.test.mjs`(命名映射 / 下载校验 / 缓存复用 / 篡改重下 / 校验失败无残留 / 404 提示 / force)与 `test/core-status.test.mjs`(未下载 / 已缓存 / 覆盖路径三种状态)。
36
+ - AC 脚本 AC-P-11 判据改为「环境变量优先 / 未设走 Release」,全套 71 条测试、11 条 AC 机械检查通过。
37
+
3
38
  ## 0.1.0
4
39
 
5
40
  首个可用版本:插件侧完整落地,内容仍全部由 `trendradar` 核心产出。
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > GitHub 趋势日报的 **DSH 入口**:每天定时 spawn 本地 [`trendradar`](https://github.com/morehao/trendradar) 核心,抓取 Trending、生成中文简介与当日总结,提交到你自己的仓库并自动合并 PR。
4
4
 
5
- **状态:0.1.0。** 插件侧(配置、调度、核心调用、状态与界面、两个工具)已完成;核心的平台二进制包(`@morehao/trendradar-core-*`)尚未发布,安装后运行会明确报 `CORE_UNAVAILABLE` 并给出修复指引——不会写空文件,也不会退回旧内容。
5
+ **状态:0.1.1。** 插件侧(配置、调度、核心调用、状态与界面、两个工具)已完成。核心二进制走 **GitHub Release**:首次运行按 `os/arch` 下载、用 `checksums.txt` 校验 sha256、缓存到插件数据目录,之后离线复用。核心仓库打 tag 出资产之前,运行会明确报 `CORE_NOT_DOWNLOADED` / `CORE_UNAVAILABLE` 并给出下载地址——不会写空文件,也不会退回旧内容。
6
6
 
7
7
  ## 它做什么
8
8
 
@@ -35,11 +35,12 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
35
35
  - 一个本地 **git 仓库**(提交目标)与可写的远端
36
36
  - 模型:provider + 模型 ID + 密钥(或用宿主已配置的 provider;OAuth 类凭据不需要密钥)
37
37
  - Git 与 GitHub 凭据(PAT / Device Flow / 本机 `gh`),仅 push 与开 PR 需要
38
- - 核心二进制:随插件通过 `optionalDependencies` 获取的平台包(`@morehao/trendradar-core-<os>-<cpu>`);开发/排障时可用环境变量 `TRENDING_DIGEST_CORE` 指向一个可执行的 `trendradar`
38
+ - 核心二进制:首次运行从 `github.com/morehao/trendradar/releases` 下载(按 `os/arch`,sha256 校验后缓存);之后离线复用。开发/排障时可用 `TRENDING_DIGEST_CORE`(或设置里的「核心路径」)指向一个可执行的 `trendradar`,此时完全不下载
39
39
 
40
40
  **访问哪里**
41
41
 
42
42
  - 本地核心进程(一次性的 `run` 子进程)
43
+ - `github.com/morehao/trendradar/releases`:仅首次运行(或点「下载核心」)时下载二进制与其 `checksums.txt`;之后不再联网
43
44
  - 你配置的模型端点(由**核心**发起,插件不代发)
44
45
  - `api.github.com`(由**核心**发起 push/PR;插件本身不发 GitHub 请求)
45
46
  - 本机 `127.0.0.1` 上的临时回环代理(仅当宿主 provider 的凭据不可导出时,端口随机、一次性 token、运行结束即关闭)
@@ -72,7 +73,8 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
72
73
  | `trending.since` / `languages` / `topN` | `daily` / `all,typescript,python,go,rust` / `25` | 实际支持范围由核心决定 |
73
74
  | `output.dir` | `trending` | 仓库内相对目录 |
74
75
  | `schedule.enabled` / `time` / `timezone` | `true` / `08:30:00` / `Asia/Shanghai` | 插件自己的 60 s tick 驱动 |
75
- | `corePath` | 空 | 等价于 `TRENDING_DIGEST_CORE`(仅开发/排障) |
76
+ | `corePath` | 空 | 等价于 `TRENDING_DIGEST_CORE`(仅开发/排障;设了就完全不下载) |
77
+ | `coreAutoDownload` | `true` | 缓存缺失时是否自动下载核心;关掉后只报 `CORE_NOT_DOWNLOADED`,由你在设置页点「下载核心」 |
76
78
 
77
79
  ## 两个工具
78
80
 
@@ -87,7 +89,9 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
87
89
 
88
90
  | 码 | 含义 | 下一步 |
89
91
  |---|---|---|
90
- | `CORE_UNAVAILABLE` | 找不到核心(平台包缺失 / `TRENDING_DIGEST_CORE` 无效) | 重装插件,或指向可执行的 `trendradar` |
92
+ | `CORE_NOT_DOWNLOADED` | 核心还没下载(缓存为空,且自动下载被关或尚未触发) | 设置页点「下载核心」,或指向可执行的 `trendradar` |
93
+ | `CORE_CHECKSUM_MISMATCH` | 下载内容的 sha256 与 `checksums.txt` 不符(已被丢弃) | 重试;仍失败则手动下载放到缓存目录(代理可能改写内容) |
94
+ | `CORE_UNAVAILABLE` | 下载失败 / 版本还没发布(404)/ 平台不在矩阵 / 覆盖路径无效 | 按提示里的 URL 与发布顺序处理 |
91
95
  | `CORE_INCOMPATIBLE` | 核心 `ipcSchema`/`digestSchema` 与插件不一致 | 升级插件(含平台包)到同一版本 |
92
96
  | `CORE_TIMEOUT` / `CORE_CANCELLED` | 超时 / 用户取消 | 不提交任何状态,也留不下半成品 |
93
97
  | `IPC_INVALID` | 核心 stdout 不是约定的结果信封 | 升级插件与核心 |
@@ -105,15 +109,16 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
105
109
  - 首次可在设置页「从本机 CLI 导入」一次性导入非敏感字段;**密钥不导入**
106
110
  - 别让两套定时同时开着:内容侧靠 `contentHash` 与 PR 复用兜底,模型消耗无法兜底
107
111
 
108
- ## 已知偏差(0.1.0)
112
+ ## 已知偏差(0.1.1)
109
113
 
110
114
  1. **核心尚无 stdin 信封**:核心当前的 `run` 只接受 `--config <path> --json`,插件据此传配置;`ok/startedAt/error.code` 形状的 `RunResult` 已兼容,核心补上后插件无需改。
111
115
  2. **密钥传输**:POSIX 上配置经 **FIFO** 交给核心(文件系统里不出现明文);Windows 无 FIFO,退化为 0600 临时文件并在读完后立即删除。
112
116
  3. **GitHub token** 经子进程环境变量传给核心(核心只认 `GITHUB_TOKEN`/`GH_TOKEN`);不进 argv、不进配置、不进日志。
113
117
  4. **调度**用插件自己的 60 s tick(宿主 `schedule` 是会话本地提醒,覆盖不到无人值守场景)。
114
- 5. **平台包未发布**:`@morehao/trendradar-core-<os>-<cpu>` 404 期间,运行会明确报 `CORE_UNAVAILABLE`;发布顺序是先核心包、后插件。
118
+ 5. **二进制改为下载**(2026-10-30 决策 B):不再随包发 npm 平台分包。首次运行需要能访问 GitHub Release;断网环境请用 `TRENDING_DIGEST_CORE` 指向本地核心。核心版本由 `package.json` 的 `dsh.core.version` 固定(`sync-contract --ref vX.Y.Z` 会同步写入)。
119
+ 6. **核心尚未打 tag**:`trendradar` 仓库还没有 Release,因此当前下载会得到「还没有发布」的明确提示;打上 `v0.1.0` 后无需改插件。
115
120
 
116
- ## 开发
121
+ ## 开发与发版
117
122
 
118
123
  ```bash
119
124
  npm install
@@ -121,7 +126,10 @@ npm run check # typecheck + 契约校验 + 构建 + 全部测试
121
126
  node scripts/ac/run.mjs # AC-P-01…13 的机械部分
122
127
  ```
123
128
 
124
- - 契约快照:`contract/`(schema + fixtures + `UPSTREAM.json`),由 `node scripts/sync-contract.mjs --ref <sha>` 从核心仓库同步;`npm run check:contract` 校验哈希与生成类型
129
+ 发版走 **tag 触发的 CI**(Trusted Publishing / OIDC,不需要 npm token):合并 PR → 在 `main` 打注记 tag `vX.Y.Z` → CI 发 npm 并用注记正文建 Release。细节见 [docs/publishing.md](docs/publishing.md)。
130
+
131
+ - 契约快照:`contract/`(schema + fixtures + `UPSTREAM.json`),由 `node scripts/sync-contract.mjs --ref <sha|vX.Y.Z>` 从核心仓库同步;ref 是 tag 时会同时把版本写进 `package.json` 的 `dsh.core.version`;`npm run check:contract` 校验哈希与生成类型
132
+ - 核心下载与缓存:`src/core/release.ts`(`ensureCore`),缓存位置 `$DSH_HOME/github-trending/core/v<version>/`
125
133
  - 宿主半 `src/`(tsup → `lib/`),客户端半 `client/`(esbuild → `client.js`,`window.__ModuleLoader__.load`)
126
134
 
127
135
  ## 许可
package/client.js CHANGED
@@ -61,10 +61,15 @@ var DICT = {
61
61
  "core.ready": "可用",
62
62
  "core.unavailable": "不可用",
63
63
  "core.incompatible": "版本不兼容",
64
- "core.package": "平台包",
64
+ "core.cached": "已缓存",
65
+ "core.notDownloaded": "未下载",
66
+ "core.download": "下载核心",
67
+ "core.downloading": "下载中…",
68
+ "core.redownload": "重新下载",
69
+ "core.downloaded": "核心已下载并校验通过",
65
70
  "core.installed": "已安装",
66
71
  "core.missing": "未安装",
67
- "core.order": "解析顺序:TRENDING_DIGEST_CORE(仅开发/排障)→ 平台包;都没有时报 CORE_UNAVAILABLE。",
72
+ "core.order": "解析顺序:TRENDING_DIGEST_CORE(仅开发/排障)→ Release 资产(缓存命中直接用,否则下载并校验);都不可用时报 CORE_UNAVAILABLE。",
68
73
  "coexist.title": "检测到本机另有 CLI",
69
74
  "coexist.hint": "插件仍使用自带核心,不切换、不接管它的定时。",
70
75
  "card.model.title": "模型",
@@ -201,10 +206,15 @@ var DICT = {
201
206
  "core.ready": "ready",
202
207
  "core.unavailable": "unavailable",
203
208
  "core.incompatible": "incompatible",
204
- "core.package": "Platform package",
205
209
  "core.installed": "installed",
206
210
  "core.missing": "missing",
207
- "core.order": "Resolution order: TRENDING_DIGEST_CORE (dev/troubleshooting only) → platform package; otherwise CORE_UNAVAILABLE.",
211
+ "core.cached": "cached",
212
+ "core.notDownloaded": "not downloaded",
213
+ "core.download": "Download core",
214
+ "core.downloading": "Downloading…",
215
+ "core.redownload": "Re-download",
216
+ "core.downloaded": "Core downloaded and verified",
217
+ "core.order": "Resolution order: TRENDING_DIGEST_CORE (dev/troubleshooting only) → Release asset (cache first, otherwise download and verify); otherwise CORE_UNAVAILABLE.",
208
218
  "coexist.title": "Another CLI found on this machine",
209
219
  "coexist.hint": "The plugin still uses its bundled core; it never switches or takes over that CLI schedule.",
210
220
  "card.model.title": "Model",
@@ -630,6 +640,7 @@ function SettingsView(props) {
630
640
  const [preflight, setPreflight] = import_react.default.useState(null);
631
641
  const [preflighting, setPreflighting] = import_react.default.useState(false);
632
642
  const [rechecking, setRechecking] = import_react.default.useState(false);
643
+ const [downloading, setDownloading] = import_react.default.useState(false);
633
644
  const [quickOpen, setQuickOpen] = import_react.default.useState(false);
634
645
  import_react.default.useEffect(() => {
635
646
  if (state !== null && !dirty) setDraft(state.settings);
@@ -690,6 +701,20 @@ function SettingsView(props) {
690
701
  setRechecking(false);
691
702
  }
692
703
  };
704
+ const downloadCore = async () => {
705
+ setDownloading(true);
706
+ setProblems([]);
707
+ setMessage(null);
708
+ try {
709
+ await apiCall("POST", "/core/download", {});
710
+ setMessage(t("core.downloaded"));
711
+ await store.refresh({ quiet: true });
712
+ } catch (error) {
713
+ setMessage(error instanceof Error ? error.message : String(error));
714
+ } finally {
715
+ setDownloading(false);
716
+ }
717
+ };
693
718
  const importCli = async () => {
694
719
  try {
695
720
  const result = await apiCall(
@@ -755,12 +780,19 @@ function SettingsView(props) {
755
780
  { label: t("core.label") },
756
781
  h("span", { className: "dshgt-pill" }, h("span", { className: `dshgt-dot ${coreKind}` }), state.core.version ?? t(`core.${state.core.state}`)),
757
782
  state.core.ipcSchema === void 0 ? null : h("span", { className: "dshgt-hint dshgt-mono" }, `ipc ${state.core.ipcSchema} · digest ${state.core.digestSchema ?? "?"}`),
783
+ state.core.release === void 0 ? null : h("span", { className: "dshgt-hint dshgt-mono" }, `${state.core.release.tag} · ${state.core.release.asset}`),
758
784
  h(
759
785
  "span",
760
- { className: "dshgt-hint dshgt-mono", title: state.core.path ?? "" },
761
- state.core.source === "package" ? t("core.package") : state.core.source === "env" ? "TRENDING_DIGEST_CORE" : state.core.path ?? ""
786
+ { className: "dshgt-hint" },
787
+ state.core.download.state === "override" ? `覆盖路径:${state.core.override?.path ?? ""}` : state.core.release?.cached === true ? t("core.cached") : t("core.notDownloaded")
788
+ ),
789
+ state.core.download.state === "override" ? null : h(
790
+ Btn,
791
+ { small: true, onClick: () => void downloadCore(), disabled: downloading },
792
+ downloading ? t("core.downloading") : state.core.release?.cached === true ? t("core.redownload") : t("core.download")
762
793
  )
763
794
  ),
795
+ state.core.release === void 0 ? null : h("p", { className: "dshgt-hint dshgt-mono", style: { fontSize: 11 } }, `${state.core.release.url} → ${state.core.release.binaryPath}`),
764
796
  state.core.state === "ready" ? null : h(Alert, { kind: "err", title: t("core.label") }, h("div", null, state.core.message ?? ""), h("div", { className: "dshgt-hint" }, t("core.order"))),
765
797
  h("p", { className: "dshgt-hint" }, t("core.order")),
766
798
  state.coexistence.found ? h(Alert, { kind: "warn", title: t("coexist.title") }, h("div", { className: "dshgt-mono", style: { fontSize: 12 } }, `${state.coexistence.path ?? ""} ${state.coexistence.version ?? ""}`), h("div", null, state.coexistence.message ?? ""), h("div", { className: "dshgt-hint" }, t("coexist.hint"))) : null,
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "https://github.com/morehao/trendradar.git",
3
- "ref": "d3bf720",
3
+ "ref": "v0.1.0",
4
4
  "commit": "d3bf720a869732e7a2ee04fa39d32dd50868fd61",
5
5
  "aggregate": "eb7f1ac9e117a709d15d87de89cf30e309c479e62dfa3d320cb8f154d3f1dc1e",
6
- "syncedAt": "2026-10-30T13:20:43.663Z",
6
+ "syncedAt": "2026-10-30T14:06:16.766Z",
7
7
  "files": [
8
8
  {
9
9
  "path": "schema/api.v1.yaml",
@@ -48,7 +48,8 @@ var DEFAULT_SETTINGS = {
48
48
  trending: { since: "daily", languages: ["all", "typescript", "python", "go", "rust"], topN: 25 },
49
49
  output: { dir: "trending" },
50
50
  schedule: { enabled: true, time: "08:30:00", timezone: "Asia/Shanghai" },
51
- corePath: ""
51
+ corePath: "",
52
+ coreAutoDownload: true
52
53
  };
53
54
  var PROVIDER_BASE_URLS = {
54
55
  deepseek: "https://api.deepseek.com/v1",
@@ -125,7 +126,8 @@ function normalizeSettings(input) {
125
126
  time: str(schedule.time, DEFAULT_SETTINGS.schedule.time),
126
127
  timezone: str(schedule.timezone, DEFAULT_SETTINGS.schedule.timezone)
127
128
  },
128
- corePath: str(raw.corePath, DEFAULT_SETTINGS.corePath).trim()
129
+ corePath: str(raw.corePath, DEFAULT_SETTINGS.corePath).trim(),
130
+ coreAutoDownload: bool(raw.coreAutoDownload, DEFAULT_SETTINGS.coreAutoDownload)
129
131
  };
130
132
  }
131
133
  var TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/;
@@ -403,6 +405,8 @@ var PLUGIN_CODES = [
403
405
  "CORE_TIMEOUT",
404
406
  "CORE_CANCELLED",
405
407
  "CORE_CONFIG_CHANNEL_FAILED",
408
+ "CORE_NOT_DOWNLOADED",
409
+ "CORE_CHECKSUM_MISMATCH",
406
410
  "IPC_INVALID",
407
411
  "MODEL_KEY_MISSING",
408
412
  "MODEL_NOT_FOUND",
@@ -435,6 +439,8 @@ var RETRIABLE = {
435
439
  CORE_TIMEOUT: true,
436
440
  CORE_CANCELLED: false,
437
441
  CORE_CONFIG_CHANNEL_FAILED: true,
442
+ CORE_NOT_DOWNLOADED: false,
443
+ CORE_CHECKSUM_MISMATCH: true,
438
444
  IPC_INVALID: false,
439
445
  MODEL_KEY_MISSING: false,
440
446
  MODEL_NOT_FOUND: false,
@@ -464,6 +470,10 @@ function hintOf(code) {
464
470
  return "\u8FD0\u884C\u5DF2\u53D6\u6D88\uFF1A\u6838\u5FC3\u5DF2\u7EC8\u6B62\uFF0C\u672A\u63D0\u4EA4\u72B6\u6001\uFF0C\u4E5F\u672A\u7559\u534A\u6210\u54C1\u3002";
465
471
  case "CORE_CONFIG_CHANNEL_FAILED":
466
472
  return "\u914D\u7F6E\u901A\u9053\u5EFA\u7ACB\u5931\u8D25\uFF08FIFO \u4E0D\u53EF\u7528\uFF09\uFF1A\u6539\u7528\u4E34\u65F6\u6587\u4EF6\u6A21\u5F0F\u91CD\u8BD5\uFF0C\u6216\u68C0\u67E5\u76EE\u6807\u6587\u4EF6\u7CFB\u7EDF\u3002";
473
+ case "CORE_NOT_DOWNLOADED":
474
+ return "\u6838\u5FC3\u8FD8\u6CA1\u4E0B\u8F7D\uFF1A\u5728\u8BBE\u7F6E\u9875\u70B9\u300C\u4E0B\u8F7D\u6838\u5FC3\u300D\uFF08\u6216\u7528 TRENDING_DIGEST_CORE \u6307\u5411\u672C\u5730\u6838\u5FC3\uFF09\u3002\u9996\u6B21\u8FD0\u884C\u9700\u8981\u8054\u7F51\u3002";
475
+ case "CORE_CHECKSUM_MISMATCH":
476
+ return "\u6838\u5FC3\u4E8C\u8FDB\u5236\u6821\u9A8C\u5931\u8D25\u4E14\u5DF2\u4E22\u5F03\uFF1A\u7F51\u7EDC\u4EE3\u7406\u53EF\u80FD\u6539\u5199\u4E86\u5185\u5BB9\uFF0C\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4ECD\u5931\u8D25\u8BF7\u624B\u52A8\u4E0B\u8F7D\u540E\u653E\u5230\u7F13\u5B58\u76EE\u5F55\u3002";
467
477
  case "IPC_INVALID":
468
478
  return "\u6838\u5FC3\u8F93\u51FA\u4E0D\u662F\u7EA6\u5B9A\u7684\u7ED3\u679C\u4FE1\u5C01\uFF1A\u5347\u7EA7\u63D2\u4EF6\u4E0E\u6838\u5FC3\u5230\u540C\u4E00\u7248\u672C\u3002";
469
479
  case "MODEL_KEY_MISSING":
@@ -731,105 +741,289 @@ async function handshake(core, options = {}) {
731
741
  return { version, corePath: core.path, durationMs: result.durationMs, supportedProviders };
732
742
  }
733
743
 
734
- // src/core/locate.ts
735
- var locate_exports = {};
736
- __export(locate_exports, {
737
- PLATFORM_PACKAGE_PREFIX: () => PLATFORM_PACKAGE_PREFIX,
738
- locateCore: () => locateCore,
739
- packageInstalled: () => packageInstalled,
740
- platformPackageName: () => platformPackageName
744
+ // src/core/release.ts
745
+ var release_exports = {};
746
+ __export(release_exports, {
747
+ assetName: () => assetName,
748
+ describeRelease: () => describeRelease,
749
+ ensureCore: () => ensureCore,
750
+ goTarget: () => goTarget,
751
+ readCoreRelease: () => readCoreRelease,
752
+ verifyCached: () => verifyCached
741
753
  });
754
+ import { createHash } from "crypto";
755
+ import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, statSync, unlinkSync } from "fs";
742
756
  import { createRequire } from "module";
743
- import { accessSync, constants, existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
757
+ import { pipeline } from "stream/promises";
758
+ import { Readable } from "stream";
744
759
  import path4 from "path";
745
- var PLATFORM_PACKAGE_PREFIX = "@morehao/trendradar-core";
746
- function platformPackageName(platform = process.platform, arch = process.arch) {
747
- return `${PLATFORM_PACKAGE_PREFIX}-${platform}-${arch}`;
760
+ var DOWNLOAD_TIMEOUT_MS = 18e4;
761
+ function requireJson() {
762
+ const require2 = createRequire(import.meta.url);
763
+ return require2("../package.json");
748
764
  }
749
- function executable(file) {
765
+ function readCoreRelease(pkg) {
766
+ const source = pkg ?? requireJson();
767
+ const repo = source.dsh?.core?.repo;
768
+ const version = source.dsh?.core?.version;
769
+ if (typeof repo !== "string" || !/^[\w.-]+\/[\w.-]+$/.test(repo)) {
770
+ throw new IpcError("CORE_UNAVAILABLE", "package.json \u7F3A\u5C11 dsh.core.repo\uFF08owner/repo\uFF09\uFF0C\u65E0\u6CD5\u5B9A\u4F4D\u6838\u5FC3 Release");
771
+ }
772
+ if (typeof version !== "string" || !/^\d+\.\d+\.\d+(?:-[\w.]+)?$/.test(version)) {
773
+ throw new IpcError("CORE_UNAVAILABLE", "package.json \u7F3A\u5C11 dsh.core.version\uFF08\u5F62\u5982 0.1.0\uFF09\uFF0C\u65E0\u6CD5\u5B9A\u4F4D\u6838\u5FC3 Release");
774
+ }
775
+ return { repo, version };
776
+ }
777
+ function goTarget(platform = process.platform, arch = process.arch) {
778
+ const goos = platform === "win32" ? "windows" : platform === "darwin" ? "darwin" : platform === "linux" ? "linux" : platform;
779
+ const goarch = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : arch === "ia32" ? "386" : arch;
780
+ return { goos, goarch };
781
+ }
782
+ function assetName(platform = process.platform, arch = process.arch) {
783
+ const { goos, goarch } = goTarget(platform, arch);
784
+ const suffix = platform === "win32" ? ".exe" : "";
785
+ return `trendradar_${goos}_${goarch}${suffix}`;
786
+ }
787
+ function binaryName(platform = process.platform) {
788
+ return platform === "win32" ? "trendradar.exe" : "trendradar";
789
+ }
790
+ function describeRelease(release, options = {}) {
791
+ const env = options.env ?? process.env;
792
+ const platform = options.platform ?? process.platform;
793
+ const arch = options.arch ?? process.arch;
794
+ const { goos, goarch } = goTarget(platform, arch);
795
+ const asset = assetName(platform, arch);
796
+ const tag = `v${release.version}`;
797
+ const base = `https://github.com/${release.repo}/releases/download/${tag}`;
798
+ const cacheDir = path4.join(dataDir(env), "core", tag);
799
+ const binaryPath = path4.join(cacheDir, binaryName(platform));
800
+ return {
801
+ ...release,
802
+ tag,
803
+ goos,
804
+ goarch,
805
+ asset,
806
+ url: `${base}/${asset}`,
807
+ checksumsUrl: `${base}/checksums.txt`,
808
+ cacheDir,
809
+ binaryPath,
810
+ cached: existsSync2(binaryPath)
811
+ };
812
+ }
813
+ function sha256File(file) {
814
+ return createHash("sha256").update(readFileSync2(file)).digest("hex");
815
+ }
816
+ function markerPath(binaryPath) {
817
+ return `${binaryPath}.sha256`;
818
+ }
819
+ function verifyCached(binaryPath) {
820
+ let info;
750
821
  try {
751
- if (!statSync(file).isFile()) return false;
752
- accessSync(file, constants.X_OK);
753
- return true;
822
+ info = statSync(binaryPath);
754
823
  } catch {
755
- return false;
824
+ return { ok: false, reason: "missing" };
825
+ }
826
+ if (!info.isFile()) return { ok: false, reason: "not-a-file" };
827
+ if (process.platform !== "win32" && (info.mode & 73) === 0) return { ok: false, reason: "not-executable" };
828
+ const marker = markerPath(binaryPath);
829
+ if (!existsSync2(marker)) return { ok: true };
830
+ const expected = readFileSync2(marker, "utf8").trim();
831
+ if (expected === "") return { ok: true };
832
+ try {
833
+ return sha256File(binaryPath) === expected ? { ok: true } : { ok: false, reason: "checksum-mismatch" };
834
+ } catch {
835
+ return { ok: false, reason: "unreadable" };
756
836
  }
757
837
  }
758
- function binCandidates(packageJsonPath, packageDir) {
759
- const candidates = [];
838
+ function fetchOf(options) {
839
+ if (options.fetchImpl !== void 0) return options.fetchImpl;
840
+ if (typeof fetch !== "function") {
841
+ throw new IpcError("CORE_UNAVAILABLE", "\u5F53\u524D Node \u6CA1\u6709\u5168\u5C40 fetch\uFF08\u9700\u8981 Node \u2265 18\uFF09\uFF0C\u65E0\u6CD5\u4E0B\u8F7D\u6838\u5FC3");
842
+ }
843
+ return fetch;
844
+ }
845
+ async function fetchChecksum(descriptor, options) {
846
+ options.onProgress?.({ phase: "checksums" });
847
+ const response = await fetchOf(options)(descriptor.checksumsUrl, { signal: AbortSignal.timeout(3e4) }).catch((error) => {
848
+ throw new IpcError(
849
+ "CORE_UNAVAILABLE",
850
+ `\u65E0\u6CD5\u83B7\u53D6\u6838\u5FC3\u6821\u9A8C\u6587\u4EF6\uFF1A${descriptor.checksumsUrl}\uFF08${error instanceof Error ? error.message : String(error)}\uFF09\u3002\u82E5\u5728\u516C\u53F8\u7F51\u7EDC\u5185\uFF0C\u8BF7\u786E\u8BA4\u4EE3\u7406\u53EF\u7528\uFF1B\u4E5F\u53EF\u7528 TRENDING_DIGEST_CORE \u6307\u5411\u672C\u5730\u5DF2\u7F16\u8BD1\u7684\u6838\u5FC3\u3002`
851
+ );
852
+ });
853
+ if (!response.ok) {
854
+ throw new IpcError(
855
+ "CORE_UNAVAILABLE",
856
+ `\u6838\u5FC3 ${descriptor.tag} \u8FD8\u6CA1\u6709\u53D1\u5E03\uFF08${descriptor.checksumsUrl} \u8FD4\u56DE HTTP ${response.status}\uFF09\u3002\u53D1\u5E03\u987A\u5E8F\u662F\u5148\u5728 trendradar \u6253 tag \u5E76\u4E0A\u4F20 Release \u8D44\u4EA7\uFF0C\u63D2\u4EF6\u968F\u540E\u624D\u80FD\u4E0B\u8F7D\u3002`
857
+ );
858
+ }
859
+ const text = await response.text();
860
+ for (const line of text.split("\n")) {
861
+ const match = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line.trim());
862
+ if (match !== null && match[2] === descriptor.asset) return match[1];
863
+ }
864
+ throw new IpcError(
865
+ "CORE_UNAVAILABLE",
866
+ `checksums.txt \u91CC\u6CA1\u6709 ${descriptor.asset}\uFF08\u5F53\u524D\u5E73\u53F0 ${descriptor.goos}/${descriptor.goarch} \u53EF\u80FD\u4E0D\u5728\u53D1\u5E03\u77E9\u9635\u5185\uFF09`
867
+ );
868
+ }
869
+ async function download(descriptor, options) {
870
+ const expected = await fetchChecksum(descriptor, options);
871
+ options.onProgress?.({ phase: "download" });
872
+ const response = await fetchOf(options)(descriptor.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) }).catch((error) => {
873
+ throw new IpcError(
874
+ "CORE_UNAVAILABLE",
875
+ `\u4E0B\u8F7D\u6838\u5FC3\u5931\u8D25\uFF1A${descriptor.url}\uFF08${error instanceof Error ? error.message : String(error)}\uFF09\u3002\u53EF\u624B\u52A8\u4E0B\u8F7D\u540E\u653E\u5230 ${descriptor.binaryPath}\uFF0C\u6216\u7528 TRENDING_DIGEST_CORE \u6307\u5B9A\u8DEF\u5F84\u3002`
876
+ );
877
+ });
878
+ if (!response.ok || response.body === null) {
879
+ throw new IpcError(
880
+ "CORE_UNAVAILABLE",
881
+ `\u4E0B\u8F7D\u6838\u5FC3\u5931\u8D25\uFF1A${descriptor.url} \u8FD4\u56DE HTTP ${response.status}\uFF08\u7248\u672C ${descriptor.tag} / ${descriptor.asset}\uFF09`
882
+ );
883
+ }
884
+ mkdirSync2(descriptor.cacheDir, { recursive: true, mode: 448 });
885
+ const temp = `${descriptor.binaryPath}.${process.pid}.${Date.now()}.part`;
886
+ const hash = createHash("sha256");
887
+ const total = Number(response.headers.get("content-length") ?? "0") || void 0;
888
+ let bytes = 0;
760
889
  try {
761
- const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
762
- const bin = pkg.bin;
763
- if (typeof bin === "string") candidates.push(path4.join(packageDir, bin));
764
- else if (typeof bin === "object" && bin !== null) {
765
- const entry = bin["trendradar"];
766
- if (typeof entry === "string") candidates.push(path4.join(packageDir, entry));
890
+ await pipeline(
891
+ Readable.fromWeb(response.body),
892
+ async function* (source) {
893
+ for await (const chunk of source) {
894
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
895
+ hash.update(buf);
896
+ bytes += buf.length;
897
+ yield buf;
898
+ }
899
+ },
900
+ createWriteStream(temp, { mode: 384 })
901
+ );
902
+ const actual = hash.digest("hex");
903
+ options.onProgress?.({ phase: "verify", bytes, total });
904
+ if (actual !== expected) {
905
+ throw new IpcError(
906
+ "CORE_CHECKSUM_MISMATCH",
907
+ `\u6838\u5FC3\u4E8C\u8FDB\u5236\u6821\u9A8C\u5931\u8D25\uFF1A\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${actual}\uFF08${descriptor.url}\uFF09\u3002\u5DF2\u4E22\u5F03\u4E0B\u8F7D\u7ED3\u679C\u3002`
908
+ );
767
909
  }
768
- } catch {
910
+ chmodSync(temp, 493);
911
+ renameSync2(temp, descriptor.binaryPath);
912
+ const fs = await import("fs");
913
+ fs.writeFileSync(markerPath(descriptor.binaryPath), `${actual}
914
+ `, { mode: 384 });
915
+ return { path: descriptor.binaryPath, sha256: actual };
916
+ } catch (error) {
917
+ rmSync(temp, { force: true });
918
+ throw error;
769
919
  }
770
- candidates.push(path4.join(packageDir, "bin", "trendradar"));
771
- candidates.push(path4.join(packageDir, "trendradar"));
772
- return candidates;
773
920
  }
774
- function locateCore(options) {
775
- const env = options.env ?? process.env;
776
- const override = options.settings.corePath !== "" ? options.settings.corePath : env.TRENDING_DIGEST_CORE?.trim() ?? "";
777
- const searched = [];
778
- if (override !== "") {
779
- const resolved = path4.isAbsolute(override) ? override : path4.resolve(override);
780
- searched.push(resolved);
781
- if (!existsSync2(resolved)) {
921
+ async function ensureCore(options = {}) {
922
+ const release = options.release ?? readCoreRelease();
923
+ const descriptor = describeRelease(release, options);
924
+ if (!options.force && existsSync2(descriptor.binaryPath)) {
925
+ const verified = verifyCached(descriptor.binaryPath);
926
+ if (verified.ok) {
927
+ return { path: descriptor.binaryPath, source: "cache", descriptor };
928
+ }
929
+ if (options.allowDownload === false) {
782
930
  throw new IpcError(
783
931
  "CORE_UNAVAILABLE",
784
- `TRENDING_DIGEST_CORE \u6307\u5411\u7684\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${resolved}\u3002\u8BE5\u53D8\u91CF\u53EA\u7528\u4E8E\u5F00\u53D1/\u6392\u969C\uFF0C\u6B63\u5E38\u5B89\u88C5\u8BF7\u5220\u6389\u5B83\u3002`
932
+ `\u672C\u5730\u6838\u5FC3\u7F13\u5B58\u4E0D\u53EF\u7528\uFF08${verified.reason ?? "unknown"}\uFF09\uFF1A${descriptor.binaryPath}\u3002\u5220\u9664\u8BE5\u6587\u4EF6\u540E\u91CD\u8BD5\uFF0C\u6216\u5728\u8BBE\u7F6E\u9875\u70B9\u300C\u91CD\u65B0\u4E0B\u8F7D\u6838\u5FC3\u300D\u3002`
785
933
  );
786
934
  }
787
- if (!executable(resolved)) {
788
- throw new IpcError("CORE_UNAVAILABLE", `TRENDING_DIGEST_CORE \u6307\u5411\u7684\u6587\u4EF6\u4E0D\u53EF\u6267\u884C\uFF1A${resolved}`);
935
+ try {
936
+ unlinkSync(descriptor.binaryPath);
937
+ } catch {
789
938
  }
790
- return {
791
- path: resolved,
792
- source: options.settings.corePath !== "" ? "settings" : "env",
793
- searched
794
- };
795
939
  }
796
- const platform = options.platform ?? process.platform;
797
- const arch = options.arch ?? process.arch;
798
- const packageName = platformPackageName(platform, arch);
799
- const resolve = options.resolve ?? createRequire(import.meta.url);
800
- try {
801
- const packageJson = resolve(`${packageName}/package.json`);
802
- const packageDir = path4.dirname(packageJson);
803
- for (const candidate of binCandidates(packageJson, packageDir)) {
804
- searched.push(candidate);
805
- if (executable(candidate)) {
806
- return { path: candidate, source: "package", packageName, searched };
807
- }
808
- }
940
+ if (options.allowDownload === false) {
809
941
  throw new IpcError(
810
- "CORE_UNAVAILABLE",
811
- `\u5E73\u53F0\u5305 ${packageName} \u5DF2\u5B89\u88C5\uFF0C\u4F46\u627E\u4E0D\u5230\u53EF\u6267\u884C\u7684\u6838\u5FC3\uFF08\u627E\u8FC7\uFF1A${searched.join("\u3001")}\uFF09\u3002\u91CD\u88C5\u63D2\u4EF6\u53EF\u4FEE\u590D\u3002`
812
- );
813
- } catch (error) {
814
- if (error instanceof IpcError) throw error;
815
- throw new IpcError(
816
- "CORE_UNAVAILABLE",
817
- `\u627E\u4E0D\u5230\u6838\u5FC3\u4E8C\u8FDB\u5236\uFF1A\u672A\u8BBE\u7F6E TRENDING_DIGEST_CORE\uFF0C\u5E73\u53F0\u5305 ${packageName} \u4E5F\u672A\u5B89\u88C5\u3002\u8BF7\u91CD\u88C5\u63D2\u4EF6\uFF08\u4F1A\u5E26\u4E0A\u5BF9\u5E94\u5E73\u53F0\u7684\u5305\uFF09\uFF1B\u652F\u6301\u7684\u5E73\u53F0\uFF1Adarwin/linux/win32 \xD7 x64/arm64\u3002`
942
+ "CORE_NOT_DOWNLOADED",
943
+ `\u6838\u5FC3 ${descriptor.tag} \u5C1A\u672A\u4E0B\u8F7D\uFF08\u7F13\u5B58\u76EE\u5F55 ${descriptor.cacheDir}\uFF09\u3002\u4E0B\u8F7D\u5730\u5740\uFF1A${descriptor.url}\uFF1B\u6216\u5728\u8BBE\u7F6E\u9875\u70B9\u300C\u4E0B\u8F7D\u6838\u5FC3\u300D\uFF0C\u6216\u8BBE\u7F6E TRENDING_DIGEST_CORE \u6307\u5411\u672C\u5730\u6838\u5FC3\u3002`
818
944
  );
819
945
  }
946
+ const downloaded = await download(descriptor, options);
947
+ return { path: downloaded.path, source: "download", descriptor, sha256: downloaded.sha256 };
820
948
  }
821
- function packageInstalled(options) {
822
- const platform = options.platform ?? process.platform;
823
- const arch = options.arch ?? process.arch;
824
- const packageName = platformPackageName(platform, arch);
825
- const resolve = options.resolve ?? createRequire(import.meta.url);
949
+
950
+ // src/core/locate.ts
951
+ var locate_exports = {};
952
+ __export(locate_exports, {
953
+ inspectRelease: () => inspectRelease,
954
+ locateCore: () => locateCore,
955
+ overridePath: () => overridePath
956
+ });
957
+ import { accessSync, constants, existsSync as existsSync3, statSync as statSync2 } from "fs";
958
+ import path5 from "path";
959
+ function executable(file) {
826
960
  try {
827
- resolve(`${packageName}/package.json`);
961
+ if (!statSync2(file).isFile()) return false;
962
+ accessSync(file, constants.X_OK);
828
963
  return true;
829
964
  } catch {
830
965
  return false;
831
966
  }
832
967
  }
968
+ function overridePath(settings, env = process.env) {
969
+ const fromSettings = settings.corePath.trim();
970
+ if (fromSettings !== "") {
971
+ return { path: path5.isAbsolute(fromSettings) ? fromSettings : path5.resolve(fromSettings), source: "settings" };
972
+ }
973
+ const fromEnv2 = env.TRENDING_DIGEST_CORE?.trim() ?? "";
974
+ if (fromEnv2 !== "") return { path: path5.isAbsolute(fromEnv2) ? fromEnv2 : path5.resolve(fromEnv2), source: "env" };
975
+ return void 0;
976
+ }
977
+ async function locateCore(options) {
978
+ const env = options.env ?? process.env;
979
+ const override = overridePath(options.settings, env);
980
+ if (override !== void 0) {
981
+ if (!existsSync3(override.path)) {
982
+ throw new IpcError(
983
+ "CORE_UNAVAILABLE",
984
+ `${override.source === "settings" ? "\u8BBE\u7F6E\u91CC\u7684 corePath" : "TRENDING_DIGEST_CORE"} \u6307\u5411\u7684\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${override.path}\u3002\u8BE5\u8986\u76D6\u53EA\u7528\u4E8E\u5F00\u53D1/\u6392\u969C\uFF0C\u6B63\u5E38\u4F7F\u7528\u8BF7\u6E05\u7A7A\u5B83\u3002`
985
+ );
986
+ }
987
+ if (!executable(override.path)) {
988
+ throw new IpcError("CORE_UNAVAILABLE", `${override.path} \u4E0D\u53EF\u6267\u884C\uFF08\u7F3A\u6267\u884C\u4F4D\u6216\u4E0D\u662F\u6587\u4EF6\uFF09`);
989
+ }
990
+ return { path: override.path, source: override.source };
991
+ }
992
+ const result = await ensureCore({
993
+ ...options.release === void 0 ? {} : { release: options.release },
994
+ env,
995
+ ...options.platform === void 0 ? {} : { platform: options.platform },
996
+ ...options.arch === void 0 ? {} : { arch: options.arch },
997
+ ...options.allowDownload === void 0 ? {} : { allowDownload: options.allowDownload },
998
+ ...options.forceDownload === void 0 ? {} : { force: options.forceDownload },
999
+ ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl }
1000
+ });
1001
+ return {
1002
+ path: result.path,
1003
+ source: result.source,
1004
+ release: { repo: result.descriptor.repo, version: result.descriptor.version },
1005
+ tag: result.descriptor.tag,
1006
+ downloaded: result.source === "download",
1007
+ ...result.sha256 === void 0 ? {} : { sha256: result.sha256 }
1008
+ };
1009
+ }
1010
+ function inspectRelease(settings, options = {}) {
1011
+ const env = options.env ?? process.env;
1012
+ const override = overridePath(settings, env);
1013
+ if (override !== void 0) return { override };
1014
+ try {
1015
+ const release = options.release ?? readCoreRelease();
1016
+ return {
1017
+ descriptor: describeRelease(release, {
1018
+ env,
1019
+ ...options.platform === void 0 ? {} : { platform: options.platform },
1020
+ ...options.arch === void 0 ? {} : { arch: options.arch }
1021
+ })
1022
+ };
1023
+ } catch (error) {
1024
+ return { error: error instanceof Error ? error.message : String(error) };
1025
+ }
1026
+ }
833
1027
 
834
1028
  // src/core/channel.ts
835
1029
  var channel_exports = {};
@@ -843,10 +1037,10 @@ import { constants as constants2 } from "fs";
843
1037
  import { open, rm, stat, unlink, writeFile } from "fs/promises";
844
1038
  import { mkdtempSync } from "fs";
845
1039
  import os2 from "os";
846
- import path5 from "path";
1040
+ import path6 from "path";
847
1041
  var WRITE_TIMEOUT_MS = 15e3;
848
1042
  function tempDir() {
849
- return mkdtempSync(path5.join(os2.tmpdir(), "dsh-trending-"));
1043
+ return mkdtempSync(path6.join(os2.tmpdir(), "dsh-trending-"));
850
1044
  }
851
1045
  async function mkfifo(file) {
852
1046
  await new Promise((resolve, reject) => {
@@ -864,7 +1058,7 @@ async function mkfifo(file) {
864
1058
  });
865
1059
  }
866
1060
  async function createFifoChannel(dir, writeTimeoutMs) {
867
- const file = path5.join(dir, "core.yml");
1061
+ const file = path6.join(dir, "core.yml");
868
1062
  await mkfifo(file);
869
1063
  let disposed = false;
870
1064
  return {
@@ -905,7 +1099,7 @@ async function createFifoChannel(dir, writeTimeoutMs) {
905
1099
  };
906
1100
  }
907
1101
  async function createTmpfileChannel(dir) {
908
- const file = path5.join(dir, "core.yml");
1102
+ const file = path6.join(dir, "core.yml");
909
1103
  let disposed = false;
910
1104
  return {
911
1105
  kind: "tmpfile",
@@ -938,7 +1132,7 @@ async function removeChannelDir(channel) {
938
1132
  if (info.isFIFO()) return;
939
1133
  } catch {
940
1134
  }
941
- await rm(path5.dirname(channel.path), { recursive: true, force: true }).catch(() => void 0);
1135
+ await rm(path6.dirname(channel.path), { recursive: true, force: true }).catch(() => void 0);
942
1136
  }
943
1137
 
944
1138
  // src/core/run.ts
@@ -1111,12 +1305,12 @@ __export(core_exports, {
1111
1305
  findOnPath: () => findOnPath,
1112
1306
  inspectCore: () => inspectCore
1113
1307
  });
1114
- import { accessSync as accessSync2, constants as constants3, statSync as statSync2 } from "fs";
1115
- import path6 from "path";
1308
+ import { accessSync as accessSync2, constants as constants3, statSync as statSync3 } from "fs";
1309
+ import path7 from "path";
1116
1310
  var CACHE_TTL_MS = 3e4;
1117
1311
  function executable2(file) {
1118
1312
  try {
1119
- if (!statSync2(file).isFile()) return false;
1313
+ if (!statSync3(file).isFile()) return false;
1120
1314
  accessSync2(file, constants3.X_OK);
1121
1315
  return true;
1122
1316
  } catch {
@@ -1126,10 +1320,10 @@ function executable2(file) {
1126
1320
  function findOnPath(command, env = process.env) {
1127
1321
  const raw = env.PATH ?? "";
1128
1322
  const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
1129
- for (const dir of raw.split(path6.delimiter)) {
1323
+ for (const dir of raw.split(path7.delimiter)) {
1130
1324
  if (dir.trim() === "") continue;
1131
1325
  for (const ext of exts) {
1132
- const candidate = path6.join(dir, `${command}${ext}`);
1326
+ const candidate = path7.join(dir, `${command}${ext}`);
1133
1327
  if (executable2(candidate)) return candidate;
1134
1328
  }
1135
1329
  }
@@ -1142,14 +1336,25 @@ async function inspectCore(settings, options = {}) {
1142
1336
  if (!options.fresh && cached !== void 0 && cached.key === key && Date.now() - cached.at < CACHE_TTL_MS) {
1143
1337
  return cached.status;
1144
1338
  }
1145
- const packageName = platformPackageName();
1339
+ const view = inspectRelease(settings, { env });
1340
+ const release = view.descriptor === void 0 ? void 0 : {
1341
+ repo: view.descriptor.repo,
1342
+ tag: view.descriptor.tag,
1343
+ asset: view.descriptor.asset,
1344
+ url: view.descriptor.url,
1345
+ checksumsUrl: view.descriptor.checksumsUrl,
1346
+ cacheDir: view.descriptor.cacheDir,
1347
+ binaryPath: view.descriptor.binaryPath,
1348
+ cached: view.descriptor.cached
1349
+ };
1146
1350
  const base = {
1147
- packageName,
1148
- packageInstalled: packageInstalled({ settings, env })
1351
+ ...release === void 0 ? {} : { release },
1352
+ ...view.override === void 0 ? {} : { override: view.override },
1353
+ download: view.override !== void 0 ? { state: "override" } : release === void 0 ? { state: "missing", reason: view.error ?? "release \u672A\u914D\u7F6E" } : { state: release.cached ? "cached" : "missing" }
1149
1354
  };
1150
1355
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
1151
1356
  try {
1152
- const core = locateCore({ settings, env });
1357
+ const core = await locateCore({ settings, env, allowDownload: false });
1153
1358
  const result = await handshake(core, { timeoutMs: 5e3 });
1154
1359
  const status = {
1155
1360
  ...base,
@@ -1186,7 +1391,7 @@ async function detectCoexistence(ownPath, options = {}) {
1186
1391
  const env = options.env ?? process.env;
1187
1392
  const found = findOnPath("trendradar", env);
1188
1393
  if (found === void 0) return { found: false };
1189
- if (ownPath !== void 0 && path6.resolve(found) === path6.resolve(ownPath)) return { found: false };
1394
+ if (ownPath !== void 0 && path7.resolve(found) === path7.resolve(ownPath)) return { found: false };
1190
1395
  if (!options.fresh && coexistCache !== void 0 && coexistCache.key === found && Date.now() - coexistCache.at < CACHE_TTL_MS) {
1191
1396
  return coexistCache.status;
1192
1397
  }
@@ -1563,17 +1768,17 @@ var runner_exports = {};
1563
1768
  __export(runner_exports, {
1564
1769
  Runner: () => Runner
1565
1770
  });
1566
- import { existsSync as existsSync3, statSync as statSync3 } from "fs";
1567
- import path7 from "path";
1771
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
1772
+ import path8 from "path";
1568
1773
  function repoProblem(repoPath) {
1569
1774
  if (repoPath.trim() === "") return { code: "CONFIG_INVALID", message: "\u8FD8\u6CA1\u6709\u9009\u62E9\u5F52\u6863\u4ED3\u5E93" };
1570
- if (!existsSync3(repoPath)) return { code: "REPO_NOT_GIT", message: `\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${repoPath}` };
1775
+ if (!existsSync4(repoPath)) return { code: "REPO_NOT_GIT", message: `\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${repoPath}` };
1571
1776
  try {
1572
- if (!statSync3(repoPath).isDirectory()) return { code: "REPO_NOT_GIT", message: `\u4E0D\u662F\u76EE\u5F55\uFF1A${repoPath}` };
1777
+ if (!statSync4(repoPath).isDirectory()) return { code: "REPO_NOT_GIT", message: `\u4E0D\u662F\u76EE\u5F55\uFF1A${repoPath}` };
1573
1778
  } catch {
1574
1779
  return { code: "REPO_NOT_GIT", message: `\u65E0\u6CD5\u8BBF\u95EE\uFF1A${repoPath}` };
1575
1780
  }
1576
- if (!existsSync3(path7.join(repoPath, ".git"))) {
1781
+ if (!existsSync4(path8.join(repoPath, ".git"))) {
1577
1782
  return { code: "REPO_NOT_GIT", message: `\u4E0D\u662F git \u4ED3\u5E93\uFF1A${repoPath}\uFF08\u63D2\u4EF6\u4E0D\u4F1A\u66FF\u4F60 git init\uFF09` };
1578
1783
  }
1579
1784
  return void 0;
@@ -1616,7 +1821,10 @@ var Runner = class {
1616
1821
  const startedMs = Date.now();
1617
1822
  let proxy;
1618
1823
  try {
1619
- const core = locateCore({ settings, env });
1824
+ const core = await locateCore({ settings, env, allowDownload: settings.coreAutoDownload });
1825
+ if (core.downloaded === true) {
1826
+ store.appendLog(logFile, `[core] \u5DF2\u4E0B\u8F7D\u6838\u5FC3 ${core.tag ?? ""} \u2192 ${core.path}`);
1827
+ }
1620
1828
  const handshakeResult = await handshake(core, { signal: controller.signal });
1621
1829
  if (!handshakeResult.supportedProviders.includes(settings.model.provider)) {
1622
1830
  throw new IpcError(
@@ -1666,7 +1874,17 @@ var Runner = class {
1666
1874
  onStderr: (line) => store.appendLog(logFile, line)
1667
1875
  });
1668
1876
  const outcome = result.outcome;
1669
- const summary = this.summarize({ id, outcome, dryRun, ...options.date === void 0 ? {} : { date: options.date }, logFile, channel: result.channelKind, proxy: proxy !== void 0 });
1877
+ const summary = this.summarize({
1878
+ id,
1879
+ outcome,
1880
+ dryRun,
1881
+ ...options.date === void 0 ? {} : { date: options.date },
1882
+ logFile,
1883
+ channel: result.channelKind,
1884
+ proxy: proxy !== void 0,
1885
+ coreSource: core.source,
1886
+ ...core.tag === void 0 ? {} : { coreTag: core.tag }
1887
+ });
1670
1888
  this.record(summary, settings);
1671
1889
  return summary;
1672
1890
  } catch (error) {
@@ -1716,7 +1934,9 @@ var Runner = class {
1716
1934
  warnings: outcome.warnings,
1717
1935
  logFile: input.logFile,
1718
1936
  ...input.channel === void 0 ? {} : { channel: input.channel },
1719
- proxy: input.proxy
1937
+ proxy: input.proxy,
1938
+ ...input.coreSource === void 0 ? {} : { coreSource: input.coreSource },
1939
+ ...input.coreTag === void 0 ? {} : { coreTag: input.coreTag }
1720
1940
  };
1721
1941
  }
1722
1942
  failureSummary(input) {
@@ -1957,14 +2177,14 @@ __export(import_cli_exports, {
1957
2177
  });
1958
2178
  import { readFileSync as readFileSync3 } from "fs";
1959
2179
  import os3 from "os";
1960
- import path8 from "path";
2180
+ import path9 from "path";
1961
2181
  function cliConfigCandidates(env = process.env, home = os3.homedir()) {
1962
2182
  const xdg = env.XDG_CONFIG_HOME?.trim();
1963
- const base = xdg !== void 0 && xdg !== "" ? path8.resolve(xdg) : path8.join(home, ".config");
2183
+ const base = xdg !== void 0 && xdg !== "" ? path9.resolve(xdg) : path9.join(home, ".config");
1964
2184
  return [
1965
2185
  env.TRENDradar_CONFIG?.trim() ?? "",
1966
- path8.join(base, "trendradar", "config.yaml"),
1967
- path8.join(home, ".trendradar", "config.yaml")
2186
+ path9.join(base, "trendradar", "config.yaml"),
2187
+ path9.join(home, ".trendradar", "config.yaml")
1968
2188
  ].filter((item) => item !== "");
1969
2189
  }
1970
2190
  function unquote(raw) {
@@ -2376,6 +2596,11 @@ async function dispatch(method, pathname, url, request, deps) {
2376
2596
  const coexistence = await detectCoexistence(core.path, { env });
2377
2597
  return { core, coexistence };
2378
2598
  }
2599
+ if (method === "POST" && pathname === "/core/download") {
2600
+ const core = await locateCore({ settings, env, forceDownload: true });
2601
+ const status = await inspectCore(settings, { env, fresh: true });
2602
+ return { downloaded: true, path: core.path, tag: core.tag ?? null, core: status };
2603
+ }
2379
2604
  if (method === "POST" && pathname === "/config") {
2380
2605
  const body = await readBody2(request);
2381
2606
  const next = normalizeSettings(body.settings ?? body);
@@ -2661,6 +2886,7 @@ export {
2661
2886
  errors_exports,
2662
2887
  ipc_exports,
2663
2888
  handshake_exports,
2889
+ release_exports,
2664
2890
  locate_exports,
2665
2891
  channel_exports,
2666
2892
  run_exports,
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createRoutes,
7
7
  createTrendingHistoryTool,
8
8
  createTrendingRunTool
9
- } from "./chunk-CWB5L7RW.js";
9
+ } from "./chunk-RXYA5WFW.js";
10
10
  import "./chunk-XLIFTAVL.js";
11
11
 
12
12
  // src/index.ts
package/lib/internal.d.ts CHANGED
@@ -64,7 +64,7 @@ declare const IPC_SCHEMA_VERSION: 1;
64
64
  */
65
65
 
66
66
  /** 插件在核心之外新增的码。前缀统一避免与核心码冲突。 */
67
- declare const PLUGIN_CODES: readonly ["CORE_UNAVAILABLE", "CORE_INCOMPATIBLE", "CORE_SPAWN_FAILED", "CORE_TIMEOUT", "CORE_CANCELLED", "CORE_CONFIG_CHANNEL_FAILED", "IPC_INVALID", "MODEL_KEY_MISSING", "MODEL_NOT_FOUND", "MODEL_PROVIDER_UNSUPPORTED", "GITHUB_AUTH", "PUSH_FAILED", "REPO_DIRTY"];
67
+ declare const PLUGIN_CODES: readonly ["CORE_UNAVAILABLE", "CORE_INCOMPATIBLE", "CORE_SPAWN_FAILED", "CORE_TIMEOUT", "CORE_CANCELLED", "CORE_CONFIG_CHANNEL_FAILED", "CORE_NOT_DOWNLOADED", "CORE_CHECKSUM_MISMATCH", "IPC_INVALID", "MODEL_KEY_MISSING", "MODEL_NOT_FOUND", "MODEL_PROVIDER_UNSUPPORTED", "GITHUB_AUTH", "PUSH_FAILED", "REPO_DIRTY"];
68
68
  type PluginCode = (typeof PLUGIN_CODES)[number];
69
69
  type AnyCode = ErrorCode | PluginCode;
70
70
  interface ClassifiedError {
@@ -264,6 +264,8 @@ interface Settings {
264
264
  schedule: ScheduleSettings;
265
265
  /** 核心二进制覆盖;只在开发/排障时用,对应 TRENDING_DIGEST_CORE。 */
266
266
  corePath: string;
267
+ /** 缓存缺失时是否自动从 Release 下载核心(首次运行需要联网)。 */
268
+ coreAutoDownload: boolean;
267
269
  }
268
270
  declare const DEFAULT_SETTINGS: Settings;
269
271
  /** 核心支持的 provider 默认端点(空字符串 = 必须由用户填)。 */
@@ -470,35 +472,137 @@ declare namespace state {
470
472
  export { type state_DigestIdentity as DigestIdentity, state_HISTORY_LIMIT as HISTORY_LIMIT, type state_HistoryFile as HistoryFile, type state_PendingPr as PendingPr, type state_PluginState as PluginState, type state_RunRecord as RunRecord, state_Store as Store, type state_StoreOptions as StoreOptions, state_newRunId as newRunId, state_writeJsonAtomic as writeJsonAtomic };
471
473
  }
472
474
 
473
- declare const PLATFORM_PACKAGE_PREFIX = "@morehao/trendradar-core";
475
+ interface CoreRelease {
476
+ /** `owner/repo`(GitHub)。 */
477
+ repo: string;
478
+ /** 不带 `v` 的版本号,例如 `0.1.0`;对应 tag `v0.1.0`。 */
479
+ version: string;
480
+ }
481
+ interface ReleaseDescriptor extends CoreRelease {
482
+ tag: string;
483
+ goos: string;
484
+ goarch: string;
485
+ asset: string;
486
+ url: string;
487
+ checksumsUrl: string;
488
+ cacheDir: string;
489
+ binaryPath: string;
490
+ cached: boolean;
491
+ }
492
+ /** 从本包的 package.json 读核心版本(`sync-contract --ref vX` 会同时更新它)。 */
493
+ declare function readCoreRelease(pkg?: {
494
+ dsh?: {
495
+ core?: {
496
+ repo?: unknown;
497
+ version?: unknown;
498
+ };
499
+ };
500
+ }): CoreRelease;
501
+ /** Node 的 platform/arch → Go 的 GOOS/GOARCH(两个生态词汇不同,映射必须显式)。 */
502
+ declare function goTarget(platform?: NodeJS.Platform, arch?: string): {
503
+ goos: string;
504
+ goarch: string;
505
+ };
506
+ declare function assetName(platform?: NodeJS.Platform, arch?: string): string;
507
+ declare function describeRelease(release: CoreRelease, options?: {
508
+ env?: NodeJS.ProcessEnv;
509
+ platform?: NodeJS.Platform;
510
+ arch?: string;
511
+ }): ReleaseDescriptor;
512
+ /** 缓存里的二进制是否可信:可执行 + 与下载时记录的哈希一致。 */
513
+ declare function verifyCached(binaryPath: string): {
514
+ ok: boolean;
515
+ reason?: string;
516
+ };
517
+ interface EnsureOptions {
518
+ release?: CoreRelease;
519
+ env?: NodeJS.ProcessEnv;
520
+ platform?: NodeJS.Platform;
521
+ arch?: string;
522
+ /** false 时只查缓存,不联网(设置页探测、离线诊断用)。 */
523
+ allowDownload?: boolean;
524
+ /** true 时忽略缓存重新下载(「重新下载核心」按钮)。 */
525
+ force?: boolean;
526
+ fetchImpl?: typeof fetch;
527
+ onProgress?: (event: {
528
+ phase: 'checksums' | 'download' | 'verify';
529
+ bytes?: number;
530
+ total?: number;
531
+ }) => void;
532
+ }
533
+ interface EnsureResult {
534
+ path: string;
535
+ source: 'cache' | 'download';
536
+ descriptor: ReleaseDescriptor;
537
+ sha256?: string;
538
+ }
539
+ /**
540
+ * 确保本地有一份可用的核心二进制。
541
+ * 缓存命中就不联网;`allowDownload=false` 时缓存缺失直接报错(不带网络副作用)。
542
+ */
543
+ declare function ensureCore(options?: EnsureOptions): Promise<EnsureResult>;
544
+
545
+ type release_CoreRelease = CoreRelease;
546
+ type release_EnsureOptions = EnsureOptions;
547
+ type release_EnsureResult = EnsureResult;
548
+ type release_ReleaseDescriptor = ReleaseDescriptor;
549
+ declare const release_assetName: typeof assetName;
550
+ declare const release_describeRelease: typeof describeRelease;
551
+ declare const release_ensureCore: typeof ensureCore;
552
+ declare const release_goTarget: typeof goTarget;
553
+ declare const release_readCoreRelease: typeof readCoreRelease;
554
+ declare const release_verifyCached: typeof verifyCached;
555
+ declare namespace release {
556
+ export { type release_CoreRelease as CoreRelease, type release_EnsureOptions as EnsureOptions, type release_EnsureResult as EnsureResult, type release_ReleaseDescriptor as ReleaseDescriptor, release_assetName as assetName, release_describeRelease as describeRelease, release_ensureCore as ensureCore, release_goTarget as goTarget, release_readCoreRelease as readCoreRelease, release_verifyCached as verifyCached };
557
+ }
558
+
474
559
  interface CoreLocation {
475
560
  path: string;
476
- source: 'settings' | 'env' | 'package';
477
- packageName?: string;
478
- /** 解析失败时给用户看的候选清单。 */
479
- searched?: string[];
561
+ source: 'settings' | 'env' | 'cache' | 'download';
562
+ release?: CoreRelease;
563
+ tag?: string;
564
+ /** 本次调用是否真的下载了(用于日志与状态提示)。 */
565
+ downloaded?: boolean;
566
+ sha256?: string;
480
567
  }
481
- declare function platformPackageName(platform?: NodeJS.Platform, arch?: string): string;
482
568
  interface LocateOptions {
483
569
  settings: Settings;
484
570
  env?: NodeJS.ProcessEnv;
485
571
  platform?: NodeJS.Platform;
486
572
  arch?: string;
487
- /** 测试注入:默认 `createRequire(import.meta.url)`。 */
488
- resolve?: (id: string) => string;
573
+ /** 默认 true;false 时只查缓存(设置页探测用)。 */
574
+ allowDownload?: boolean;
575
+ forceDownload?: boolean;
576
+ fetchImpl?: typeof fetch;
577
+ release?: CoreRelease;
489
578
  }
490
- declare function locateCore(options: LocateOptions): CoreLocation;
491
- /** 平台包是否已安装(不执行),供设置页提前展示。 */
492
- declare function packageInstalled(options: LocateOptions): boolean;
579
+ declare function overridePath(settings: Settings, env?: NodeJS.ProcessEnv): {
580
+ path: string;
581
+ source: 'settings' | 'env';
582
+ } | undefined;
583
+ declare function locateCore(options: LocateOptions): Promise<CoreLocation>;
584
+ /** 只读的 Release 视图:不联网、不创建目录,供设置页展示「有没有、从哪来」。 */
585
+ declare function inspectRelease(settings: Settings, options?: {
586
+ env?: NodeJS.ProcessEnv;
587
+ platform?: NodeJS.Platform;
588
+ arch?: string;
589
+ release?: CoreRelease;
590
+ }): {
591
+ descriptor?: ReleaseDescriptor;
592
+ override?: {
593
+ path: string;
594
+ source: 'settings' | 'env';
595
+ };
596
+ error?: string;
597
+ };
493
598
 
494
599
  type locate_CoreLocation = CoreLocation;
495
600
  type locate_LocateOptions = LocateOptions;
496
- declare const locate_PLATFORM_PACKAGE_PREFIX: typeof PLATFORM_PACKAGE_PREFIX;
601
+ declare const locate_inspectRelease: typeof inspectRelease;
497
602
  declare const locate_locateCore: typeof locateCore;
498
- declare const locate_packageInstalled: typeof packageInstalled;
499
- declare const locate_platformPackageName: typeof platformPackageName;
603
+ declare const locate_overridePath: typeof overridePath;
500
604
  declare namespace locate {
501
- export { type locate_CoreLocation as CoreLocation, type locate_LocateOptions as LocateOptions, locate_PLATFORM_PACKAGE_PREFIX as PLATFORM_PACKAGE_PREFIX, locate_locateCore as locateCore, locate_packageInstalled as packageInstalled, locate_platformPackageName as platformPackageName };
605
+ export { type locate_CoreLocation as CoreLocation, type locate_LocateOptions as LocateOptions, locate_inspectRelease as inspectRelease, locate_locateCore as locateCore, locate_overridePath as overridePath };
502
606
  }
503
607
 
504
608
  interface HandshakeResult {
@@ -622,12 +726,31 @@ declare namespace run {
622
726
  export { run_DEFAULT_RUN_TIMEOUT_MS as DEFAULT_RUN_TIMEOUT_MS, type run_RunCoreOptions as RunCoreOptions, type run_RunCoreResult as RunCoreResult, run_buildCoreArgs as buildCoreArgs, run_runCore as runCore };
623
727
  }
624
728
 
729
+ /** Release 分发下的核心视图:用户一眼能看出「版本、下没下、从哪来」。 */
730
+ interface ReleaseStatus {
731
+ repo: string;
732
+ tag: string;
733
+ asset: string;
734
+ url: string;
735
+ checksumsUrl: string;
736
+ cacheDir: string;
737
+ binaryPath: string;
738
+ cached: boolean;
739
+ }
625
740
  interface CoreStatus {
626
741
  state: 'ready' | 'unavailable' | 'incompatible';
627
742
  path?: string;
628
743
  source?: CoreLocation['source'];
629
- packageName: string;
630
- packageInstalled: boolean;
744
+ /** Release 分发状态(有覆盖路径时只有 override,没有 release)。 */
745
+ release?: ReleaseStatus;
746
+ override?: {
747
+ path: string;
748
+ source: 'settings' | 'env';
749
+ };
750
+ download: {
751
+ state: 'cached' | 'missing' | 'override';
752
+ reason?: string;
753
+ };
631
754
  version?: string;
632
755
  commit?: string;
633
756
  ipcSchema?: number;
@@ -665,11 +788,12 @@ declare function detectCoexistence(ownPath: string | undefined, options?: {
665
788
 
666
789
  type core_CoexistenceStatus = CoexistenceStatus;
667
790
  type core_CoreStatus = CoreStatus;
791
+ type core_ReleaseStatus = ReleaseStatus;
668
792
  declare const core_detectCoexistence: typeof detectCoexistence;
669
793
  declare const core_findOnPath: typeof findOnPath;
670
794
  declare const core_inspectCore: typeof inspectCore;
671
795
  declare namespace core {
672
- export { type core_CoexistenceStatus as CoexistenceStatus, type core_CoreStatus as CoreStatus, core_detectCoexistence as detectCoexistence, core_findOnPath as findOnPath, core_inspectCore as inspectCore };
796
+ export { type core_CoexistenceStatus as CoexistenceStatus, type core_CoreStatus as CoreStatus, type core_ReleaseStatus as ReleaseStatus, core_detectCoexistence as detectCoexistence, core_findOnPath as findOnPath, core_inspectCore as inspectCore };
673
797
  }
674
798
 
675
799
  /**
@@ -926,6 +1050,9 @@ interface RunSummary {
926
1050
  logFile?: string;
927
1051
  channel?: 'fifo' | 'tmpfile';
928
1052
  proxy?: boolean;
1053
+ /** 核心从哪来:env/settings 覆盖、缓存、还是本次下载。 */
1054
+ coreSource?: 'settings' | 'env' | 'cache' | 'download';
1055
+ coreTag?: string;
929
1056
  }
930
1057
  interface RunRequestOptions {
931
1058
  dryRun?: boolean;
@@ -1313,4 +1440,4 @@ declare namespace tools {
1313
1440
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
1314
1441
  }
1315
1442
 
1316
- export { channel, config, core, credentials, errors, exec, handshake$1 as handshake, importCli, ipc, locate, paths, preflight, proxy, quickfill, routes, run, runner, schedule, state, tools };
1443
+ export { channel, config, core, credentials, errors, exec, handshake$1 as handshake, importCli, ipc, locate, paths, preflight, proxy, quickfill, release, routes, run, runner, schedule, state, tools };
package/lib/internal.js CHANGED
@@ -12,13 +12,14 @@ import {
12
12
  preflight_exports,
13
13
  proxy_exports,
14
14
  quickfill_exports,
15
+ release_exports,
15
16
  routes_exports,
16
17
  run_exports,
17
18
  runner_exports,
18
19
  schedule_exports,
19
20
  state_exports,
20
21
  tools_exports
21
- } from "./chunk-CWB5L7RW.js";
22
+ } from "./chunk-RXYA5WFW.js";
22
23
  import {
23
24
  exec_exports
24
25
  } from "./chunk-XLIFTAVL.js";
@@ -37,6 +38,7 @@ export {
37
38
  preflight_exports as preflight,
38
39
  proxy_exports as proxy,
39
40
  quickfill_exports as quickfill,
41
+ release_exports as release,
40
42
  routes_exports as routes,
41
43
  run_exports as run,
42
44
  runner_exports as runner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -43,14 +43,6 @@
43
43
  "node": ">=22.20",
44
44
  "dsh": ">=0.2.1-rc.3 <0.3.0-0"
45
45
  },
46
- "optionalDependencies": {
47
- "@morehao/trendradar-core-darwin-arm64": "0.1.0",
48
- "@morehao/trendradar-core-darwin-x64": "0.1.0",
49
- "@morehao/trendradar-core-linux-arm64": "0.1.0",
50
- "@morehao/trendradar-core-linux-x64": "0.1.0",
51
- "@morehao/trendradar-core-win32-arm64": "0.1.0",
52
- "@morehao/trendradar-core-win32-x64": "0.1.0"
53
- },
54
46
  "peerDependencies": {
55
47
  "@deepseek-ai/dsh-tools": ">=0.2.1-rc.3 <0.3.0-0"
56
48
  },
@@ -63,6 +55,10 @@
63
55
  "bundle": {
64
56
  "patch": "./cordis.patch.yml"
65
57
  },
58
+ "core": {
59
+ "repo": "morehao/trendradar",
60
+ "version": "0.1.0"
61
+ },
66
62
  "client": {
67
63
  "platform": "web",
68
64
  "immediately": true,
@@ -80,9 +76,10 @@
80
76
  "typecheck": "tsc --noEmit",
81
77
  "gen:ipc": "node scripts/gen-ipc-types.mjs",
82
78
  "check:contract": "node scripts/sync-contract.mjs --check && node scripts/gen-ipc-types.mjs --check",
79
+ "check:lockfile": "node scripts/check-lockfile.mjs",
83
80
  "test": "node --test \"test/*.test.mjs\"",
84
81
  "pretest": "npm run build",
85
- "check": "npm run typecheck && npm run check:contract && npm run build && npm test",
82
+ "check": "npm run typecheck && npm run check:lockfile && npm run check:contract && npm run build && npm test",
86
83
  "prepublishOnly": "npm run check"
87
84
  },
88
85
  "devDependencies": {