@morehao/dsh-github-trending 0.8.1 → 0.8.3

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.8.3] - 2026-11-02
4
+
5
+ ### Fixes
6
+
7
+ - **auth**: fall back to device flow when client_secret is missing (#75)
8
+
9
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.8.2...v0.8.3
10
+
11
+ ## [v0.8.2] - 2026-11-02
12
+
13
+ ### Fixes
14
+
15
+ - **auth**: register github flow after service is ready (#72)
16
+
17
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.8.1...v0.8.2
18
+
3
19
  ## [v0.7.11] - 2026-11-01
4
20
 
5
21
  ### Refactors
package/README.md CHANGED
@@ -59,11 +59,13 @@ GitHub 趋势榜的 **DeepSeek Harness 插件**:按日 / 周 / 月周期定时
59
59
 
60
60
  认证分区支持三种方式(可随时切换):
61
61
 
62
- - **浏览器登录(默认推荐)**:点「用 GitHub 登录」在浏览器完成授权,无需手工粘贴 token。走 OAuth PKCE + 本机回环回调(或设备码),token 由 DSH 凭据记录安全保存,不落 `settings.json`。插件内置了公开的 OAuth App client_id(client_id 属公开标识、非机密;纯 PKCE 不使用 client_secret),开箱即用。
62
+ - **浏览器登录(默认推荐)**:点「用 GitHub 登录」在浏览器完成授权,无需手工粘贴 token,token 由 DSH 凭据记录安全保存、不落 `settings.json`。插件内置了公开的 OAuth App client_id,开箱即用;GitHub 的 web flow 换 token 仍然**必须带 client_secret**(PKCE 只是叠加保护),而客户端不能内置机密,所以默认走**设备码**:点一下后在打开的 GitHub 页面输入验证码即可。若你自带 OAuth App,可在插件配置里同时给 `githubClientId` + `githubClientSecret`,就会改用本机回环回调(不用手输验证码)。
63
63
  - **本机 gh**:复用你终端里 `gh auth login` 的登录态。
64
64
  - **PAT**:手工填 Personal Access Token(存到凭据引用 / 环境变量)。
65
65
 
66
- > 浏览器登录需要核心提供 `authorization` 服务;若当前核心版本不含该能力,「浏览器登录」会提示不可用并引导改用 gh / PAT。
66
+ > 浏览器登录需要核心提供 `authorization` 服务,并且 GitHub flow 注册成功才算可用;两者任一不满足时,设置页直接提示不可用并引导改用 gh / PAT,而不是给一个点了没反应的按钮。宿主启动时服务可能晚于插件就绪,插件会等服务就绪再注册,并在读状态时再兜底一次。
67
+ >
68
+ > 设备码 / 授权页默认尝试自动打开;若被浏览器的弹窗拦截,状态行里始终有可点的「打开授权页」链接、验证码与「复制链接」。
67
69
 
68
70
  ### 订阅
69
71
 
package/client.js CHANGED
@@ -125,17 +125,21 @@ var DICT = {
125
125
  "auth.gh": "本机 gh",
126
126
  "auth.browser": "浏览器登录",
127
127
  "auth.gh.hint": "使用本机 gh 登录态:终端执行 `gh auth login` 完成登录,再点「测试认证」。",
128
- "auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。",
128
+ "auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。默认走设备码:点一下后按提示输入验证码。",
129
129
  "auth.browser.signin": "用 GitHub 登录",
130
130
  "auth.browser.waiting": "等待浏览器授权…",
131
131
  "auth.browser.code": "在打开的页面输入验证码:",
132
132
  "auth.browser.cancel": "取消",
133
133
  "auth.browser.done": "已登录",
134
134
  "auth.browser.device": "用设备码登录",
135
- "auth.browser.unavailable": "当前环境未配置 GitHub OAuth client_id,浏览器登录不可用:请改用「本机 gh」或 PAT。",
136
- "auth.browser.openHint": "若浏览器未自动打开,请手动打开发起授权,或复制链接:",
135
+ "auth.browser.unavailable": "宿主的 authorization 服务不可用(或 GitHub 授权流程没注册成功):请重试一次;仍不行就改用「本机 gh」或 PAT。",
136
+ "auth.browser.openHint": "若浏览器没有自动打开,点下面的链接完成授权,或复制链接:",
137
+ "auth.browser.open": "打开授权页",
137
138
  "auth.browser.copy": "复制链接",
138
139
  "auth.browser.copied": "授权链接已复制",
140
+ "auth.browser.failed": "登录失败",
141
+ "auth.browser.failed.detail": "登录失败:{message}",
142
+ "auth.browser.failed.hint": "请重试一次;仍失败可改用「本机 gh」或 PAT。",
139
143
  "auth.pat.hint": "在 GitHub → Settings → Developer settings 生成 Personal Access Token(勾选 public_repo),填到下方引用或环境变量。",
140
144
  "auth.tokenRef": "凭据引用",
141
145
  "auth.tokenRef.hint": "凭据引用名 / 环境变量名",
@@ -666,17 +670,21 @@ var DICT = {
666
670
  "auth.gh": "Local gh",
667
671
  "auth.browser": "Browser login",
668
672
  "auth.gh.hint": "Uses your local gh login: run `gh auth login` in a terminal, then click Test authentication.",
669
- "auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste.",
673
+ "auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste. Uses a device code by default: enter the code on the GitHub page after clicking.",
670
674
  "auth.browser.signin": "Sign in with GitHub",
671
675
  "auth.browser.waiting": "Waiting for browser authorization…",
672
676
  "auth.browser.code": "Enter this code on the opened page:",
673
677
  "auth.browser.cancel": "Cancel",
674
678
  "auth.browser.done": "Signed in",
675
679
  "auth.browser.device": "Use a device code",
676
- "auth.browser.unavailable": "No GitHub OAuth client_id configured in this environment, so browser sign-in is unavailable: use Local gh or PAT instead.",
677
- "auth.browser.openHint": "If the browser did not open, open the authorization page manually, or copy the link:",
680
+ "auth.browser.unavailable": "The host authorization service is unavailable (or the GitHub flow did not register): retry once, then fall back to Local gh or a PAT.",
681
+ "auth.browser.openHint": "If the browser did not open, use the link below to finish signing in, or copy the link:",
682
+ "auth.browser.open": "Open authorization page",
678
683
  "auth.browser.copy": "Copy link",
679
684
  "auth.browser.copied": "Authorization link copied",
685
+ "auth.browser.failed": "Sign-in failed",
686
+ "auth.browser.failed.detail": "Sign-in failed: {message}",
687
+ "auth.browser.failed.hint": "Retry once; if it keeps failing, use Local gh or a PAT instead.",
680
688
  "auth.pat.hint": "Create a Personal Access Token under GitHub → Settings → Developer settings (scope public_repo) and put it in the ref below or an env var.",
681
689
  "auth.tokenRef": "Credential ref",
682
690
  "auth.tokenRef.hint": "credential ref / env var name",
@@ -1340,6 +1348,8 @@ var CSS = `
1340
1348
  .dshgt-ready-extra{color:var(--gt-muted);font-size:12px;margin-left:7px;}
1341
1349
  .dshgt-linkbtn{border:0;background:none;color:var(--gt-accent);font-family:inherit;font-size:12.5px;padding:6px 2px;border-radius:4px;cursor:pointer;}
1342
1350
  .dshgt-linkbtn:hover{text-decoration:underline;}
1351
+ .dshgt-link{color:var(--gt-accent);text-decoration:none;font-size:12.5px;word-break:break-all;}
1352
+ .dshgt-link:hover{text-decoration:underline;}
1343
1353
  .dshgt-week{display:flex;gap:6px;align-items:stretch;}
1344
1354
  .dshgt-day{flex:1;min-width:0;border:1px solid var(--gt-border);border-radius:8px;padding:8px;background:var(--gt-card);display:flex;flex-direction:column;gap:5px;text-align:left;font-family:inherit;color:inherit;}
1345
1355
  .dshgt-day-clickable{cursor:pointer;}
@@ -2249,14 +2259,25 @@ function SettingsView(props) {
2249
2259
  };
2250
2260
  const startSignIn = async (method) => {
2251
2261
  try {
2252
- const view = await beginGithubSignIn(method);
2262
+ let view = await beginGithubSignIn(method);
2263
+ if (view.phase === "idle") view = await getGithubSignInStatus();
2253
2264
  setSignIn(view);
2265
+ if (view.phase === "error") {
2266
+ notify("err", t("auth.browser.failed.detail", { message: view.error ?? "" }));
2267
+ return;
2268
+ }
2254
2269
  if (view.phase === "waiting-browser" && view.authorizeUrl !== void 0) {
2255
2270
  try {
2256
2271
  window.open(view.authorizeUrl, "_blank", "noopener,noreferrer");
2257
2272
  } catch {
2258
2273
  }
2259
2274
  }
2275
+ if (view.phase === "waiting-device" && view.verificationUri !== void 0) {
2276
+ try {
2277
+ window.open(view.verificationUri, "_blank", "noopener,noreferrer");
2278
+ } catch {
2279
+ }
2280
+ }
2260
2281
  } catch (error) {
2261
2282
  notify("err", error instanceof Error ? error.message : String(error));
2262
2283
  }
@@ -2333,6 +2354,7 @@ function SettingsView(props) {
2333
2354
  const isGh = draft.github.authMode === "gh";
2334
2355
  const isBrowser = draft.github.authMode === "browser";
2335
2356
  const browserAvailable = state.github.browserAvailable === true;
2357
+ const loopbackAvailable = state.github.loopbackAvailable === true;
2336
2358
  const sections = [
2337
2359
  { id: "source", label: t("st.source") },
2338
2360
  { id: "model", label: t("st.model") },
@@ -2527,12 +2549,12 @@ function SettingsView(props) {
2527
2549
  {
2528
2550
  small: true,
2529
2551
  variant: "primary",
2530
- onClick: () => void startSignIn("browser"),
2552
+ onClick: () => void startSignIn(loopbackAvailable ? "browser" : "device"),
2531
2553
  disabled: signInActive
2532
2554
  },
2533
2555
  signIn.phase === "done" && signIn.login !== void 0 ? `${t("auth.browser.done")} · @${signIn.login}` : signInActive ? t("auth.browser.waiting") : t("auth.browser.signin")
2534
2556
  ) : h("span", { className: "dshgt-hint" }, t("auth.browser.unavailable")),
2535
- browserAvailable ? h(Btn, { small: true, variant: "ghost", onClick: () => void startSignIn("device"), disabled: signInActive }, t("auth.browser.device")) : null,
2557
+ browserAvailable && loopbackAvailable ? h(Btn, { small: true, variant: "ghost", onClick: () => void startSignIn("device"), disabled: signInActive }, t("auth.browser.device")) : null,
2536
2558
  signInActive ? h(Btn, { small: true, variant: "ghost", onClick: () => void cancelSignIn() }, t("auth.browser.cancel")) : null
2537
2559
  ) : h(
2538
2560
  Row,
@@ -2542,8 +2564,10 @@ function SettingsView(props) {
2542
2564
  }) }),
2543
2565
  h("span", { className: "dshgt-hint" }, t("auth.tokenRef.hint"))
2544
2566
  ),
2545
- // 浏览器登录进行中的状态行:授权页链接(可复制)+ 设备码。
2546
- isBrowser && signIn.phase !== "idle" && signIn.phase !== "done" ? h(
2567
+ // 浏览器登录失败:必须显式说出来,否则「点了没反应」又回来了。
2568
+ isBrowser && signIn.phase === "error" ? h(Alert, { kind: "err", title: t("auth.browser.failed") }, signIn.error ?? t("auth.browser.failed.hint")) : null,
2569
+ // 浏览器登录进行中的状态行:可点的授权页链接 + 复制兜底 + 设备码。
2570
+ isBrowser && (signIn.phase === "waiting-browser" || signIn.phase === "waiting-device" || signIn.phase === "exchanging") ? h(
2547
2571
  "div",
2548
2572
  { className: "dshgt-hint" },
2549
2573
  signIn.authorizeUrl !== void 0 ? h(
@@ -2551,8 +2575,19 @@ function SettingsView(props) {
2551
2575
  { className: "dshgt-desc" },
2552
2576
  h("span", null, t("auth.browser.openHint")),
2553
2577
  " ",
2578
+ h(
2579
+ "a",
2580
+ { className: "dshgt-link", href: signIn.authorizeUrl, target: "_blank", rel: "noreferrer" },
2581
+ t("auth.browser.open")
2582
+ ),
2583
+ " ",
2554
2584
  h(Btn, { small: true, variant: "ghost", onClick: () => copyAuthorizeLink() }, t("auth.browser.copy"))
2555
2585
  ) : null,
2586
+ signIn.verificationUri !== void 0 ? h(
2587
+ "div",
2588
+ { className: "dshgt-desc" },
2589
+ h("a", { className: "dshgt-link", href: signIn.verificationUri, target: "_blank", rel: "noreferrer" }, signIn.verificationUri)
2590
+ ) : null,
2556
2591
  signIn.userCode !== void 0 ? h("div", { className: "dshgt-mono" }, `${t("auth.browser.code")} ${signIn.userCode}`) : null
2557
2592
  ) : null,
2558
2593
  h(
@@ -3538,9 +3538,14 @@ __export(github_auth_exports, {
3538
3538
  buildAuthorizeUrl: () => buildAuthorizeUrl,
3539
3539
  cancelGithubSignIn: () => cancelGithubSignIn,
3540
3540
  createPkce: () => createPkce,
3541
+ ensureGithubFlow: () => ensureGithubFlow,
3542
+ isGithubFlowRegistered: () => isGithubFlowRegistered,
3543
+ isLoopbackSignInAvailable: () => isLoopbackSignInAvailable,
3541
3544
  registerGithubFlow: () => registerGithubFlow,
3542
3545
  resetAttemptForTest: () => resetAttemptForTest,
3543
3546
  resolveClientId: () => resolveClientId,
3547
+ resolveClientSecret: () => resolveClientSecret,
3548
+ resolveMethod: () => resolveMethod,
3544
3549
  verifyState: () => verifyState
3545
3550
  });
3546
3551
  import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
@@ -3551,6 +3556,16 @@ function resolveClientId(deps) {
3551
3556
  const fromConfig = deps.clientId;
3552
3557
  return fromConfig !== void 0 && fromConfig !== "" ? fromConfig : DEFAULT_GITHUB_CLIENT_ID;
3553
3558
  }
3559
+ function resolveClientSecret(deps) {
3560
+ const secret = deps.clientSecret;
3561
+ return secret !== void 0 && secret !== "" ? secret : void 0;
3562
+ }
3563
+ function isLoopbackSignInAvailable(deps) {
3564
+ return resolveClientSecret(deps) !== void 0;
3565
+ }
3566
+ function resolveMethod(deps, requested) {
3567
+ return requested === "browser" && !isLoopbackSignInAvailable(deps) ? "device" : requested;
3568
+ }
3554
3569
  var AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
3555
3570
  var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
3556
3571
  var DEVICE_CODE_URL = "https://github.com/login/device/code";
@@ -3587,21 +3602,29 @@ function buildAuthorizeUrl(params) {
3587
3602
  return url.toString();
3588
3603
  }
3589
3604
  async function exchangeCode(fetchImpl, endpoint, params, signal) {
3605
+ const form = new URLSearchParams({
3606
+ client_id: params.clientId,
3607
+ code: params.code,
3608
+ code_verifier: params.verifier,
3609
+ redirect_uri: params.redirectUri
3610
+ });
3611
+ if (params.clientSecret !== void 0) form.set("client_secret", params.clientSecret);
3590
3612
  const response = await fetchImpl(endpoint, {
3591
3613
  method: "POST",
3592
3614
  headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
3593
- body: new URLSearchParams({
3594
- client_id: params.clientId,
3595
- code: params.code,
3596
- code_verifier: params.verifier,
3597
- redirect_uri: params.redirectUri
3598
- }).toString(),
3615
+ body: form.toString(),
3599
3616
  signal
3600
3617
  });
3601
3618
  if (!response.ok) throw new Error(`GitHub access_token HTTP ${response.status}`);
3602
3619
  const body = await response.json();
3603
3620
  if (typeof body.access_token !== "string" || body.access_token === "") {
3604
- throw new Error(`GitHub access_token \u672A\u8FD4\u56DE token${typeof body.error === "string" ? `\uFF08${body.error}\uFF09` : ""}`);
3621
+ const error = typeof body.error === "string" ? body.error : void 0;
3622
+ if (error === "incorrect_client_credentials") {
3623
+ throw new Error(
3624
+ params.clientSecret === void 0 ? "GitHub \u7F51\u9875\u6388\u6743\u9700\u8981 client_secret\uFF08incorrect_client_credentials\uFF09\uFF0C\u5185\u7F6E\u7684\u516C\u5F00 client_id \u65E0\u6CD5\u5B8C\u6210\u8FD9\u4E00\u6B65\uFF1A\u8BF7\u6539\u7528\u8BBE\u5907\u7801\u767B\u5F55\uFF0C\u6216\u914D\u7F6E\u81EA\u5DF1\u7684 OAuth App client_id + client_secret\u3002" : "GitHub \u8BA4\u4E3A\u8FD9\u4E2A OAuth App \u7684 client_id / client_secret \u4E0D\u6B63\u786E\uFF08incorrect_client_credentials\uFF09\uFF1A\u8BF7\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\uFF0C\u6216\u6539\u7528\u8BBE\u5907\u7801\u767B\u5F55\u3002"
3625
+ );
3626
+ }
3627
+ throw new Error(`GitHub access_token \u672A\u8FD4\u56DE token${error === void 0 ? "" : `\uFF08${error}\uFF09`}`);
3605
3628
  }
3606
3629
  return {
3607
3630
  token: body.access_token,
@@ -3629,6 +3652,10 @@ async function runBrowser(deps, session) {
3629
3652
  }
3630
3653
  const clientId = resolveClientId(deps);
3631
3654
  if (clientId === void 0 || clientId === "") throw new Error("\u7F3A\u5C11 GitHub OAuth client_id");
3655
+ const clientSecret = resolveClientSecret(deps);
3656
+ if (clientSecret === void 0) {
3657
+ throw new Error("\u56DE\u73AF\u767B\u5F55\u4E0D\u53EF\u7528\uFF1A\u6CA1\u6709\u914D\u7F6E GitHub OAuth App \u7684 client_secret\uFF0C\u8BF7\u6539\u7528\u8BBE\u5907\u7801\u767B\u5F55");
3658
+ }
3632
3659
  const fetchImpl = deps.fetchImpl ?? fetch;
3633
3660
  const endpoints = normalizeEndpoints(deps.endpoints);
3634
3661
  const scope = deps.scope ?? DEFAULT_SCOPE;
@@ -3679,7 +3706,12 @@ async function runBrowser(deps, session) {
3679
3706
  session.notify?.({ message: "Continue in your browser to sign in to GitHub.", url: authorizeUrl });
3680
3707
  const code = await withAbort(codePromise, session.signal, () => rejectCode(new Error("authorization cancelled")));
3681
3708
  updateCurrent({ phase: "exchanging" });
3682
- const exchanged = await exchangeCode(fetchImpl, endpoints.accessToken, { clientId, code, verifier, redirectUri }, session.signal);
3709
+ const exchanged = await exchangeCode(
3710
+ fetchImpl,
3711
+ endpoints.accessToken,
3712
+ { clientId, clientSecret, code, verifier, redirectUri },
3713
+ session.signal
3714
+ );
3683
3715
  const login = await fetchLogin(fetchImpl, endpoints.user, exchanged.token, session.signal);
3684
3716
  const payload = {
3685
3717
  version: 1,
@@ -3709,7 +3741,10 @@ async function runDevice(deps, session) {
3709
3741
  if (!startResponse.ok) throw new Error(`GitHub device/code HTTP ${startResponse.status}`);
3710
3742
  const start = await startResponse.json();
3711
3743
  if (typeof start.device_code !== "string" || typeof start.user_code !== "string" || typeof start.verification_uri !== "string") {
3712
- throw new Error("GitHub device/code \u8FD4\u56DE\u4E0D\u5B8C\u6574");
3744
+ const error = typeof start.error === "string" ? start.error : void 0;
3745
+ throw new Error(
3746
+ error === "device_flow_disabled" ? "GitHub \u6CA1\u6709\u4E3A\u8FD9\u4E2A OAuth App \u542F\u7528\u8BBE\u5907\u7801\u767B\u5F55\uFF1A\u8BF7\u5230 OAuth App \u8BBE\u7F6E\u52FE\u9009 Device flow\uFF0C\u6216\u6539\u7528\u300C\u672C\u673A gh\u300D/ PAT\u3002" : `GitHub device/code \u8FD4\u56DE\u4E0D\u5B8C\u6574${error === void 0 ? "" : `\uFF08${error}\uFF09`}`
3747
+ );
3713
3748
  }
3714
3749
  const deviceCode = start.device_code;
3715
3750
  const intervalSec = typeof start.interval === "number" && start.interval > 0 ? start.interval : 5;
@@ -3764,13 +3799,8 @@ async function runDevice(deps, session) {
3764
3799
  }
3765
3800
  throw new Error("GitHub device flow \u8D85\u65F6");
3766
3801
  }
3767
- function registerGithubFlow(deps) {
3768
- const authorization = deps.get("authorization");
3769
- if (authorization === void 0 || typeof authorization.registerFlow !== "function") {
3770
- deps.logger?.("auth.flow.skipped", { reason: "no-authorization-service" });
3771
- return;
3772
- }
3773
- authorization.registerFlow({
3802
+ function flowDefinition(deps) {
3803
+ return {
3774
3804
  key: GITHUB_RECORD_KEY2,
3775
3805
  label: "GitHub",
3776
3806
  methods: [
@@ -3778,44 +3808,105 @@ function registerGithubFlow(deps) {
3778
3808
  { id: "device", label: "Use a device code" }
3779
3809
  ],
3780
3810
  run: (session) => session.method === "device" ? runDevice(deps, session) : runBrowser(deps, session)
3781
- });
3811
+ };
3812
+ }
3813
+ var registeredOn = /* @__PURE__ */ new WeakSet();
3814
+ var failedOn = /* @__PURE__ */ new WeakSet();
3815
+ function isGithubFlowRegistered(deps) {
3816
+ const authorization = deps.get("authorization");
3817
+ if (authorization === void 0) return false;
3818
+ if (typeof authorization.describe === "function") {
3819
+ try {
3820
+ return authorization.describe(GITHUB_RECORD_KEY2) !== void 0;
3821
+ } catch {
3822
+ return registeredOn.has(authorization);
3823
+ }
3824
+ }
3825
+ return registeredOn.has(authorization);
3826
+ }
3827
+ function ensureGithubFlow(deps) {
3828
+ const authorization = deps.get("authorization");
3829
+ if (authorization === void 0 || typeof authorization.registerFlow !== "function") return false;
3830
+ if (isGithubFlowRegistered(deps)) {
3831
+ registeredOn.add(authorization);
3832
+ return true;
3833
+ }
3834
+ if (failedOn.has(authorization)) return false;
3835
+ try {
3836
+ authorization.registerFlow(flowDefinition(deps));
3837
+ } catch (error) {
3838
+ failedOn.add(authorization);
3839
+ deps.logger?.("auth.flow.failed", {
3840
+ key: GITHUB_RECORD_KEY2,
3841
+ message: error instanceof Error ? error.message : String(error)
3842
+ });
3843
+ return isGithubFlowRegistered(deps);
3844
+ }
3845
+ registeredOn.add(authorization);
3782
3846
  deps.logger?.("auth.flow.registered", { key: GITHUB_RECORD_KEY2 });
3847
+ return true;
3848
+ }
3849
+ function registerGithubFlow(deps) {
3850
+ if (!ensureGithubFlow(deps)) deps.logger?.("auth.flow.skipped", { reason: "no-authorization-service" });
3783
3851
  }
3852
+ var START_TIMEOUT_MS = 1500;
3853
+ var START_POLL_MS = 10;
3784
3854
  async function beginGithubSignIn(deps, method = "browser") {
3785
3855
  const authorization = deps.get("authorization");
3786
3856
  if (authorization === void 0 || typeof authorization.begin !== "function") {
3787
3857
  throw new Error("authorization \u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8FDB\u884C\u6D4F\u89C8\u5668\u767B\u5F55");
3788
3858
  }
3859
+ if (!ensureGithubFlow(deps)) {
3860
+ throw new Error("authorization \u670D\u52A1\u5C1A\u672A\u5C31\u7EEA\uFF0CGitHub \u6388\u6743\u6D41\u7A0B\u6CA1\u6709\u6CE8\u518C\u6210\u529F\uFF1A\u8BF7\u7A0D\u540E\u91CD\u8BD5");
3861
+ }
3789
3862
  if (current !== void 0 && (current.phase === "waiting-browser" || current.phase === "waiting-device" || current.phase === "exchanging")) {
3790
3863
  throw new Error("already-in-flight");
3791
3864
  }
3865
+ const effectiveMethod = resolveMethod(deps, method);
3866
+ if (effectiveMethod !== method) {
3867
+ deps.logger?.("auth.method.fallback", { from: method, to: effectiveMethod, reason: "no-client-secret" });
3868
+ }
3792
3869
  const controller = new AbortController();
3793
3870
  const timer = setTimeout(() => controller.abort(), attemptTimeout(deps));
3794
3871
  const attempt = { phase: "idle", controller, done: Promise.resolve() };
3795
3872
  current = attempt;
3796
- attempt.done = authorization.begin({
3797
- key: GITHUB_RECORD_KEY2,
3798
- method,
3799
- signal: controller.signal,
3800
- // 浏览器往返靠 authorizeUrl + 回调;notify 只更新内存视图,prompt 不支持(拒绝)。
3801
- interaction: {
3802
- notify: (notice) => {
3803
- if (notice.code !== void 0 && notice.url !== void 0) {
3804
- updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
3805
- } else if (notice.url !== void 0) {
3806
- updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
3807
- }
3808
- },
3809
- prompt: () => Promise.reject(new Error("interactive prompt unsupported in this surface"))
3810
- }
3811
- }).then((outcome) => {
3873
+ let started;
3874
+ try {
3875
+ started = authorization.begin({
3876
+ key: GITHUB_RECORD_KEY2,
3877
+ method: effectiveMethod,
3878
+ signal: controller.signal,
3879
+ // 浏览器往返靠 authorizeUrl + 回调;notify 只更新内存视图,prompt 不支持(拒绝)。
3880
+ interaction: {
3881
+ notify: (notice) => {
3882
+ if (notice.code !== void 0 && notice.url !== void 0) {
3883
+ updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
3884
+ } else if (notice.url !== void 0) {
3885
+ updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
3886
+ }
3887
+ },
3888
+ prompt: () => Promise.reject(new Error("interactive prompt unsupported in this surface"))
3889
+ }
3890
+ });
3891
+ } catch (error) {
3892
+ clearTimeout(timer);
3893
+ updateCurrent({ phase: "error", error: error instanceof Error ? error.message : String(error) });
3894
+ return attemptView();
3895
+ }
3896
+ attempt.done = started.then((outcome) => {
3812
3897
  if (outcome.status === "cancelled") updateCurrent({ phase: "idle" });
3813
3898
  }).catch((error) => {
3814
3899
  updateCurrent({ phase: "error", error: error instanceof Error ? error.message : String(error) });
3815
3900
  }).finally(() => clearTimeout(timer));
3816
- await Promise.resolve();
3901
+ await waitForStart(attempt);
3817
3902
  return attemptView();
3818
3903
  }
3904
+ async function waitForStart(attempt, timeoutMs = START_TIMEOUT_MS) {
3905
+ const deadline = Date.now() + timeoutMs;
3906
+ while (attempt.phase === "idle" && Date.now() < deadline) {
3907
+ await new Promise((resolve) => setTimeout(resolve, START_POLL_MS));
3908
+ }
3909
+ }
3819
3910
  function cancelGithubSignIn(deps) {
3820
3911
  const authorization = deps.get("authorization");
3821
3912
  if (authorization !== void 0 && typeof authorization.cancel === "function") {
@@ -4392,11 +4483,16 @@ function isAuthorizationAvailable(get) {
4392
4483
  const authorization = get("authorization");
4393
4484
  return authorization !== void 0 && typeof authorization.begin === "function";
4394
4485
  }
4486
+ function browserSignInAvailable(deps) {
4487
+ if (!isAuthorizationAvailable(deps.get)) return false;
4488
+ return ensureGithubFlow(githubAuthDeps(deps));
4489
+ }
4395
4490
  function githubAuthDeps(deps) {
4396
4491
  return {
4397
4492
  get: deps.get,
4398
4493
  ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
4399
4494
  ...deps.githubClientId === void 0 ? {} : { clientId: deps.githubClientId },
4495
+ ...deps.githubClientSecret === void 0 ? {} : { clientSecret: deps.githubClientSecret },
4400
4496
  ...deps.logger === void 0 ? {} : { logger: deps.logger }
4401
4497
  };
4402
4498
  }
@@ -4586,8 +4682,10 @@ async function dispatch(method, pathname, url, body, deps) {
4586
4682
  const method2 = body.method === "device" ? "device" : "browser";
4587
4683
  try {
4588
4684
  const view = await beginGithubSignIn(githubAuthDeps(deps), method2);
4685
+ if (view.phase === "error") throw new IpcError("GITHUB_AUTH_UNAVAILABLE", redact(view.error ?? "GitHub \u767B\u5F55\u5931\u8D25"));
4589
4686
  return { attempt: view };
4590
4687
  } catch (error) {
4688
+ if (error instanceof IpcError) throw error;
4591
4689
  const message = error instanceof Error ? error.message : String(error);
4592
4690
  if (message === "already-in-flight") {
4593
4691
  return { attempt: attemptView(), alreadyInFlight: true };
@@ -4733,8 +4831,13 @@ async function statePayload(deps, settings) {
4733
4831
  source: github.source,
4734
4832
  /** 浏览器登录 attempt 视图(idle / waiting-browser / waiting-device / exchanging / done / error)。 */
4735
4833
  attempt: attemptView(),
4736
- /** 浏览器登录是否可用:核心提供了 authorization 服务即可(client_id 有内置默认)。 */
4737
- browserAvailable: isAuthorizationAvailable(deps.get)
4834
+ /** 浏览器登录是否可用:authorization 服务在、且 GitHub flow 真的注册上了(client_id 有内置默认)。 */
4835
+ browserAvailable: browserSignInAvailable(deps),
4836
+ /**
4837
+ * 回环 web flow 是否可用:GitHub 换 token 必须有 client_secret,内置 client_id 没有 → 只能设备码。
4838
+ * 前端据此决定主按钮启动哪个 method(false 时直接用 device,避免点了必然失败的 web flow)。
4839
+ */
4840
+ loopbackAvailable: isLoopbackSignInAvailable(githubAuthDeps(deps))
4738
4841
  }
4739
4842
  };
4740
4843
  }
@@ -5013,7 +5116,7 @@ export {
5013
5116
  readPluginInfo,
5014
5117
  plugin_info_exports,
5015
5118
  github_exports,
5016
- registerGithubFlow,
5119
+ ensureGithubFlow,
5017
5120
  github_auth_exports,
5018
5121
  import_cli_exports,
5019
5122
  pending_pr_exports,
package/lib/index.d.ts CHANGED
@@ -10,6 +10,8 @@ interface PluginLogger {
10
10
  interface PluginContext {
11
11
  get?: (name: string) => unknown;
12
12
  effect?: (factory: () => unknown, label?: string) => unknown;
13
+ /** cordis 的服务就绪钩子:依赖被提供(或重提供)时回调,用于「晚到的服务」注册。 */
14
+ inject?: (deps: string[], callback: (scope: PluginContext) => unknown) => unknown;
13
15
  logger?: PluginLogger;
14
16
  webServer?: {
15
17
  register: (options: {
@@ -25,6 +27,7 @@ declare function apply(ctx: PluginContext, config?: {
25
27
  stateDir?: string;
26
28
  corePath?: string;
27
29
  githubClientId?: string;
30
+ githubClientSecret?: string;
28
31
  }): void;
29
32
  declare const _default: {
30
33
  name: string;
package/lib/index.js CHANGED
@@ -7,9 +7,9 @@ import {
7
7
  createTrendingHistoryTool,
8
8
  createTrendingRunTool,
9
9
  createUpdateService,
10
- readPluginInfo,
11
- registerGithubFlow
12
- } from "./chunk-LLTW4YHJ.js";
10
+ ensureGithubFlow,
11
+ readPluginInfo
12
+ } from "./chunk-VO72OOVK.js";
13
13
 
14
14
  // src/index.ts
15
15
  var name = "dsh-github-trending";
@@ -20,6 +20,7 @@ function readVersion() {
20
20
  var VERSION = readVersion();
21
21
  function apply(ctx, config = {}) {
22
22
  const env = process.env;
23
+ const githubClientSecret = (config.githubClientSecret ?? env.DSH_GITHUB_CLIENT_SECRET)?.trim();
23
24
  const get = (key) => typeof ctx.get === "function" ? ctx.get(key) : void 0;
24
25
  const log = (event, data) => {
25
26
  const suffix = data === void 0 ? "" : ` ${JSON.stringify(data)}`;
@@ -64,11 +65,22 @@ function apply(ctx, config = {}) {
64
65
  onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
65
66
  });
66
67
  const pluginInfo = readPluginInfo();
67
- registerGithubFlow({
68
- get,
68
+ const githubAuth = (resolve) => ({
69
+ get: resolve,
69
70
  logger: log,
70
- ...config.githubClientId === void 0 ? {} : { clientId: config.githubClientId }
71
+ ...config.githubClientId === void 0 ? {} : { clientId: config.githubClientId },
72
+ ...githubClientSecret === void 0 || githubClientSecret === "" ? {} : { clientSecret: githubClientSecret }
71
73
  });
74
+ const registerAuth = (resolve) => {
75
+ if (!ensureGithubFlow(githubAuth(resolve ?? get))) log("auth.flow.deferred", { reason: "authorization-not-ready" });
76
+ };
77
+ if (typeof ctx.inject === "function") {
78
+ ctx.inject(["authorization"], (scope) => {
79
+ registerAuth((name2) => typeof scope.get === "function" ? scope.get(name2) : void 0);
80
+ });
81
+ } else {
82
+ registerAuth();
83
+ }
72
84
  const update = createUpdateService({
73
85
  get,
74
86
  env,
@@ -90,6 +102,7 @@ function apply(ctx, config = {}) {
90
102
  pluginName: pluginInfo.name,
91
103
  ...pluginInfo.repository === void 0 ? {} : { repository: pluginInfo.repository },
92
104
  ...config.githubClientId === void 0 ? {} : { githubClientId: config.githubClientId },
105
+ ...githubClientSecret === void 0 || githubClientSecret === "" ? {} : { githubClientSecret },
93
106
  logger: log
94
107
  });
95
108
  const register = () => {
package/lib/internal.d.ts CHANGED
@@ -1164,7 +1164,13 @@ declare const CALLBACK_PATH = "/api/morehao/github-trending/oauth/github/callbac
1164
1164
  /**
1165
1165
  * 内置的 GitHub OAuth App 公开 client_id(**非机密**:OAuth 的 client_id 设计上就是公开的,
1166
1166
  * 会出现在授权 URL 里,与 `gh`/VS Code 内置自己的 client_id 同理)。
1167
- * 纯 PKCE public client,无需 client_secret。用户/部署可在 `apply(config.githubClientId)` 覆盖。
1167
+ *
1168
+ * ⚠️ 只有 client_id 不够:GitHub 的 web application flow 在换 token 时**仍然要求
1169
+ * client_secret**(PKCE 是叠加保护,不能替代,2025-07 起官方也只说「推荐」而非「可免 secret」)。
1170
+ * 因此:
1171
+ * - 内置 client_id → 只能走 **device flow**(`resolveMethod` 自动降级),开箱即用;
1172
+ * - 用户自带 OAuth App(`githubClientId` + `githubClientSecret`)→ 才启用回环 web flow。
1173
+ * 且 device flow 需要在 OAuth App 设置里勾选启用(本插件内置的 App 已启用)。
1168
1174
  *
1169
1175
  * OAuth App 侧登记的回调:`http://127.0.0.1/api/morehao/github-trending/oauth/github/callback`
1170
1176
  * (loopback 按 RFC 8252 忽略端口,运行时随机端口可正常回跳)。
@@ -1172,6 +1178,18 @@ declare const CALLBACK_PATH = "/api/morehao/github-trending/oauth/github/callbac
1172
1178
  declare const DEFAULT_GITHUB_CLIENT_ID = "Ov23li7rhp0EKoqbkJKY";
1173
1179
  /** 解析生效的 client_id:显式 config 优先,否则用内置默认。 */
1174
1180
  declare function resolveClientId(deps: GithubAuthDeps): string | undefined;
1181
+ /** 解析生效的 client_secret(没有就返回 undefined,绝不以空串形式发出去)。 */
1182
+ declare function resolveClientSecret(deps: GithubAuthDeps): string | undefined;
1183
+ /**
1184
+ * 回环(浏览器 web flow)是否可用:GitHub 换 token 必须带 client_secret,
1185
+ * 内置的公开 client_id 没有 secret,只能退到设备码。见文件头的说明。
1186
+ */
1187
+ declare function isLoopbackSignInAvailable(deps: GithubAuthDeps): boolean;
1188
+ /**
1189
+ * 实际要跑的方法:`browser` 需要 client_secret,缺省时自动降级为 `device`(开箱即用)。
1190
+ * 显式选择 `device` 的调用不受影响。
1191
+ */
1192
+ declare function resolveMethod(deps: GithubAuthDeps, requested: 'browser' | 'device'): 'browser' | 'device';
1175
1193
  /** 本插件运行需要 push / 建 PR,`read:user` 不够;一次授权到 `repo`。 */
1176
1194
  declare const DEFAULT_SCOPE = "repo";
1177
1195
  interface AuthorizationFlow {
@@ -1203,6 +1221,8 @@ interface AuthorizationLike {
1203
1221
  status: 'authorized' | 'cancelled';
1204
1222
  }>;
1205
1223
  cancel?: (key: string) => void;
1224
+ /** 核心 0.2.x 有:按 key 查已注册的 flow;判定「真的注册上了」靠它。 */
1225
+ describe?: (key: string) => unknown;
1206
1226
  }
1207
1227
  interface WebServerLike {
1208
1228
  register?: (route: {
@@ -1227,6 +1247,11 @@ interface GithubAuthDeps {
1227
1247
  };
1228
1248
  /** GitHub OAuth App 的 public client_id(无需 secret)。 */
1229
1249
  clientId?: string;
1250
+ /**
1251
+ * GitHub OAuth App 的 client_secret。只有自带 OAuth App 的回环 web flow 才需要;
1252
+ * 缺省时 `browser` 方法自动降级为 device flow。不进 `settings.json`、不进日志。
1253
+ */
1254
+ clientSecret?: string;
1230
1255
  scope?: string;
1231
1256
  attemptTimeoutMs?: number;
1232
1257
  }
@@ -1264,14 +1289,35 @@ interface GithubGrantPayload {
1264
1289
  tokenType?: string;
1265
1290
  scope?: string;
1266
1291
  }
1292
+ /** 这个 key 现在是否**真的**有 flow 在注册表里。 */
1293
+ declare function isGithubFlowRegistered(deps: GithubAuthDeps): boolean;
1294
+ /**
1295
+ * 幂等注册 GitHub flow;`authorization` 还没就绪时返回 false,调用方可稍后重试。
1296
+ *
1297
+ * 为什么不能只在 `apply()` 里试一次:宿主按「服务可用性」激活插件,本插件只
1298
+ * `inject: ['webServer']`,apply 完全可能早于 `authorization`——后者要等
1299
+ * `credentials` 的异步 `[Service.init]`(启动时读 `.credentials.yaml`)走完才
1300
+ * 可见,而 `ctx.get()` 对未 active 的 provider 返回 undefined。那一刻若放弃注册,
1301
+ * 就永久没有 flow;而可用性又是按请求现算的,于是出现「按钮亮着、点了必然
1302
+ * NO_FLOW、界面毫无反馈」。所以注册做成可重入的自愈入口:apply 用
1303
+ * `ctx.inject(['authorization'])` 等服务就绪,HTTP 面每次读状态再兜底一次。
1304
+ *
1305
+ * @returns 注册后是否可用(服务缺失 / 注册失败时为 false)。
1306
+ */
1307
+ declare function ensureGithubFlow(deps: GithubAuthDeps): boolean;
1267
1308
  /**
1268
1309
  * 向 `ctx.authorization` 注册 GitHub flow(若核心没有该服务则降级 no-op)。
1269
- * 在 `apply()` 里调用一次。
1310
+ * 等价于 {@link ensureGithubFlow},保留旧名字给 `apply()` 与测试用。
1270
1311
  */
1271
1312
  declare function registerGithubFlow(deps: GithubAuthDeps): void;
1272
1313
  /**
1273
1314
  * 启动一次浏览器登录 attempt(进程内 `authorization.begin`,不阻塞请求)。
1274
- * @returns 当前 attempt 视图(含 authorizeUrl,供前端打开)。二次调用在已有进行中时抛错。
1315
+ *
1316
+ * 返回的视图保证**不是 `idle`**:要么已经进入 waiting-*,要么已经落成 `error`
1317
+ * (供路由转成 HTTP 错误)。`idle` 只可能来自「flow 没注册」这类抢跑,前端拿到
1318
+ * 它就会以为什么都没发生——那正是「点了没反应」的形态,所以这里把它堵掉。
1319
+ *
1320
+ * @throws 服务不可用、flow 未注册、已有 attempt 在飞时抛错。
1275
1321
  */
1276
1322
  declare function beginGithubSignIn(deps: GithubAuthDeps, method?: 'browser' | 'device'): Promise<GithubAttemptView>;
1277
1323
  /** 取消进行中的 attempt。 */
@@ -1296,12 +1342,17 @@ declare const githubAuth_beginGithubSignIn: typeof beginGithubSignIn;
1296
1342
  declare const githubAuth_buildAuthorizeUrl: typeof buildAuthorizeUrl;
1297
1343
  declare const githubAuth_cancelGithubSignIn: typeof cancelGithubSignIn;
1298
1344
  declare const githubAuth_createPkce: typeof createPkce;
1345
+ declare const githubAuth_ensureGithubFlow: typeof ensureGithubFlow;
1346
+ declare const githubAuth_isGithubFlowRegistered: typeof isGithubFlowRegistered;
1347
+ declare const githubAuth_isLoopbackSignInAvailable: typeof isLoopbackSignInAvailable;
1299
1348
  declare const githubAuth_registerGithubFlow: typeof registerGithubFlow;
1300
1349
  declare const githubAuth_resetAttemptForTest: typeof resetAttemptForTest;
1301
1350
  declare const githubAuth_resolveClientId: typeof resolveClientId;
1351
+ declare const githubAuth_resolveClientSecret: typeof resolveClientSecret;
1352
+ declare const githubAuth_resolveMethod: typeof resolveMethod;
1302
1353
  declare const githubAuth_verifyState: typeof verifyState;
1303
1354
  declare namespace githubAuth {
1304
- export { type githubAuth_AuthorizationFlow as AuthorizationFlow, type githubAuth_AuthorizationLike as AuthorizationLike, type githubAuth_AuthorizationSessionLike as AuthorizationSessionLike, githubAuth_CALLBACK_PATH as CALLBACK_PATH, githubAuth_DEFAULT_GITHUB_CLIENT_ID as DEFAULT_GITHUB_CLIENT_ID, githubAuth_DEFAULT_SCOPE as DEFAULT_SCOPE, githubAuth_GITHUB_RECORD_KEY as GITHUB_RECORD_KEY, type githubAuth_GithubAttemptPhase as GithubAttemptPhase, type githubAuth_GithubAttemptView as GithubAttemptView, type githubAuth_GithubAuthDeps as GithubAuthDeps, type githubAuth_GithubGrantPayload as GithubGrantPayload, type githubAuth_WebServerLike as WebServerLike, githubAuth_attemptView as attemptView, githubAuth_beginGithubSignIn as beginGithubSignIn, githubAuth_buildAuthorizeUrl as buildAuthorizeUrl, githubAuth_cancelGithubSignIn as cancelGithubSignIn, githubAuth_createPkce as createPkce, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_verifyState as verifyState };
1355
+ export { type githubAuth_AuthorizationFlow as AuthorizationFlow, type githubAuth_AuthorizationLike as AuthorizationLike, type githubAuth_AuthorizationSessionLike as AuthorizationSessionLike, githubAuth_CALLBACK_PATH as CALLBACK_PATH, githubAuth_DEFAULT_GITHUB_CLIENT_ID as DEFAULT_GITHUB_CLIENT_ID, githubAuth_DEFAULT_SCOPE as DEFAULT_SCOPE, githubAuth_GITHUB_RECORD_KEY as GITHUB_RECORD_KEY, type githubAuth_GithubAttemptPhase as GithubAttemptPhase, type githubAuth_GithubAttemptView as GithubAttemptView, type githubAuth_GithubAuthDeps as GithubAuthDeps, type githubAuth_GithubGrantPayload as GithubGrantPayload, type githubAuth_WebServerLike as WebServerLike, githubAuth_attemptView as attemptView, githubAuth_beginGithubSignIn as beginGithubSignIn, githubAuth_buildAuthorizeUrl as buildAuthorizeUrl, githubAuth_cancelGithubSignIn as cancelGithubSignIn, githubAuth_createPkce as createPkce, githubAuth_ensureGithubFlow as ensureGithubFlow, githubAuth_isGithubFlowRegistered as isGithubFlowRegistered, githubAuth_isLoopbackSignInAvailable as isLoopbackSignInAvailable, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_resolveClientSecret as resolveClientSecret, githubAuth_resolveMethod as resolveMethod, githubAuth_verifyState as verifyState };
1305
1356
  }
1306
1357
 
1307
1358
  interface DefaultBranchResult {
@@ -1985,8 +2036,10 @@ interface RouteDeps {
1985
2036
  /** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
1986
2037
  pluginName?: string;
1987
2038
  repository?: string;
1988
- /** 浏览器 GitHub 登录用的 public client_id(由 apply 的 config 传入;缺省时按钮会提示未配置)。 */
2039
+ /** 浏览器 GitHub 登录用的 public client_id(由 apply 的 config 传入;缺省时用内置默认)。 */
1989
2040
  githubClientId?: string;
2041
+ /** 自带 OAuth App 时的 client_secret(只用于回环 web flow;缺省时该 method 自动降级为设备码)。 */
2042
+ githubClientSecret?: string;
1990
2043
  logger?: (event: string, data?: Record<string, unknown>) => void;
1991
2044
  }
1992
2045
  declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
package/lib/internal.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  tools_exports,
28
28
  update_exports,
29
29
  version_exports
30
- } from "./chunk-LLTW4YHJ.js";
30
+ } from "./chunk-VO72OOVK.js";
31
31
 
32
32
  // src/internal.ts
33
33
  import * as exec from "@morehao/dsh-plugin-kit/exec";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",