@morehao/dsh-github-trending 0.4.5 → 0.5.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.
@@ -67,9 +67,13 @@ async function execCapture(file, args, options = {}) {
67
67
  const child = spawn(file, args, {
68
68
  cwd: options.cwd,
69
69
  env: options.env ?? process.env,
70
- stdio: ["ignore", "pipe", "pipe"],
70
+ stdio: [options.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
71
71
  windowsHide: true
72
72
  });
73
+ if (options.input !== void 0) {
74
+ child.stdin?.on("error", () => void 0);
75
+ child.stdin?.end(options.input);
76
+ }
73
77
  let stdout = "";
74
78
  let stderr = "";
75
79
  let timedOut = false;
@@ -2,7 +2,7 @@ import {
2
2
  __export,
3
3
  execCapture,
4
4
  sanitizedEnv
5
- } from "./chunk-XLIFTAVL.js";
5
+ } from "./chunk-R7BK7SJK.js";
6
6
 
7
7
  // src/config.ts
8
8
  var config_exports = {};
@@ -13,6 +13,7 @@ __export(config_exports, {
13
13
  PROVIDER_BASE_URLS: () => PROVIDER_BASE_URLS,
14
14
  SETTINGS_SECTIONS: () => SETTINGS_SECTIONS,
15
15
  effectiveBaseURL: () => effectiveBaseURL,
16
+ isEnrich: () => isEnrich,
16
17
  isPeriod: () => isPeriod,
17
18
  isValidTimezone: () => isValidTimezone,
18
19
  mergeSettingsSection: () => mergeSettingsSection,
@@ -27,6 +28,9 @@ var PERIODS = ["daily", "weekly", "monthly"];
27
28
  function isPeriod(value) {
28
29
  return value === "daily" || value === "weekly" || value === "monthly";
29
30
  }
31
+ function isEnrich(value) {
32
+ return value === "none" || value === "repo" || value === "readme";
33
+ }
30
34
  var DEFAULT_SETTINGS = {
31
35
  model: {
32
36
  provider: "deepseek",
@@ -55,6 +59,25 @@ var DEFAULT_SETTINGS = {
55
59
  },
56
60
  github: { authMode: "gh", tokenRef: "GITHUB_TOKEN" },
57
61
  trending: { since: "daily", languages: ["all", "typescript", "python", "go", "rust"], topN: 25 },
62
+ summarize: {
63
+ batchSize: 10,
64
+ maxRetries: 1,
65
+ enrich: "none",
66
+ readmeChars: 800,
67
+ enrichCacheTTLHours: 168,
68
+ enrichConcurrency: 4,
69
+ enrichTimeoutSeconds: 5,
70
+ enrichBudgetSeconds: 8
71
+ },
72
+ releases: {
73
+ includePrerelease: false,
74
+ includeDrafts: false,
75
+ fallbackToTags: true,
76
+ baselineOnFirstRun: true,
77
+ perRepoLimit: 5,
78
+ maxNotesChars: 4e3,
79
+ period: "daily"
80
+ },
58
81
  output: { dir: "trending" },
59
82
  schedule: { enabled: true, time: "08:30:00", timezone: "Asia/Shanghai", periods: ["daily", "weekly", "monthly"] },
60
83
  corePath: "",
@@ -66,12 +89,14 @@ var PROVIDER_BASE_URLS = {
66
89
  anthropic: ""
67
90
  };
68
91
  var CORE_PROVIDERS = ["deepseek", "openai-compatible"];
69
- var SETTINGS_SECTIONS = ["source", "model", "repo", "auth", "scope", "schedule"];
92
+ var SETTINGS_SECTIONS = ["source", "model", "repo", "auth", "scope", "summarize", "releases", "schedule"];
70
93
  function sectionOfField(field) {
71
94
  if (field.startsWith("model.")) return "model";
72
95
  if (field.startsWith("repo.") || field.startsWith("output.") || field.startsWith("pr.")) return "repo";
73
96
  if (field.startsWith("github.")) return "auth";
74
97
  if (field.startsWith("trending.")) return "scope";
98
+ if (field.startsWith("summarize.")) return "summarize";
99
+ if (field.startsWith("releases.")) return "releases";
75
100
  if (field.startsWith("schedule.")) return "schedule";
76
101
  if (field === "corePath" || field === "coreAutoDownload") return "source";
77
102
  return void 0;
@@ -97,6 +122,12 @@ function mergeSettingsSection(stored, incoming, section) {
97
122
  case "scope":
98
123
  next.trending = incoming.trending;
99
124
  break;
125
+ case "summarize":
126
+ next.summarize = incoming.summarize;
127
+ break;
128
+ case "releases":
129
+ next.releases = incoming.releases;
130
+ break;
100
131
  case "schedule":
101
132
  next.schedule = incoming.schedule;
102
133
  break;
@@ -127,11 +158,15 @@ function normalizeSettings(input) {
127
158
  const pr = raw.pr ?? {};
128
159
  const github = raw.github ?? {};
129
160
  const trending = raw.trending ?? {};
161
+ const summarize = raw.summarize ?? {};
162
+ const releases = raw.releases ?? {};
130
163
  const output = raw.output ?? {};
131
164
  const schedule = raw.schedule ?? {};
132
165
  const commitMode = str(repo.commitMode, DEFAULT_SETTINGS.repo.commitMode);
133
166
  const authMode = str(github.authMode, DEFAULT_SETTINGS.github.authMode);
134
167
  const since = str(trending.since, DEFAULT_SETTINGS.trending.since);
168
+ const enrich = str(summarize.enrich, DEFAULT_SETTINGS.summarize.enrich);
169
+ const releasesPeriod = str(releases.period, DEFAULT_SETTINGS.releases.period);
135
170
  return {
136
171
  model: {
137
172
  provider: str(model.provider, DEFAULT_SETTINGS.model.provider),
@@ -168,6 +203,25 @@ function normalizeSettings(input) {
168
203
  languages: strArray(trending.languages, DEFAULT_SETTINGS.trending.languages),
169
204
  topN: Math.round(num(trending.topN, DEFAULT_SETTINGS.trending.topN))
170
205
  },
206
+ summarize: {
207
+ batchSize: Math.round(num(summarize.batchSize, DEFAULT_SETTINGS.summarize.batchSize)),
208
+ maxRetries: Math.round(num(summarize.maxRetries, DEFAULT_SETTINGS.summarize.maxRetries)),
209
+ enrich: isEnrich(enrich) ? enrich : DEFAULT_SETTINGS.summarize.enrich,
210
+ readmeChars: Math.round(num(summarize.readmeChars, DEFAULT_SETTINGS.summarize.readmeChars)),
211
+ enrichCacheTTLHours: Math.round(num(summarize.enrichCacheTTLHours, DEFAULT_SETTINGS.summarize.enrichCacheTTLHours)),
212
+ enrichConcurrency: Math.round(num(summarize.enrichConcurrency, DEFAULT_SETTINGS.summarize.enrichConcurrency)),
213
+ enrichTimeoutSeconds: Math.round(num(summarize.enrichTimeoutSeconds, DEFAULT_SETTINGS.summarize.enrichTimeoutSeconds)),
214
+ enrichBudgetSeconds: Math.round(num(summarize.enrichBudgetSeconds, DEFAULT_SETTINGS.summarize.enrichBudgetSeconds))
215
+ },
216
+ releases: {
217
+ includePrerelease: bool(releases.includePrerelease, DEFAULT_SETTINGS.releases.includePrerelease),
218
+ includeDrafts: bool(releases.includeDrafts, DEFAULT_SETTINGS.releases.includeDrafts),
219
+ fallbackToTags: bool(releases.fallbackToTags, DEFAULT_SETTINGS.releases.fallbackToTags),
220
+ baselineOnFirstRun: bool(releases.baselineOnFirstRun, DEFAULT_SETTINGS.releases.baselineOnFirstRun),
221
+ perRepoLimit: Math.round(num(releases.perRepoLimit, DEFAULT_SETTINGS.releases.perRepoLimit)),
222
+ maxNotesChars: Math.round(num(releases.maxNotesChars, DEFAULT_SETTINGS.releases.maxNotesChars)),
223
+ period: isPeriod(releasesPeriod) ? releasesPeriod : DEFAULT_SETTINGS.releases.period
224
+ },
171
225
  output: {
172
226
  dir: str(output.dir, DEFAULT_SETTINGS.output.dir).trim() || DEFAULT_SETTINGS.output.dir
173
227
  },
@@ -193,7 +247,7 @@ function isValidTimezone(timezone) {
193
247
  }
194
248
  function validateSettings(settings, providers = CORE_PROVIDERS) {
195
249
  const problems = [];
196
- const { model, repo, trending, output, schedule } = settings;
250
+ const { model, repo, trending, summarize, releases, output, schedule } = settings;
197
251
  if (model.provider.trim() === "") {
198
252
  problems.push({ field: "model.provider", code: "CONFIG_INVALID", message: "\u7F3A\u5C11 provider" });
199
253
  } else if (!providers.includes(model.provider)) {
@@ -232,6 +286,27 @@ function validateSettings(settings, providers = CORE_PROVIDERS) {
232
286
  if (!Number.isInteger(trending.topN) || trending.topN <= 0) {
233
287
  problems.push({ field: "trending.topN", code: "CONFIG_INVALID", message: "\u6BCF\u8BED\u8A00\u6761\u6570\u5FC5\u987B\u662F\u6B63\u6574\u6570" });
234
288
  }
289
+ if (!isEnrich(summarize.enrich)) {
290
+ problems.push({ field: "summarize.enrich", code: "CONFIG_INVALID", message: "enrich \u5FC5\u987B\u662F none / repo / readme" });
291
+ }
292
+ if (!Number.isInteger(summarize.batchSize) || summarize.batchSize <= 0) {
293
+ problems.push({ field: "summarize.batchSize", code: "CONFIG_INVALID", message: "batchSize \u5FC5\u987B\u662F\u6B63\u6574\u6570" });
294
+ }
295
+ if (!Number.isInteger(summarize.maxRetries) || summarize.maxRetries < 0) {
296
+ problems.push({ field: "summarize.maxRetries", code: "CONFIG_INVALID", message: "maxRetries \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570" });
297
+ }
298
+ if (!Number.isInteger(summarize.readmeChars) || summarize.readmeChars <= 0) {
299
+ problems.push({ field: "summarize.readmeChars", code: "CONFIG_INVALID", message: "readmeChars \u5FC5\u987B\u662F\u6B63\u6574\u6570" });
300
+ }
301
+ if (!isPeriod(releases.period)) {
302
+ problems.push({ field: "releases.period", code: "CONFIG_INVALID", message: "releases.period \u5FC5\u987B\u662F daily / weekly / monthly" });
303
+ }
304
+ if (!Number.isInteger(releases.perRepoLimit) || releases.perRepoLimit < 1 || releases.perRepoLimit > 20) {
305
+ problems.push({ field: "releases.perRepoLimit", code: "CONFIG_INVALID", message: "perRepoLimit \u5FC5\u987B\u662F 1\u201320 \u7684\u6574\u6570" });
306
+ }
307
+ if (!Number.isInteger(releases.maxNotesChars) || releases.maxNotesChars < 500 || releases.maxNotesChars > 2e4) {
308
+ problems.push({ field: "releases.maxNotesChars", code: "CONFIG_INVALID", message: "maxNotesChars \u5FC5\u987B\u662F 500\u201320000 \u7684\u6574\u6570" });
309
+ }
235
310
  if (output.dir.trim() === "" || path.isAbsolute(output.dir) || output.dir.split(/[\\/]/).includes("..")) {
236
311
  problems.push({ field: "output.dir", code: "CONFIG_INVALID", message: "\u8F93\u51FA\u76EE\u5F55\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u4E14\u4E0D\u542B .." });
237
312
  }
@@ -275,6 +350,27 @@ function toCoreConfig(settings, apiKey, baseURLOverride, period) {
275
350
  since: period ?? settings.trending.since,
276
351
  perLanguage: settings.trending.topN
277
352
  },
353
+ summarize: {
354
+ batchSize: settings.summarize.batchSize,
355
+ maxRetries: settings.summarize.maxRetries,
356
+ enrich: settings.summarize.enrich,
357
+ readmeChars: settings.summarize.readmeChars,
358
+ enrichCacheTTLHours: settings.summarize.enrichCacheTTLHours,
359
+ enrichConcurrency: settings.summarize.enrichConcurrency,
360
+ enrichTimeoutSeconds: settings.summarize.enrichTimeoutSeconds,
361
+ enrichBudgetSeconds: settings.summarize.enrichBudgetSeconds
362
+ },
363
+ releases: {
364
+ // 发版订阅常开:插件侧不再提供开关,是否产出由订阅列表决定。
365
+ enabled: true,
366
+ includePrerelease: settings.releases.includePrerelease,
367
+ includeDrafts: settings.releases.includeDrafts,
368
+ fallbackToTags: settings.releases.fallbackToTags,
369
+ baselineOnFirstRun: settings.releases.baselineOnFirstRun,
370
+ perRepoLimit: settings.releases.perRepoLimit,
371
+ maxNotesChars: settings.releases.maxNotesChars,
372
+ period: settings.releases.period
373
+ },
278
374
  output: {
279
375
  dir: settings.output.dir,
280
376
  branchPrefix: "trending/",
@@ -354,6 +450,7 @@ __export(state_exports, {
354
450
  PREVIEW_MAX_BYTES: () => PREVIEW_MAX_BYTES,
355
451
  Store: () => Store,
356
452
  newRunId: () => newRunId,
453
+ sumTokens: () => sumTokens,
357
454
  writeJsonAtomic: () => writeJsonAtomic
358
455
  });
359
456
  import { mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, appendFileSync, existsSync } from "fs";
@@ -566,6 +663,17 @@ function newRunId(now = /* @__PURE__ */ new Date()) {
566
663
  const random = Math.random().toString(36).slice(2, 8);
567
664
  return `${stamp}-${random}`;
568
665
  }
666
+ function sumTokens(records) {
667
+ let total = 0;
668
+ let seen = false;
669
+ for (const record of records) {
670
+ if (typeof record.tokens === "number" && Number.isFinite(record.tokens)) {
671
+ total += record.tokens;
672
+ seen = true;
673
+ }
674
+ }
675
+ return seen ? total : void 0;
676
+ }
569
677
 
570
678
  // src/errors.ts
571
679
  var errors_exports = {};
@@ -593,7 +701,8 @@ var PLUGIN_CODES = [
593
701
  "MODEL_PROVIDER_UNSUPPORTED",
594
702
  "GITHUB_AUTH",
595
703
  "PUSH_FAILED",
596
- "REPO_DIRTY"
704
+ "REPO_DIRTY",
705
+ "SUBSCRIBE_FAILED"
597
706
  ];
598
707
  var RETRIABLE = {
599
708
  INTERNAL: true,
@@ -626,7 +735,8 @@ var RETRIABLE = {
626
735
  MODEL_NOT_FOUND: false,
627
736
  MODEL_PROVIDER_UNSUPPORTED: false,
628
737
  GITHUB_AUTH: false,
629
- PUSH_FAILED: true
738
+ PUSH_FAILED: true,
739
+ SUBSCRIBE_FAILED: true
630
740
  };
631
741
  function isRetriable(code) {
632
742
  return RETRIABLE[code] === true;
@@ -709,6 +819,8 @@ function hintOf(code, message, reason) {
709
819
  return "PR \u65E0\u6CD5\u81EA\u52A8\u5408\u5E76\uFF08\u5E38\u89C1\u4E8E\u5206\u652F\u4FDD\u62A4\uFF09\uFF1APR \u5DF2\u4FDD\u7559\uFF0C\u9700\u4EBA\u5DE5\u5904\u7406\u3002";
710
820
  case "PUSH_FAILED":
711
821
  return "\u63A8\u9001\u5931\u8D25\uFF1A\u672C\u5730 commit \u5DF2\u4FDD\u7559\uFF0C\u4FEE\u590D\u6743\u9650/\u7F51\u7EDC\u540E\u91CD\u8BD5\u5373\u53EF\u590D\u7528\u3002";
822
+ case "SUBSCRIBE_FAILED":
823
+ return "\u8BA2\u9605\u547D\u4EE4\u5931\u8D25\uFF1A\u6838\u5BF9 owner/repo \u5F62\u6001\uFF08\u5982 vercel/next.js\uFF09\uFF0C\u786E\u8BA4\u4ED3\u5E93\u6709\u5199\u6743\u9650\u540E\u91CD\u8BD5\uFF1B\u8BA2\u9605\u5217\u8868\u968F\u4E0B\u6B21\u8FD0\u884C\u843D\u76D8\u63D0\u4EA4\u3002";
712
824
  case "PR_FAILED":
713
825
  return "\u521B\u5EFA PR \u5931\u8D25\uFF1A\u672C\u5730 commit \u4E0E\u5206\u652F\u5DF2\u4FDD\u7559\u3002";
714
826
  case "FETCH_FAILED":
@@ -802,6 +914,7 @@ __export(ipc_exports, {
802
914
  isVersionInfo: () => isVersionInfo,
803
915
  parseJsonObject: () => parseJsonObject,
804
916
  parseRunResult: () => parseRunResult,
917
+ parseSubscribeResult: () => parseSubscribeResult,
805
918
  parseVersionInfo: () => parseVersionInfo
806
919
  });
807
920
  import { validators } from "@morehao/trendradar-contract";
@@ -871,6 +984,8 @@ function parseRunResult(text, options) {
871
984
  commit: typeof result.commit === "string" ? result.commit : void 0,
872
985
  prUrl: typeof result.prUrl === "string" ? result.prUrl : void 0,
873
986
  skipReason: typeof skipReason === "string" && skipReason !== "" ? skipReason : void 0,
987
+ ...result.usage === void 0 ? {} : { usage: result.usage },
988
+ ...result.releases === void 0 ? {} : { releases: result.releases },
874
989
  error,
875
990
  warnings: Array.isArray(result.warnings) ? result.warnings.filter((item) => typeof item === "string") : [],
876
991
  ...timing(options),
@@ -880,6 +995,35 @@ function parseRunResult(text, options) {
880
995
  function timing(options) {
881
996
  return { startedAt: options.startedAt, finishedAt: options.finishedAt, durationMs: options.durationMs };
882
997
  }
998
+ var REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
999
+ function parseSubscribeResult(text) {
1000
+ const trimmed = text.trim();
1001
+ if (trimmed === "") throw new IpcError("IPC_INVALID", "\u6838\u5FC3 subscribe \u8F93\u51FA\u4E3A\u7A7A");
1002
+ const lines = trimmed.split("\n");
1003
+ const lastLine = (lines[lines.length - 1] ?? "").trim();
1004
+ let envelope;
1005
+ try {
1006
+ envelope = JSON.parse(lastLine);
1007
+ } catch (error2) {
1008
+ const detail = error2 instanceof Error ? error2.message : String(error2);
1009
+ throw new IpcError("IPC_INVALID", `\u6838\u5FC3 subscribe \u672A\u4EE5\u7ED3\u679C\u4FE1\u5C01\u7ED3\u5C3E\uFF1A${detail}`);
1010
+ }
1011
+ if (!isRecord(envelope) || envelope.ipcSchema !== IPC_SCHEMA_VERSION || typeof envelope.ok !== "boolean") {
1012
+ throw new IpcError("IPC_INVALID", "\u6838\u5FC3 subscribe --json \u4E0D\u662F\u5951\u7EA6\u4FE1\u5C01\uFF08ipcSchema=2 / ok\uFF09");
1013
+ }
1014
+ const error = normalizeError(envelope.error);
1015
+ const repos = [];
1016
+ for (const line of lines.slice(0, -1)) {
1017
+ const value = line.trim();
1018
+ if (value === "") continue;
1019
+ if (REPO_PATTERN.test(value)) repos.push(value);
1020
+ }
1021
+ return {
1022
+ ok: envelope.ok === true,
1023
+ repos,
1024
+ ...error === void 0 ? {} : { error }
1025
+ };
1026
+ }
883
1027
 
884
1028
  // src/core/handshake.ts
885
1029
  var handshake_exports = {};
@@ -1698,7 +1842,7 @@ async function defaultGhToken(fresh = false) {
1698
1842
  if (!fresh && ghTokenCache !== void 0 && Date.now() - ghTokenCache.at < GH_TOKEN_TTL_MS) {
1699
1843
  return ghTokenCache.value;
1700
1844
  }
1701
- const { execCapture: execCapture2 } = await import("./exec-33EQXGUA.js");
1845
+ const { execCapture: execCapture2 } = await import("./exec-VMDINNX5.js");
1702
1846
  const result = await execCapture2("gh", ["auth", "token"], { timeoutMs: 2e3 });
1703
1847
  const token = result.code === 0 ? result.stdout.trim() : "";
1704
1848
  const value = token === "" ? void 0 : token;
@@ -2244,7 +2388,8 @@ var runner_exports = {};
2244
2388
  __export(runner_exports, {
2245
2389
  Runner: () => Runner,
2246
2390
  isoWeekName: () => isoWeekName,
2247
- periodFileBase: () => periodFileBase
2391
+ periodFileBase: () => periodFileBase,
2392
+ projectUsage: () => projectUsage
2248
2393
  });
2249
2394
  import { createHash as createHash2 } from "crypto";
2250
2395
  import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync, statSync as statSync5 } from "fs";
@@ -2297,6 +2442,27 @@ function periodFileBase(period, date) {
2297
2442
  if (period === "weekly") return isoWeekName(date);
2298
2443
  return date;
2299
2444
  }
2445
+ function projectUsage(outcome) {
2446
+ const usageTokens = outcome.usage?.totalTokens;
2447
+ const releasesTokens = outcome.releases?.usage?.totalTokens;
2448
+ let tokens;
2449
+ if (typeof usageTokens === "number" || typeof releasesTokens === "number") {
2450
+ tokens = (typeof usageTokens === "number" ? usageTokens : 0) + (typeof releasesTokens === "number" ? releasesTokens : 0);
2451
+ }
2452
+ const releases = outcome.releases === void 0 ? void 0 : {
2453
+ enabled: outcome.releases.enabled,
2454
+ repos: outcome.releases.repos,
2455
+ newReleases: outcome.releases.newReleases,
2456
+ ...outcome.releases.summarized === void 0 ? {} : { summarized: outcome.releases.summarized },
2457
+ ...outcome.releases.degraded === void 0 ? {} : { degraded: outcome.releases.degraded },
2458
+ ...outcome.releases.failed === void 0 ? {} : { failed: outcome.releases.failed },
2459
+ ...outcome.releases.skipped === void 0 ? {} : { skipped: outcome.releases.skipped }
2460
+ };
2461
+ return {
2462
+ ...tokens === void 0 ? {} : { tokens },
2463
+ ...releases === void 0 ? {} : { releases }
2464
+ };
2465
+ }
2300
2466
  var Runner = class {
2301
2467
  active;
2302
2468
  /** 批量运行(一次触发跑多个周期)期间为 true,挡住外部并发触发。 */
@@ -2494,6 +2660,7 @@ var Runner = class {
2494
2660
  ...outcome.prUrl === void 0 ? {} : { prUrl: outcome.prUrl },
2495
2661
  ...outcome.skipReason === void 0 ? {} : { skipReason: outcome.skipReason },
2496
2662
  ...error === void 0 ? {} : { error },
2663
+ ...projectUsage(outcome),
2497
2664
  warnings: outcome.warnings,
2498
2665
  logFile: input.logFile,
2499
2666
  ...input.channel === void 0 ? {} : { channel: input.channel },
@@ -2538,7 +2705,9 @@ var Runner = class {
2538
2705
  const markdown = outcome.files.filter((file) => {
2539
2706
  const ext = path8.extname(file.path).toLowerCase();
2540
2707
  const base = path8.basename(file.path).toLowerCase();
2541
- return ext === ".md" && base !== "readme.md" && base !== "index.md";
2708
+ const normalized = file.path.replace(/\\/g, "/");
2709
+ const inReleases = normalized === "releases" || normalized.startsWith("releases/");
2710
+ return ext === ".md" && base !== "readme.md" && base !== "index.md" && !inReleases;
2542
2711
  });
2543
2712
  if (markdown.length === 0) return skip("no-markdown");
2544
2713
  const expected = period === void 0 ? `${date}.md` : `${periodFileBase(period, date)}.md`;
@@ -2608,6 +2777,8 @@ var Runner = class {
2608
2777
  ...summary.commit === void 0 ? {} : { commit: summary.commit },
2609
2778
  ...summary.prUrl === void 0 ? {} : { prUrl: summary.prUrl },
2610
2779
  ...summary.preview === void 0 ? {} : { preview: summary.preview },
2780
+ ...summary.tokens === void 0 ? {} : { tokens: summary.tokens },
2781
+ ...summary.releases === void 0 ? {} : { releases: summary.releases },
2611
2782
  coreVersion: summary.coreVersion,
2612
2783
  model: { provider: settings.model.provider, model: settings.model.model },
2613
2784
  languages: settings.trending.languages,
@@ -3228,6 +3399,74 @@ async function readDefaultBranch(repoPath, options = {}) {
3228
3399
  return { source: "none", message: "\u672A\u8BFB\u53D6\u5230\u9ED8\u8BA4\u5206\u652F\uFF08\u53EF\u80FD\u662F\u7A7A\u4ED3\u5E93\u6216\u6CA1\u6709 origin\uFF09" };
3229
3400
  }
3230
3401
 
3402
+ // src/host/subscribe.ts
3403
+ var subscribe_exports = {};
3404
+ __export(subscribe_exports, {
3405
+ SUBSCRIBE_TIMEOUT_MS: () => SUBSCRIBE_TIMEOUT_MS,
3406
+ isValidRepo: () => isValidRepo,
3407
+ runSubscribe: () => runSubscribe
3408
+ });
3409
+ import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
3410
+ import os4 from "os";
3411
+ import path11 from "path";
3412
+ var SUBSCRIBE_TIMEOUT_MS = 8e3;
3413
+ var REPO_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
3414
+ function isValidRepo(repo) {
3415
+ return REPO_PATTERN2.test(repo.trim());
3416
+ }
3417
+ function writeTempConfig(settings) {
3418
+ const dir = mkdtempSync2(path11.join(os4.tmpdir(), "dsh-subscribe-"));
3419
+ const file = path11.join(dir, "config.yml");
3420
+ const yaml = `${toYaml(toCoreConfig(settings, ""))}
3421
+ `;
3422
+ writeFileSync2(file, yaml, { mode: 384 });
3423
+ return { file, dir };
3424
+ }
3425
+ function requireUsableConfig(settings) {
3426
+ const problems = validateSettings(settings);
3427
+ if (problems.length > 0) {
3428
+ const first = problems[0];
3429
+ throw new IpcError("CONFIG_INVALID", `\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u7BA1\u7406\u8BA2\u9605\uFF1A${first?.message ?? "\u672A\u77E5\u95EE\u9898"}`);
3430
+ }
3431
+ }
3432
+ async function runSubscribe(input, deps) {
3433
+ const env = deps.env ?? process.env;
3434
+ const settings = deps.store.readSettings();
3435
+ requireUsableConfig(settings);
3436
+ if (input.action !== "list") {
3437
+ const repo = (input.repo ?? "").trim();
3438
+ if (!isValidRepo(repo)) {
3439
+ throw new IpcError("CONFIG_INVALID", `\u4ED3\u5E93\u5FC5\u987B\u662F owner/repo \u5F62\u6001\uFF1A${repo || "(\u7A7A)"}`);
3440
+ }
3441
+ }
3442
+ const corePath = deps.corePath !== void 0 ? deps.corePath : (await locateCore({ settings, env, allowDownload: settings.coreAutoDownload })).path;
3443
+ const { file, dir } = writeTempConfig(settings);
3444
+ try {
3445
+ const request = {
3446
+ ipcSchema: 2,
3447
+ command: "subscribe",
3448
+ action: input.action,
3449
+ ...input.action === "list" ? {} : { repo: (input.repo ?? "").trim() },
3450
+ configPath: file
3451
+ };
3452
+ const result = await execCapture(corePath, ["subscribe", "--json"], {
3453
+ env: sanitizedEnv(env),
3454
+ input: JSON.stringify(request),
3455
+ timeoutMs: SUBSCRIBE_TIMEOUT_MS
3456
+ });
3457
+ if (result.timedOut) {
3458
+ throw new IpcError("SUBSCRIBE_FAILED", `\u8BA2\u9605\u547D\u4EE4\u8D85\u65F6\uFF08${Math.round(SUBSCRIBE_TIMEOUT_MS / 1e3)} s\uFF09`);
3459
+ }
3460
+ if (result.stdout.trim() === "") {
3461
+ const detail = result.stderr.trim();
3462
+ throw new IpcError("SUBSCRIBE_FAILED", `\u8BA2\u9605\u547D\u4EE4\u65E0\u8F93\u51FA${detail === "" ? "" : `\uFF1A${detail}`}`);
3463
+ }
3464
+ return parseSubscribeResult(result.stdout);
3465
+ } finally {
3466
+ rmSync2(dir, { recursive: true, force: true });
3467
+ }
3468
+ }
3469
+
3231
3470
  // src/host/routes.ts
3232
3471
  var routes_exports = {};
3233
3472
  __export(routes_exports, {
@@ -3379,6 +3618,28 @@ async function dispatch(method, pathname, url, request, deps) {
3379
3618
  const repoPath = url.searchParams.get("path") ?? settings.repo.path;
3380
3619
  return { repo: await readDefaultBranch(repoPath, { env }) };
3381
3620
  }
3621
+ if (method === "GET" && pathname === "/subscriptions") {
3622
+ const result = await runSubscribe({ action: "list" }, { store: deps.store, env });
3623
+ if (!result.ok) {
3624
+ throw new IpcError("SUBSCRIBE_FAILED", result.error?.message ?? "\u8BFB\u53D6\u8BA2\u9605\u5217\u8868\u5931\u8D25");
3625
+ }
3626
+ return { repos: result.repos };
3627
+ }
3628
+ if (method === "POST" && pathname === "/subscriptions") {
3629
+ const body = await readBody2(request);
3630
+ const action = body.action === "add" || body.action === "remove" ? body.action : void 0;
3631
+ if (action === void 0) throw new IpcError("CONFIG_INVALID", "action \u5FC5\u987B\u662F add \u6216 remove");
3632
+ const repos = Array.isArray(body.repos) ? body.repos.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => item.trim()) : typeof body.repo === "string" && body.repo.trim() !== "" ? [body.repo.trim()] : [];
3633
+ if (repos.length === 0) throw new IpcError("CONFIG_INVALID", "\u5FC5\u987B\u63D0\u4F9B\u8981\u64CD\u4F5C\u7684 owner/repo");
3634
+ for (const repo of repos) {
3635
+ const result = await runSubscribe({ action, repo }, { store: deps.store, env });
3636
+ if (!result.ok) {
3637
+ throw new IpcError("SUBSCRIBE_FAILED", result.error?.message ?? `${action} ${repo} \u5931\u8D25`);
3638
+ }
3639
+ }
3640
+ const latest = await runSubscribe({ action: "list" }, { store: deps.store, env });
3641
+ return { repos: latest.ok ? latest.repos : [] };
3642
+ }
3382
3643
  if (method === "POST" && pathname === "/github/test") {
3383
3644
  const body = await readBody2(request);
3384
3645
  const candidate = body.settings === void 0 ? settings : normalizeSettings(body.settings);
@@ -3495,7 +3756,12 @@ async function statePayload(deps, settings) {
3495
3756
  lastResult: state.lastResult ?? null,
3496
3757
  pendingPr,
3497
3758
  ...pendingPrState === void 0 ? {} : { pendingPrState },
3498
- history
3759
+ history,
3760
+ /** 历史里全部运行的 token 累计(读时求和,不落盘);全无数据则省略。 */
3761
+ ...(() => {
3762
+ const totalTokens = sumTokens(history);
3763
+ return totalTokens === void 0 ? {} : { totalTokens };
3764
+ })()
3499
3765
  },
3500
3766
  model: {
3501
3767
  mode: settings.model.mode,
@@ -3664,6 +3930,13 @@ function createTrendingRunTool(runner) {
3664
3930
  const head = summary.ok ? summary.skipped ? "\u65E0\u53D8\u66F4\uFF08skipped\uFF09" : summary.dryRun ? "\u8BD5\u8DD1\u5B8C\u6210" : "\u8FD0\u884C\u6210\u529F" : `\u8FD0\u884C\u5931\u8D25\uFF1A${summary.error?.code ?? "INTERNAL"}`;
3665
3931
  lines.push(`${summary.ok ? "\u2713" : "\u2717"} [${periodLabel}] ${head}`);
3666
3932
  lines.push(`\xB7 \u6838\u5FC3 ${summary.coreVersion || "(\u672A\u77E5)"} \xB7 ${formatDuration(summary.durationMs)} \xB7 ${summary.files} \u4E2A\u6587\u4EF6`);
3933
+ if (summary.tokens !== void 0) {
3934
+ const parts = [`\u672C\u6B21 tokens ${summary.tokens.toLocaleString("en-US")}`];
3935
+ if (summary.releases !== void 0 && summary.releases.enabled) {
3936
+ parts.push(`\u53D1\u7248 ${summary.releases.newReleases} \u4E2A\u65B0\u7248\u672C / ${summary.releases.repos} \u4E2A\u4ED3\u5E93`);
3937
+ }
3938
+ lines.push(`\xB7 ${parts.join(" \xB7 ")}`);
3939
+ }
3667
3940
  if (summary.contentHash !== void 0) lines.push(`\xB7 contentHash ${summary.contentHash.slice(0, 12)}`);
3668
3941
  if (summary.branch !== void 0) lines.push(`\xB7 \u5206\u652F ${summary.branch}`);
3669
3942
  if (summary.prUrl !== void 0) lines.push(`\xB7 PR ${summary.prUrl}`);
@@ -3686,6 +3959,8 @@ function createTrendingRunTool(runner) {
3686
3959
  dryRun: summary.dryRun,
3687
3960
  durationMs: summary.durationMs,
3688
3961
  files: summary.files,
3962
+ ...summary.tokens === void 0 ? {} : { tokens: summary.tokens },
3963
+ ...summary.releases === void 0 ? {} : { releases: summary.releases },
3689
3964
  ...summary.branch === void 0 ? {} : { branch: summary.branch },
3690
3965
  ...summary.prUrl === void 0 ? {} : { prUrl: summary.prUrl },
3691
3966
  ...summary.error === void 0 ? {} : { errorCode: summary.error.code, errorMessage: summary.error.message }
@@ -3695,6 +3970,10 @@ function createTrendingRunTool(runner) {
3695
3970
  dryRun: base.dryRun,
3696
3971
  durationMs: summaries.reduce((sum, summary) => sum + summary.durationMs, 0),
3697
3972
  files: summaries.reduce((sum, summary) => sum + summary.files, 0),
3973
+ ...(() => {
3974
+ const total = summaries.reduce((sum, summary) => sum + (summary.tokens ?? 0), 0);
3975
+ return summaries.some((summary) => summary.tokens !== void 0) ? { tokens: total } : {};
3976
+ })(),
3698
3977
  warnings: summaries.flatMap((summary) => summary.warnings),
3699
3978
  ...primary?.error === void 0 ? {} : { errorCode: primary.error.code, errorMessage: primary.error.message, errorHint: primary.error.hint },
3700
3979
  markdown: lines.join("\n")
@@ -3772,6 +4051,7 @@ export {
3772
4051
  pending_pr_exports,
3773
4052
  preflight_exports,
3774
4053
  repo_exports,
4054
+ subscribe_exports,
3775
4055
  ROUTE_PREFIX,
3776
4056
  createRoutes,
3777
4057
  routes_exports,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  execCapture,
3
3
  sanitizedEnv
4
- } from "./chunk-XLIFTAVL.js";
4
+ } from "./chunk-R7BK7SJK.js";
5
5
  export {
6
6
  execCapture,
7
7
  sanitizedEnv
package/lib/index.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  createTrendingRunTool,
9
9
  createUpdateService,
10
10
  readPluginInfo
11
- } from "./chunk-GBXP2NGW.js";
12
- import "./chunk-XLIFTAVL.js";
11
+ } from "./chunk-R7EUBUTS.js";
12
+ import "./chunk-R7BK7SJK.js";
13
13
 
14
14
  // src/index.ts
15
15
  var name = "dsh-github-trending";