@morehao/dsh-github-trending 0.8.3 → 0.8.5

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.8.5] - 2026-11-02
4
+
5
+ ### Fixes
6
+
7
+ - **auth**: show device code with one-click copy
8
+
9
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.8.4...v0.8.5
10
+
11
+ ## [v0.8.4] - 2026-11-02
12
+
13
+ ### Features
14
+
15
+ - **auth**: open device-code page with prefilled code
16
+ - **auth**: 美化 GitHub OAuth 回调落地页 (#78)
17
+
18
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.8.3...v0.8.4
19
+
3
20
  ## [v0.8.3] - 2026-11-02
4
21
 
5
22
  ### Fixes
package/client.js CHANGED
@@ -125,10 +125,13 @@ 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 安全保存,无需手工粘贴。点一下会打开 GitHub 验证页,把下方验证码输入并确认即可。",
129
129
  "auth.browser.signin": "用 GitHub 登录",
130
130
  "auth.browser.waiting": "等待浏览器授权…",
131
- "auth.browser.code": "在打开的页面输入验证码:",
131
+ "auth.browser.code": "验证码:",
132
+ "auth.browser.copyCode": "复制验证码",
133
+ "auth.browser.codeCopied": "验证码已复制",
134
+ "auth.browser.codeCopyFailed": "复制失败,请手动记下验证码",
132
135
  "auth.browser.cancel": "取消",
133
136
  "auth.browser.done": "已登录",
134
137
  "auth.browser.device": "用设备码登录",
@@ -670,10 +673,13 @@ var DICT = {
670
673
  "auth.gh": "Local gh",
671
674
  "auth.browser": "Browser login",
672
675
  "auth.gh.hint": "Uses your local gh login: run `gh auth login` in a terminal, then click Test authentication.",
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.",
676
+ "auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste. Clicking opens the GitHub verification page — enter the code below and confirm.",
674
677
  "auth.browser.signin": "Sign in with GitHub",
675
678
  "auth.browser.waiting": "Waiting for browser authorization…",
676
- "auth.browser.code": "Enter this code on the opened page:",
679
+ "auth.browser.code": "Verification code:",
680
+ "auth.browser.copyCode": "Copy code",
681
+ "auth.browser.codeCopied": "Code copied",
682
+ "auth.browser.codeCopyFailed": "Copy failed — please note the code manually",
677
683
  "auth.browser.cancel": "Cancel",
678
684
  "auth.browser.done": "Signed in",
679
685
  "auth.browser.device": "Use a device code",
@@ -2272,9 +2278,10 @@ function SettingsView(props) {
2272
2278
  } catch {
2273
2279
  }
2274
2280
  }
2275
- if (view.phase === "waiting-device" && view.verificationUri !== void 0) {
2281
+ const deviceUrl = view.verificationUriComplete ?? view.verificationUri;
2282
+ if (view.phase === "waiting-device" && deviceUrl !== void 0) {
2276
2283
  try {
2277
- window.open(view.verificationUri, "_blank", "noopener,noreferrer");
2284
+ window.open(deviceUrl, "_blank", "noopener,noreferrer");
2278
2285
  } catch {
2279
2286
  }
2280
2287
  }
@@ -2293,6 +2300,12 @@ function SettingsView(props) {
2293
2300
  if (signIn.authorizeUrl === void 0) return;
2294
2301
  void navigator.clipboard?.writeText(signIn.authorizeUrl).then(() => notify("ok", t("auth.browser.copied")));
2295
2302
  };
2303
+ const copyDeviceCode = () => {
2304
+ if (signIn.userCode === void 0) return;
2305
+ void copyText(signIn.userCode).then(
2306
+ (ok) => notify(ok ? "ok" : "err", ok ? t("auth.browser.codeCopied") : t("auth.browser.codeCopyFailed"))
2307
+ );
2308
+ };
2296
2309
  const signInActive = signIn.phase === "waiting-browser" || signIn.phase === "waiting-device" || signIn.phase === "exchanging";
2297
2310
  import_react.default.useEffect(() => {
2298
2311
  if (!signInActive) return void 0;
@@ -2583,12 +2596,27 @@ function SettingsView(props) {
2583
2596
  " ",
2584
2597
  h(Btn, { small: true, variant: "ghost", onClick: () => copyAuthorizeLink() }, t("auth.browser.copy"))
2585
2598
  ) : null,
2586
- signIn.verificationUri !== void 0 ? h(
2599
+ signIn.verificationUriComplete !== void 0 || signIn.verificationUri !== void 0 ? h(
2587
2600
  "div",
2588
2601
  { className: "dshgt-desc" },
2589
- h("a", { className: "dshgt-link", href: signIn.verificationUri, target: "_blank", rel: "noreferrer" }, signIn.verificationUri)
2602
+ h(
2603
+ "a",
2604
+ {
2605
+ className: "dshgt-link",
2606
+ href: signIn.verificationUriComplete ?? signIn.verificationUri,
2607
+ target: "_blank",
2608
+ rel: "noreferrer"
2609
+ },
2610
+ t("auth.browser.open")
2611
+ )
2590
2612
  ) : null,
2591
- signIn.userCode !== void 0 ? h("div", { className: "dshgt-mono" }, `${t("auth.browser.code")} ${signIn.userCode}`) : null
2613
+ signIn.userCode !== void 0 ? h(
2614
+ "div",
2615
+ { className: "dshgt-desc", style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } },
2616
+ h("span", null, t("auth.browser.code")),
2617
+ h("span", { className: "dshgt-mono", style: { fontSize: "1.1em", fontWeight: "bold", letterSpacing: "1px" } }, signIn.userCode),
2618
+ h(Btn, { small: true, variant: "ghost", onClick: () => copyDeviceCode() }, t("auth.browser.copyCode"))
2619
+ ) : null
2592
2620
  ) : null,
2593
2621
  h(
2594
2622
  Row,
@@ -3540,8 +3540,10 @@ __export(github_auth_exports, {
3540
3540
  createPkce: () => createPkce,
3541
3541
  ensureGithubFlow: () => ensureGithubFlow,
3542
3542
  isGithubFlowRegistered: () => isGithubFlowRegistered,
3543
+ isHttpUrl: () => isHttpUrl,
3543
3544
  isLoopbackSignInAvailable: () => isLoopbackSignInAvailable,
3544
3545
  registerGithubFlow: () => registerGithubFlow,
3546
+ renderCallbackPage: () => renderCallbackPage,
3545
3547
  resetAttemptForTest: () => resetAttemptForTest,
3546
3548
  resolveClientId: () => resolveClientId,
3547
3549
  resolveClientSecret: () => resolveClientSecret,
@@ -3581,6 +3583,16 @@ function attemptView() {
3581
3583
  function base64url(buf) {
3582
3584
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3583
3585
  }
3586
+ function isHttpUrl(value) {
3587
+ if (typeof value !== "string" || value === "") return false;
3588
+ let url;
3589
+ try {
3590
+ url = new URL(value);
3591
+ } catch {
3592
+ return false;
3593
+ }
3594
+ return url.protocol === "https:" || url.protocol === "http:";
3595
+ }
3584
3596
  function createPkce() {
3585
3597
  const verifier = base64url(randomBytes2(32));
3586
3598
  const challenge = base64url(createHash3("sha256").update(verifier).digest());
@@ -3591,6 +3603,41 @@ function verifyState(received, expected) {
3591
3603
  const b = Buffer.from(expected);
3592
3604
  return a.length === b.length && timingSafeEqual2(a, b);
3593
3605
  }
3606
+ function renderCallbackPage(kind) {
3607
+ const ok = kind === "success";
3608
+ const title = ok ? "\u767B\u5F55\u5B8C\u6210" : "\u767B\u5F55\u672A\u5B8C\u6210";
3609
+ const heading = ok ? "GitHub \u6388\u6743\u6210\u529F" : "GitHub \u6388\u6743\u5931\u8D25";
3610
+ const body = ok ? "\u5DF2\u5B8C\u6210\u767B\u5F55\uFF0C\u53EF\u4EE5\u5173\u95ED\u6B64\u9875\u9762\u3002" : "\u6388\u6743\u6821\u9A8C\u672A\u901A\u8FC7\uFF0C\u8BF7\u56DE\u5230 DSH \u91CD\u65B0\u53D1\u8D77\u767B\u5F55\u3002";
3611
+ const accent = ok ? "#1f883d" : "#cf222e";
3612
+ const icon = ok ? '<path d="M20 6 9 17l-5-5"/>' : '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>';
3613
+ const autoClose = ok ? "<script>(function(){try{setTimeout(function(){window.close()},1200)}catch(e){}})()</script>" : "";
3614
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">
3615
+ <meta name="viewport" content="width=device-width,initial-scale=1">
3616
+ <meta name="color-scheme" content="light dark">
3617
+ <title>${title}</title>
3618
+ <style>
3619
+ :root{--bg:#f6f8fa;--card:#ffffff;--fg:#1f2328;--muted:#59636e;--border:#d1d9e0;--accent:${accent}}
3620
+ @media (prefers-color-scheme:dark){:root{--bg:#0d1117;--card:#151b23;--fg:#e6edf3;--muted:#9198a1;--border:#3d444d}}
3621
+ *{box-sizing:border-box}
3622
+ html,body{height:100%}
3623
+ body{margin:0;display:flex;align-items:center;justify-content:center;padding:24px;
3624
+ background:var(--bg);color:var(--fg);
3625
+ font:400 15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}
3626
+ main{width:100%;max-width:400px;background:var(--card);border:1px solid var(--border);
3627
+ border-radius:14px;padding:40px 32px;text-align:center;
3628
+ box-shadow:0 8px 24px rgba(0,0,0,.06)}
3629
+ .mark{width:64px;height:64px;margin:0 auto 20px;border-radius:50%;display:flex;
3630
+ align-items:center;justify-content:center;background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--accent)}
3631
+ .mark svg{width:30px;height:30px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
3632
+ h1{margin:0 0 8px;font-size:19px;font-weight:600;letter-spacing:.2px}
3633
+ p{margin:0;color:var(--muted);font-size:14px}
3634
+ </style></head>
3635
+ <body><main role="status">
3636
+ <div class="mark"><svg viewBox="0 0 24 24" aria-hidden="true">${icon}</svg></div>
3637
+ <h1>${heading}</h1>
3638
+ <p>${body}</p>
3639
+ </main>${autoClose}</body></html>`;
3640
+ }
3594
3641
  function buildAuthorizeUrl(params) {
3595
3642
  const url = new URL(params.authorizeEndpoint);
3596
3643
  url.searchParams.set("client_id", params.clientId);
@@ -3684,10 +3731,10 @@ async function runBrowser(deps, session) {
3684
3731
  const single = url.searchParams.getAll("state").length === 1 && url.searchParams.getAll("code").length === 1;
3685
3732
  const bad = req.method !== "GET" || url.pathname !== CALLBACK_PATH || !single || code === "" || !verifyState(gotState, state);
3686
3733
  if (bad) {
3687
- res.writeHead(400, { "cache-control": "no-store" }).end();
3734
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(renderCallbackPage("error"));
3688
3735
  return;
3689
3736
  }
3690
- res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end('<!doctype html><meta charset="utf-8"><title>GitHub</title><p>\u5DF2\u5B8C\u6210\u767B\u5F55\uFF0C\u53EF\u4EE5\u5173\u95ED\u6B64\u9875\u9762\u3002</p>');
3737
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(renderCallbackPage("success"));
3691
3738
  resolveCode(code);
3692
3739
  }
3693
3740
  });
@@ -3750,15 +3797,17 @@ async function runDevice(deps, session) {
3750
3797
  const intervalSec = typeof start.interval === "number" && start.interval > 0 ? start.interval : 5;
3751
3798
  const expiresInSec = typeof start.expires_in === "number" && start.expires_in > 0 ? start.expires_in : 900;
3752
3799
  const deadline = Date.now() + expiresInSec * 1e3;
3800
+ const verificationUriComplete = isHttpUrl(start.verification_uri_complete) ? start.verification_uri_complete : void 0;
3753
3801
  updateCurrent({
3754
3802
  phase: "waiting-device",
3755
3803
  verificationUri: start.verification_uri,
3804
+ ...verificationUriComplete === void 0 ? {} : { verificationUriComplete },
3756
3805
  userCode: start.user_code,
3757
3806
  expiresAt: deadline
3758
3807
  });
3759
3808
  session.notify?.({
3760
3809
  message: "Enter this code on the verification page to finish signing in.",
3761
- url: start.verification_uri,
3810
+ url: verificationUriComplete ?? start.verification_uri,
3762
3811
  code: start.user_code
3763
3812
  });
3764
3813
  let waitMs = intervalSec * 1e3;
@@ -3880,7 +3929,7 @@ async function beginGithubSignIn(deps, method = "browser") {
3880
3929
  interaction: {
3881
3930
  notify: (notice) => {
3882
3931
  if (notice.code !== void 0 && notice.url !== void 0) {
3883
- updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
3932
+ updateCurrent({ phase: "waiting-device", userCode: notice.code });
3884
3933
  } else if (notice.url !== void 0) {
3885
3934
  updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
3886
3935
  }
package/lib/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  createUpdateService,
10
10
  ensureGithubFlow,
11
11
  readPluginInfo
12
- } from "./chunk-VO72OOVK.js";
12
+ } from "./chunk-QOYTABJH.js";
13
13
 
14
14
  // src/index.ts
15
15
  var name = "dsh-github-trending";
package/lib/internal.d.ts CHANGED
@@ -1260,12 +1260,20 @@ interface GithubAttemptView {
1260
1260
  phase: GithubAttemptPhase;
1261
1261
  authorizeUrl?: string;
1262
1262
  verificationUri?: string;
1263
+ /**
1264
+ * device flow 的预填验证码地址(`verification_uri_complete`),**可选**:若 provider 返回就透出,
1265
+ * 前端优先用它打开(码已预填)。注意 GitHub 官方 device flow 实测不返回该字段,此时走
1266
+ * `verificationUri` + 手动输入;保留此字段只为兼容会返回它的 provider。
1267
+ */
1268
+ verificationUriComplete?: string;
1263
1269
  userCode?: string;
1264
1270
  expiresAt?: number;
1265
1271
  login?: string;
1266
1272
  error?: string;
1267
1273
  }
1268
1274
  declare function attemptView(): GithubAttemptView;
1275
+ /** 判断一个未知值是否是可直接打开的 http(s) URL(用于校验 GitHub 返回的验证页地址)。 */
1276
+ declare function isHttpUrl(value: unknown): boolean;
1269
1277
  /** PKCE:返回 verifier 与 S256 challenge。 */
1270
1278
  declare function createPkce(): {
1271
1279
  verifier: string;
@@ -1273,6 +1281,13 @@ declare function createPkce(): {
1273
1281
  };
1274
1282
  /** 常量时间比较 state(长度不等直接 false,避免 timingSafeEqual 抛错)。 */
1275
1283
  declare function verifyState(received: string, expected: string): boolean;
1284
+ /** 回调落地页的两种结果(纯展示,无外部资源,离线可测)。 */
1285
+ type CallbackPageKind = 'success' | 'error';
1286
+ /**
1287
+ * 回环回调的落地页 HTML。自包含、无外链资源(CSP 友好、离线可用),
1288
+ * 跟随系统深浅色;成功页会尝试自动关闭本窗口(脚本失败时文字兜底)。
1289
+ */
1290
+ declare function renderCallbackPage(kind: CallbackPageKind): string;
1276
1291
  /** 拼授权页 URL。 */
1277
1292
  declare function buildAuthorizeUrl(params: {
1278
1293
  authorizeEndpoint: string;
@@ -1344,15 +1359,17 @@ declare const githubAuth_cancelGithubSignIn: typeof cancelGithubSignIn;
1344
1359
  declare const githubAuth_createPkce: typeof createPkce;
1345
1360
  declare const githubAuth_ensureGithubFlow: typeof ensureGithubFlow;
1346
1361
  declare const githubAuth_isGithubFlowRegistered: typeof isGithubFlowRegistered;
1362
+ declare const githubAuth_isHttpUrl: typeof isHttpUrl;
1347
1363
  declare const githubAuth_isLoopbackSignInAvailable: typeof isLoopbackSignInAvailable;
1348
1364
  declare const githubAuth_registerGithubFlow: typeof registerGithubFlow;
1365
+ declare const githubAuth_renderCallbackPage: typeof renderCallbackPage;
1349
1366
  declare const githubAuth_resetAttemptForTest: typeof resetAttemptForTest;
1350
1367
  declare const githubAuth_resolveClientId: typeof resolveClientId;
1351
1368
  declare const githubAuth_resolveClientSecret: typeof resolveClientSecret;
1352
1369
  declare const githubAuth_resolveMethod: typeof resolveMethod;
1353
1370
  declare const githubAuth_verifyState: typeof verifyState;
1354
1371
  declare namespace githubAuth {
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 };
1372
+ 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_isHttpUrl as isHttpUrl, githubAuth_isLoopbackSignInAvailable as isLoopbackSignInAvailable, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_renderCallbackPage as renderCallbackPage, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_resolveClientSecret as resolveClientSecret, githubAuth_resolveMethod as resolveMethod, githubAuth_verifyState as verifyState };
1356
1373
  }
1357
1374
 
1358
1375
  interface DefaultBranchResult {
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-VO72OOVK.js";
30
+ } from "./chunk-QOYTABJH.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.3",
3
+ "version": "0.8.5",
4
4
  "description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",