@morehao/dsh-github-trending 0.7.10 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.7.11] - 2026-11-01
4
+
5
+ ### Refactors
6
+
7
+ - **settings**: merge schedule into scope card and link subscription repos (#65)
8
+
9
+ ### Docs
10
+
11
+ - 新增英文版 README.en.md,中文 README 默认并加语言切换 (#64)
12
+
13
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.7.10...v0.7.11
14
+
3
15
  ## [v0.7.10] - 2026-11-01
4
16
 
5
17
  > 修复 peer / engine 版本区间漏掉 `0.2.2-*` 预发布的问题,让插件在宿主升到 `0.2.2` 线后仍能正常安装。
package/README.en.md ADDED
@@ -0,0 +1,92 @@
1
+ <p align="center">
2
+ <img src="icon.svg" alt="dsh-github-trending logo" width="88" height="88">
3
+ </p>
4
+
5
+ <h1 align="center">dsh-github-trending</h1>
6
+
7
+ <p align="center">
8
+ A DeepSeek Harness plugin for the GitHub Trending board
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="README.md">简体中文</a> | English
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://github.com/morehao/dsh-github-trending/actions/workflows/check.yml"><img alt="check" src="https://github.com/morehao/dsh-github-trending/actions/workflows/check.yml/badge.svg"></a>
17
+ <a href="https://www.npmjs.com/package/@morehao/dsh-github-trending"><img alt="npm version" src="https://img.shields.io/npm/v/@morehao/dsh-github-trending.svg"></a>
18
+ </p>
19
+
20
+ A **DeepSeek Harness plugin** for the GitHub Trending board: it schedules daily / weekly / monthly digests, hands them to the local [`trendradar`](https://github.com/morehao/trendradar) core to scrape Trending, write Chinese intros and period summaries, and commits the result to your own repository with a PR merged automatically. You follow status, read digests and manage subscriptions from the workbench — no command line needed.
21
+
22
+ ## Positioning
23
+
24
+ - **An entry point, not an engine**. Scraping, parsing, model summarization, rendering, git and PR all live in one place — the local `trendradar` core. The plugin only handles **configuration, scheduling, spawning the core and displaying results**, so there is no second implementation to drift.
25
+ - **Deliver the artifact as-is**. Content is written byte-for-byte exactly as the core produces it; the plugin does not rewrite, reorder or re-invoke a model on it.
26
+ - **Local execution, no telemetry**. The core runs on your machine; the plugin has no cloud account, and secrets are handed to the core only over a runtime channel — never written to config files or logs.
27
+
28
+ > For deeper material — requirements, config fields, error codes, subscription stages and implementation details — see **[docs/reference.md](docs/reference.md)**.
29
+
30
+ ## Features
31
+
32
+ Open the workbench from the sidebar **GitHub Trending** or `Settings → GitHub Trending`. It has five views:
33
+
34
+ ### Overview
35
+
36
+ Answers "when will it run next, are the four readiness checks all green, what was delivered last time" at a glance. The top shows the plugin identity bar (version / package name / check for updates); the right side keeps the next run and readiness N/4 always visible.
37
+
38
+ ![Overview](docs/images/overview.png)
39
+
40
+ ### Digest
41
+
42
+ Browse past digests by period (daily / weekly / monthly). Markdown is rendered directly (headings, lists, tables, links), and you can switch to **Raw**. Only issues whose preview copy was kept can be read; failed / unchanged / dry-run / old records each have their own empty state.
43
+
44
+ ![Digest](docs/images/digest.png)
45
+
46
+ ### Runs
47
+
48
+ The last 30 runs, filterable by success / failure / dry-run. Expand a run to see its deliverables (commit, content hash, branch, PR link) and a read-only log; the footer shows cumulative tokens.
49
+
50
+ ![Runs](docs/images/runs.png)
51
+
52
+ ### Settings
53
+
54
+ Six sections — Content source / Model / Repository / Auth / Digest / Schedule — each saved and validated independently; an unsaved section shows a yellow dot next to its item on the left.
55
+
56
+ ![Settings](docs/images/settings.png)
57
+
58
+ ### Subscriptions
59
+
60
+ (Optional) Subscribe to a set of repositories; each week the new Releases from each of them are merged into one Chinese summary and committed alongside the digest on the same branch and PR. The subscription list is versioned with the data repo, auditable and reproducible.
61
+
62
+ ![Subscriptions](docs/images/subscriptions.png)
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ dsh plugin --profile <profile> add @morehao/dsh-github-trending
68
+ ```
69
+
70
+ Restart the host after installing, then open **GitHub Trending** from the sidebar or `Settings → GitHub Trending`.
71
+
72
+ ### Quick start in three steps
73
+
74
+ 1. **Pick a repository**: enter the absolute path of a local git repository (the plugin does not run `git init` for you; a non-repo path returns `REPO_NOT_GIT`).
75
+ 2. **Pick a model**: click "Use DSH config" to choose a provider and model from the host's provider directory; providers that can export a key pass it straight to the core, while OAuth-type credentials go through a local loopback proxy.
76
+ 3. **Dry-run once**: "Dry run (no commit)" only scrapes and renders — it writes no files and makes no commit.
77
+
78
+ Requirements: DSH `0.2.1-rc.3` or later, Node.js `^22.20.0 || >=24.0.0`, a local git ≥ 2.30 with a writable remote, and git / GitHub credentials (needed only for push and opening PRs). On first run the core binary is downloaded from GitHub Releases and verified by sha256, then reused offline.
79
+
80
+ ## Development and release
81
+
82
+ ```bash
83
+ npm install
84
+ npm run check # typecheck + contract check + build + all tests
85
+ node scripts/ac/run.mjs # AC-P mechanical checks
86
+ ```
87
+
88
+ Releases go through **tag-triggered CI** (Trusted Publishing / OIDC): merge the PR → push an annotated tag `vX.Y.Z` on `main` → CI publishes to npm and creates the Release from the annotation body. See [docs/publishing.md](docs/publishing.md) for details.
89
+
90
+ ## License
91
+
92
+ MIT
package/README.md CHANGED
@@ -8,6 +8,10 @@
8
8
  GitHub 趋势榜的 DeepSeek Harness 插件
9
9
  </p>
10
10
 
11
+ <p align="center">
12
+ 简体中文 | <a href="README.en.md">English</a>
13
+ </p>
14
+
11
15
  <p align="center">
12
16
  <a href="https://github.com/morehao/dsh-github-trending/actions/workflows/check.yml"><img alt="check" src="https://github.com/morehao/dsh-github-trending/actions/workflows/check.yml/badge.svg"></a>
13
17
  <a href="https://www.npmjs.com/package/@morehao/dsh-github-trending"><img alt="npm version" src="https://img.shields.io/npm/v/@morehao/dsh-github-trending.svg"></a>
package/client.js CHANGED
@@ -54,7 +54,7 @@ var DICT = {
54
54
  "st.model": "模型",
55
55
  "st.repo": "仓库",
56
56
  "st.auth": "认证",
57
- "st.scope": "榜单",
57
+ "st.scope": "榜单与调度",
58
58
  "st.summarize": "摘要与用量",
59
59
  "st.releases": "订阅",
60
60
  "st.schedule": "调度",
@@ -123,7 +123,19 @@ var DICT = {
123
123
  "auth.mode": "方式",
124
124
  "auth.pat": "PAT",
125
125
  "auth.gh": "本机 gh",
126
+ "auth.browser": "浏览器登录",
126
127
  "auth.gh.hint": "使用本机 gh 登录态:终端执行 `gh auth login` 完成登录,再点「测试认证」。",
128
+ "auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。",
129
+ "auth.browser.signin": "用 GitHub 登录",
130
+ "auth.browser.waiting": "等待浏览器授权…",
131
+ "auth.browser.code": "在打开的页面输入验证码:",
132
+ "auth.browser.cancel": "取消",
133
+ "auth.browser.done": "已登录",
134
+ "auth.browser.device": "用设备码登录",
135
+ "auth.browser.unavailable": "当前环境未配置 GitHub OAuth client_id,浏览器登录不可用:请改用「本机 gh」或 PAT。",
136
+ "auth.browser.openHint": "若浏览器未自动打开,请手动打开发起授权,或复制链接:",
137
+ "auth.browser.copy": "复制链接",
138
+ "auth.browser.copied": "授权链接已复制",
127
139
  "auth.pat.hint": "在 GitHub → Settings → Developer settings 生成 Personal Access Token(勾选 public_repo),填到下方引用或环境变量。",
128
140
  "auth.tokenRef": "凭据引用",
129
141
  "auth.tokenRef.hint": "凭据引用名 / 环境变量名",
@@ -583,7 +595,7 @@ var DICT = {
583
595
  "st.model": "Model",
584
596
  "st.repo": "Repository",
585
597
  "st.auth": "Auth",
586
- "st.scope": "Scope",
598
+ "st.scope": "Scope & Schedule",
587
599
  "st.summarize": "Summary & usage",
588
600
  "st.releases": "Subscriptions",
589
601
  "st.schedule": "Schedule",
@@ -652,7 +664,19 @@ var DICT = {
652
664
  "auth.mode": "Mode",
653
665
  "auth.pat": "PAT",
654
666
  "auth.gh": "Local gh",
667
+ "auth.browser": "Browser login",
655
668
  "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.",
670
+ "auth.browser.signin": "Sign in with GitHub",
671
+ "auth.browser.waiting": "Waiting for browser authorization…",
672
+ "auth.browser.code": "Enter this code on the opened page:",
673
+ "auth.browser.cancel": "Cancel",
674
+ "auth.browser.done": "Signed in",
675
+ "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:",
678
+ "auth.browser.copy": "Copy link",
679
+ "auth.browser.copied": "Authorization link copied",
656
680
  "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.",
657
681
  "auth.tokenRef": "Credential ref",
658
682
  "auth.tokenRef.hint": "credential ref / env var name",
@@ -1147,6 +1171,7 @@ var CSS = `
1147
1171
  .dshgt-card-h h3{margin:0;font-size:13px;font-weight:600;white-space:nowrap;}
1148
1172
  .dshgt-hint{color:var(--gt-muted);font-size:12px;}
1149
1173
  .dshgt-desc{margin:0;color:var(--gt-muted);font-size:12px;}
1174
+ .dshgt-subhead{margin:4px 0 0;padding-top:8px;border-top:1px solid var(--gt-border);font-size:12px;font-weight:600;color:var(--gt-muted);}
1150
1175
  .dshgt-row{display:flex;gap:10px;align-items:center;min-height:28px;flex-wrap:wrap;}
1151
1176
  .dshgt-row>label.dshgt-label{width:96px;color:var(--gt-muted);flex:none;font-size:12px;display:flex;align-items:flex-start;gap:5px;}
1152
1177
  .dshgt-label-text{flex:1;min-width:0;}
@@ -1521,6 +1546,8 @@ var CSS = `
1521
1546
  .dshgt-subs-cb{width:14px;height:14px;margin:0;accent-color:var(--gt-accent);justify-self:center;}
1522
1547
  .dshgt-subs-ident{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
1523
1548
  .dshgt-subs-ident mark{background:color-mix(in srgb,var(--gt-accent) 18%,transparent);color:inherit;border-radius:3px;padding:0 1px;}
1549
+ .dshgt-subs-ident-link{color:inherit;text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;}
1550
+ .dshgt-subs-ident-link:hover{color:var(--gt-accent);text-decoration:underline;text-underline-offset:2px;}
1524
1551
  .dshgt-subs-ver{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--gt-fg);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
1525
1552
  .dshgt-subs-sum{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;color:var(--gt-muted);text-align:right;white-space:nowrap;}
1526
1553
  .dshgt-subs-stat{display:flex;align-items:center;gap:6px;padding-left:4px;font-size:12px;color:var(--gt-muted);white-space:nowrap;}
@@ -1848,6 +1875,23 @@ async function apiCall(method, path, body, options = {}) {
1848
1875
  }
1849
1876
  return json;
1850
1877
  }
1878
+ async function beginGithubSignIn(method = "browser") {
1879
+ const payload = await apiCall("POST", "/github/browser/begin", { method });
1880
+ return payload.attempt;
1881
+ }
1882
+ async function getGithubSignInStatus(signal) {
1883
+ const payload = await apiCall(
1884
+ "GET",
1885
+ "/github/browser/status",
1886
+ void 0,
1887
+ signal === void 0 ? {} : { signal }
1888
+ );
1889
+ return payload.attempt;
1890
+ }
1891
+ async function cancelGithubSignIn() {
1892
+ const payload = await apiCall("POST", "/github/browser/cancel", {});
1893
+ return payload.attempt;
1894
+ }
1851
1895
  function releaseRunsOf(state) {
1852
1896
  if (state === null || state === void 0) return [];
1853
1897
  const root = Array.isArray(state.releaseRuns) ? state.releaseRuns : void 0;
@@ -2024,8 +2068,8 @@ function credentialView(model, t) {
2024
2068
 
2025
2069
  // client/settings.ts
2026
2070
  var LANGUAGES = ["all", "typescript", "python", "go", "rust", "java", "c", "cpp", "javascript", "csharp"];
2027
- var SECTIONS = ["source", "model", "repo", "auth", "scope", "summarize", "releases", "schedule"];
2028
- var EMPTY_DIRTY = { source: false, model: false, repo: false, auth: false, scope: false, summarize: false, releases: false, schedule: false };
2071
+ var SECTIONS = ["source", "model", "repo", "auth", "scope", "summarize", "releases"];
2072
+ var EMPTY_DIRTY = { source: false, model: false, repo: false, auth: false, scope: false, summarize: false, releases: false };
2029
2073
  function cloneSettings(settings) {
2030
2074
  return JSON.parse(JSON.stringify(settings));
2031
2075
  }
@@ -2040,13 +2084,11 @@ function sectionSlice(settings, section) {
2040
2084
  case "auth":
2041
2085
  return { github: settings.github };
2042
2086
  case "scope":
2043
- return { trending: settings.trending };
2087
+ return { trending: settings.trending, schedule: settings.schedule };
2044
2088
  case "summarize":
2045
2089
  return { summarize: settings.summarize };
2046
2090
  case "releases":
2047
2091
  return { releases: settings.releases };
2048
- case "schedule":
2049
- return { schedule: settings.schedule };
2050
2092
  }
2051
2093
  }
2052
2094
  var NOTICE_TTL_MS = 5e3;
@@ -2077,6 +2119,7 @@ function SettingsView(props) {
2077
2119
  const [branchLoading, setBranchLoading] = import_react.default.useState(false);
2078
2120
  const [testing, setTesting] = import_react.default.useState(false);
2079
2121
  const [githubTest, setGithubTest] = import_react.default.useState(null);
2122
+ const [signIn, setSignIn] = import_react.default.useState({ phase: "idle" });
2080
2123
  const anyDirty = SECTIONS.some((id) => dirtySections[id]);
2081
2124
  const sectionEditable = section !== "source";
2082
2125
  import_react.default.useEffect(() => {
@@ -2204,6 +2247,52 @@ function SettingsView(props) {
2204
2247
  setTesting(false);
2205
2248
  }
2206
2249
  };
2250
+ const startSignIn = async (method) => {
2251
+ try {
2252
+ const view = await beginGithubSignIn(method);
2253
+ setSignIn(view);
2254
+ if (view.phase === "waiting-browser" && view.authorizeUrl !== void 0) {
2255
+ try {
2256
+ window.open(view.authorizeUrl, "_blank", "noopener,noreferrer");
2257
+ } catch {
2258
+ }
2259
+ }
2260
+ } catch (error) {
2261
+ notify("err", error instanceof Error ? error.message : String(error));
2262
+ }
2263
+ };
2264
+ const cancelSignIn = async () => {
2265
+ try {
2266
+ setSignIn(await cancelGithubSignIn());
2267
+ } catch (error) {
2268
+ notify("err", error instanceof Error ? error.message : String(error));
2269
+ }
2270
+ };
2271
+ const copyAuthorizeLink = () => {
2272
+ if (signIn.authorizeUrl === void 0) return;
2273
+ void navigator.clipboard?.writeText(signIn.authorizeUrl).then(() => notify("ok", t("auth.browser.copied")));
2274
+ };
2275
+ const signInActive = signIn.phase === "waiting-browser" || signIn.phase === "waiting-device" || signIn.phase === "exchanging";
2276
+ import_react.default.useEffect(() => {
2277
+ if (!signInActive) return void 0;
2278
+ let alive = true;
2279
+ const timer = setInterval(() => {
2280
+ void getGithubSignInStatus().then((view) => {
2281
+ if (!alive) return;
2282
+ setSignIn(view);
2283
+ if (view.phase === "done") {
2284
+ setGithubTest(view.login === void 0 ? { ok: true } : { ok: true, login: view.login });
2285
+ notify("ok", t("auth.browser.done"));
2286
+ void store.refresh({ quiet: true });
2287
+ }
2288
+ }).catch(() => {
2289
+ });
2290
+ }, 1500);
2291
+ return () => {
2292
+ alive = false;
2293
+ clearInterval(timer);
2294
+ };
2295
+ }, [signInActive]);
2207
2296
  const importCli = async () => {
2208
2297
  try {
2209
2298
  const result = await apiCall(
@@ -2242,6 +2331,8 @@ function SettingsView(props) {
2242
2331
  const branchText = branchLoading ? t("branch.loading") : branchInfo?.branch !== void 0 ? branchInfo.branch : draft.repo.baseBranch !== "" ? draft.repo.baseBranch : "—";
2243
2332
  const branchHint = branchInfo?.source === "origin-head" ? t("branch.fromOrigin") : branchInfo?.source === "head" ? t("branch.fromHead") : branchInfo?.message !== void 0 ? branchInfo.message : t("branch.readonlyHint");
2244
2333
  const isGh = draft.github.authMode === "gh";
2334
+ const isBrowser = draft.github.authMode === "browser";
2335
+ const browserAvailable = state.github.browserAvailable === true;
2245
2336
  const sections = [
2246
2337
  { id: "source", label: t("st.source") },
2247
2338
  { id: "model", label: t("st.model") },
@@ -2249,8 +2340,7 @@ function SettingsView(props) {
2249
2340
  { id: "auth", label: t("st.auth") },
2250
2341
  { id: "scope", label: t("st.scope") },
2251
2342
  { id: "summarize", label: t("st.summarize") },
2252
- { id: "releases", label: t("st.releases") },
2253
- { id: "schedule", label: t("st.schedule") }
2343
+ { id: "releases", label: t("st.releases") }
2254
2344
  ];
2255
2345
  const sourceCard = h(
2256
2346
  Card,
@@ -2419,16 +2509,32 @@ function SettingsView(props) {
2419
2509
  value: draft.github.authMode,
2420
2510
  options: [
2421
2511
  { value: "gh", label: t("auth.gh") },
2422
- { value: "pat", label: t("auth.pat") }
2512
+ { value: "pat", label: t("auth.pat") },
2513
+ { value: "browser", label: t("auth.browser") }
2423
2514
  ],
2424
2515
  onChange: (value) => update((next) => {
2425
- next.github.authMode = value === "pat" ? "pat" : "gh";
2516
+ next.github.authMode = value === "pat" ? "pat" : value === "browser" ? "browser" : "gh";
2426
2517
  })
2427
2518
  }),
2428
2519
  h(Pill, { kind: state.github.configured ? "ok" : "warn" }, state.github.configured ? `${t("auth.configured")} · ${state.github.source}` : t("auth.missing"))
2429
2520
  ),
2430
- h("p", { className: "dshgt-hint" }, isGh ? t("auth.gh.hint") : t("auth.pat.hint")),
2431
- isGh ? null : h(
2521
+ h("p", { className: "dshgt-hint" }, isGh ? t("auth.gh.hint") : isBrowser ? t("auth.browser.hint") : t("auth.pat.hint")),
2522
+ isGh ? null : isBrowser ? h(
2523
+ Row,
2524
+ { label: t("auth.status") },
2525
+ browserAvailable ? h(
2526
+ Btn,
2527
+ {
2528
+ small: true,
2529
+ variant: "primary",
2530
+ onClick: () => void startSignIn("browser"),
2531
+ disabled: signInActive
2532
+ },
2533
+ signIn.phase === "done" && signIn.login !== void 0 ? `${t("auth.browser.done")} · @${signIn.login}` : signInActive ? t("auth.browser.waiting") : t("auth.browser.signin")
2534
+ ) : 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,
2536
+ signInActive ? h(Btn, { small: true, variant: "ghost", onClick: () => void cancelSignIn() }, t("auth.browser.cancel")) : null
2537
+ ) : h(
2432
2538
  Row,
2433
2539
  { label: t("auth.tokenRef") },
2434
2540
  h(Input, { mono: true, width: 220, value: draft.github.tokenRef, placeholder: "GITHUB_TOKEN", onInput: (value) => update((next) => {
@@ -2436,6 +2542,19 @@ function SettingsView(props) {
2436
2542
  }) }),
2437
2543
  h("span", { className: "dshgt-hint" }, t("auth.tokenRef.hint"))
2438
2544
  ),
2545
+ // 浏览器登录进行中的状态行:授权页链接(可复制)+ 设备码。
2546
+ isBrowser && signIn.phase !== "idle" && signIn.phase !== "done" ? h(
2547
+ "div",
2548
+ { className: "dshgt-hint" },
2549
+ signIn.authorizeUrl !== void 0 ? h(
2550
+ "div",
2551
+ { className: "dshgt-desc" },
2552
+ h("span", null, t("auth.browser.openHint")),
2553
+ " ",
2554
+ h(Btn, { small: true, variant: "ghost", onClick: () => copyAuthorizeLink() }, t("auth.browser.copy"))
2555
+ ) : null,
2556
+ signIn.userCode !== void 0 ? h("div", { className: "dshgt-mono" }, `${t("auth.browser.code")} ${signIn.userCode}`) : null
2557
+ ) : null,
2439
2558
  h(
2440
2559
  Row,
2441
2560
  { label: t("auth.test") },
@@ -2449,7 +2568,10 @@ function SettingsView(props) {
2449
2568
  );
2450
2569
  const scopeCard = h(
2451
2570
  Card,
2452
- { title: t("card.scope.title") },
2571
+ {
2572
+ title: t("card.scope.title"),
2573
+ hint: state.schedule.nextRunAt === void 0 ? void 0 : `${t("schedule.next")} ${localTime(state.schedule.nextRunAt)}`
2574
+ },
2453
2575
  h(
2454
2576
  Row,
2455
2577
  { label: t("scope.period") },
@@ -2483,11 +2605,8 @@ function SettingsView(props) {
2483
2605
  draftNext.trending.languages = next;
2484
2606
  })
2485
2607
  })
2486
- )
2487
- );
2488
- const scheduleCard = h(
2489
- Card,
2490
- { title: t("card.schedule.title"), hint: state.schedule.nextRunAt === void 0 ? void 0 : `${t("schedule.next")} ${localTime(state.schedule.nextRunAt)}` },
2608
+ ),
2609
+ h("h4", { className: "dshgt-subhead" }, t("card.schedule.title")),
2491
2610
  h(
2492
2611
  Row,
2493
2612
  { label: t("schedule.enabled") },
@@ -2657,7 +2776,7 @@ function SettingsView(props) {
2657
2776
  })
2658
2777
  )
2659
2778
  );
2660
- const currentCard = section === "source" ? sourceCard : section === "model" ? modelCard : section === "repo" ? repoCard : section === "auth" ? authCard : section === "scope" ? scopeCard : section === "summarize" ? summarizeCard : section === "releases" ? releasesCard : scheduleCard;
2779
+ const currentCard = section === "source" ? sourceCard : section === "model" ? modelCard : section === "repo" ? repoCard : section === "auth" ? authCard : section === "scope" ? scopeCard : section === "summarize" ? summarizeCard : releasesCard;
2661
2780
  return h(
2662
2781
  "div",
2663
2782
  { className: "dshgt-settings" },
@@ -4574,6 +4693,9 @@ function highlight(text, needle) {
4574
4693
  if (index < text.length) parts.push(text.slice(index));
4575
4694
  return parts;
4576
4695
  }
4696
+ function repoUrl(name) {
4697
+ return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(name) ? `https://github.com/${name}` : void 0;
4698
+ }
4577
4699
  function isMissingDoc(code, message) {
4578
4700
  if (code !== void 0 && /NOT_?FOUND|MISSING|NO_?DOC/i.test(code)) return true;
4579
4701
  return /not\s*found|no such file|enoent|404|不存在|no release document|has no release|无发版记录/i.test(message);
@@ -4885,7 +5007,21 @@ function SubscriptionsView(props) {
4885
5007
  }
4886
5008
  },
4887
5009
  checkbox,
4888
- h("span", { key: "name", role: "cell", className: "dshgt-subs-ident", title: row.name }, highlight(row.name, needle)),
5010
+ h(
5011
+ "span",
5012
+ { key: "name", role: "cell", className: "dshgt-subs-ident", title: row.name },
5013
+ repoUrl(row.name) === void 0 ? highlight(row.name, needle) : h(
5014
+ "a",
5015
+ {
5016
+ className: "dshgt-subs-ident-link",
5017
+ href: repoUrl(row.name),
5018
+ target: "_blank",
5019
+ rel: "noreferrer",
5020
+ onClick: (event) => event.stopPropagation()
5021
+ },
5022
+ highlight(row.name, needle)
5023
+ )
5024
+ ),
4889
5025
  h("span", { key: "tag", role: "cell", className: "dshgt-subs-ver", title: item?.latestTag ?? "" }, item?.latestTag ?? "—"),
4890
5026
  h(
4891
5027
  "span",