@morehao/dsh-github-trending 0.4.5 → 0.5.0

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