co-maintainer 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/main.d.ts +2 -0
  4. package/dist/main.js +9 -0
  5. package/dist/mod.d.ts +4 -0
  6. package/dist/mod.js +2 -0
  7. package/dist/package.json +54 -0
  8. package/dist/src/ai/batch.d.ts +16 -0
  9. package/dist/src/ai/batch.js +117 -0
  10. package/dist/src/ai/fake.d.ts +7 -0
  11. package/dist/src/ai/fake.js +40 -0
  12. package/dist/src/ai/hetzner.d.ts +16 -0
  13. package/dist/src/ai/hetzner.js +69 -0
  14. package/dist/src/ai/mermaid.d.ts +27 -0
  15. package/dist/src/ai/mermaid.js +457 -0
  16. package/dist/src/ai/mermaid_loop.d.ts +12 -0
  17. package/dist/src/ai/mermaid_loop.js +100 -0
  18. package/dist/src/ai/openrouter.d.ts +9 -0
  19. package/dist/src/ai/openrouter.js +29 -0
  20. package/dist/src/ai/provider.d.ts +4 -0
  21. package/dist/src/ai/provider.js +63 -0
  22. package/dist/src/cli/args.d.ts +5 -0
  23. package/dist/src/cli/args.js +252 -0
  24. package/dist/src/cli/commands/probe.d.ts +2 -0
  25. package/dist/src/cli/commands/probe.js +122 -0
  26. package/dist/src/cli/commands/review.d.ts +3 -0
  27. package/dist/src/cli/commands/review.js +84 -0
  28. package/dist/src/cli/commands/serve.d.ts +18 -0
  29. package/dist/src/cli/commands/serve.js +183 -0
  30. package/dist/src/cli/commands/set.d.ts +7 -0
  31. package/dist/src/cli/commands/set.js +171 -0
  32. package/dist/src/cli/main.d.ts +1 -0
  33. package/dist/src/cli/main.js +27 -0
  34. package/dist/src/cli/prompt.d.ts +4 -0
  35. package/dist/src/cli/prompt.js +26 -0
  36. package/dist/src/cli/review_args.d.ts +28 -0
  37. package/dist/src/cli/review_args.js +84 -0
  38. package/dist/src/cli/review_output.d.ts +2 -0
  39. package/dist/src/cli/review_output.js +10 -0
  40. package/dist/src/cli/review_result.d.ts +81 -0
  41. package/dist/src/cli/review_result.js +281 -0
  42. package/dist/src/config.d.ts +108 -0
  43. package/dist/src/config.js +138 -0
  44. package/dist/src/git/default_branch.d.ts +5 -0
  45. package/dist/src/git/default_branch.js +22 -0
  46. package/dist/src/github/app.d.ts +43 -0
  47. package/dist/src/github/app.js +120 -0
  48. package/dist/src/github/client.d.ts +17 -0
  49. package/dist/src/github/client.js +66 -0
  50. package/dist/src/github/collect.d.ts +7 -0
  51. package/dist/src/github/collect.js +368 -0
  52. package/dist/src/github/gh.d.ts +8 -0
  53. package/dist/src/github/gh.js +63 -0
  54. package/dist/src/github/jwt.d.ts +12 -0
  55. package/dist/src/github/jwt.js +84 -0
  56. package/dist/src/github/oauth.d.ts +9 -0
  57. package/dist/src/github/oauth.js +51 -0
  58. package/dist/src/github/pat.d.ts +10 -0
  59. package/dist/src/github/pat.js +60 -0
  60. package/dist/src/knowledge/facts.d.ts +3 -0
  61. package/dist/src/knowledge/facts.js +325 -0
  62. package/dist/src/knowledge/guide.d.ts +6 -0
  63. package/dist/src/knowledge/guide.js +82 -0
  64. package/dist/src/knowledge/probe.d.ts +12 -0
  65. package/dist/src/knowledge/probe.js +144 -0
  66. package/dist/src/knowledge/sections.d.ts +2 -0
  67. package/dist/src/knowledge/sections.js +13 -0
  68. package/dist/src/knowledge/skill.d.ts +10 -0
  69. package/dist/src/knowledge/skill.js +106 -0
  70. package/dist/src/knowledge/synthesis.d.ts +7 -0
  71. package/dist/src/knowledge/synthesis.js +315 -0
  72. package/dist/src/knowledge/types.d.ts +49 -0
  73. package/dist/src/knowledge/types.js +1 -0
  74. package/dist/src/knowledge/validate.d.ts +6 -0
  75. package/dist/src/knowledge/validate.js +134 -0
  76. package/dist/src/local/carry_over_store.d.ts +18 -0
  77. package/dist/src/local/carry_over_store.js +40 -0
  78. package/dist/src/local/codegraph_exclude.d.ts +3 -0
  79. package/dist/src/local/codegraph_exclude.js +26 -0
  80. package/dist/src/local/codegraph_prepare.d.ts +17 -0
  81. package/dist/src/local/codegraph_prepare.js +51 -0
  82. package/dist/src/local/git_ops.d.ts +12 -0
  83. package/dist/src/local/git_ops.js +98 -0
  84. package/dist/src/local/git_parse.d.ts +27 -0
  85. package/dist/src/local/git_parse.js +131 -0
  86. package/dist/src/local/git_revision.d.ts +15 -0
  87. package/dist/src/local/git_revision.js +141 -0
  88. package/dist/src/local/git_untracked.d.ts +21 -0
  89. package/dist/src/local/git_untracked.js +79 -0
  90. package/dist/src/local/review_local.d.ts +4 -0
  91. package/dist/src/local/review_local.js +273 -0
  92. package/dist/src/local/review_lock.d.ts +7 -0
  93. package/dist/src/local/review_lock.js +45 -0
  94. package/dist/src/pr/checkout.d.ts +25 -0
  95. package/dist/src/pr/checkout.js +136 -0
  96. package/dist/src/pr/codegraph_tool_args.d.ts +8 -0
  97. package/dist/src/pr/codegraph_tool_args.js +34 -0
  98. package/dist/src/pr/codegraph_tools.d.ts +15 -0
  99. package/dist/src/pr/codegraph_tools.js +408 -0
  100. package/dist/src/pr/diff_summary.d.ts +6 -0
  101. package/dist/src/pr/diff_summary.js +52 -0
  102. package/dist/src/pr/findings.d.ts +14 -0
  103. package/dist/src/pr/findings.js +94 -0
  104. package/dist/src/pr/github_revision.d.ts +4 -0
  105. package/dist/src/pr/github_revision.js +39 -0
  106. package/dist/src/pr/hunks.d.ts +18 -0
  107. package/dist/src/pr/hunks.js +73 -0
  108. package/dist/src/pr/reviewer.d.ts +40 -0
  109. package/dist/src/pr/reviewer.js +582 -0
  110. package/dist/src/pr/rounds.d.ts +17 -0
  111. package/dist/src/pr/rounds.js +43 -0
  112. package/dist/src/pr/scope.d.ts +35 -0
  113. package/dist/src/pr/scope.js +84 -0
  114. package/dist/src/pr/snapshot.d.ts +11 -0
  115. package/dist/src/pr/snapshot.js +1 -0
  116. package/dist/src/pr/suggestion.d.ts +17 -0
  117. package/dist/src/pr/suggestion.js +70 -0
  118. package/dist/src/remote/client.d.ts +4 -0
  119. package/dist/src/remote/client.js +199 -0
  120. package/dist/src/remote/client_tools.d.ts +13 -0
  121. package/dist/src/remote/client_tools.js +30 -0
  122. package/dist/src/remote/fixtures.d.ts +1 -0
  123. package/dist/src/remote/fixtures.js +30 -0
  124. package/dist/src/remote/revision_from_submit.d.ts +2 -0
  125. package/dist/src/remote/revision_from_submit.js +26 -0
  126. package/dist/src/remote/schema.d.ts +14 -0
  127. package/dist/src/remote/schema.js +30 -0
  128. package/dist/src/remote/server/body.d.ts +18 -0
  129. package/dist/src/remote/server/body.js +73 -0
  130. package/dist/src/remote/server/routes.d.ts +1 -0
  131. package/dist/src/remote/server/routes.js +150 -0
  132. package/dist/src/remote/server/sessions.d.ts +16 -0
  133. package/dist/src/remote/server/sessions.js +48 -0
  134. package/dist/src/remote/server/sync.d.ts +3 -0
  135. package/dist/src/remote/server/sync.js +90 -0
  136. package/dist/src/remote/tool_bridge.d.ts +15 -0
  137. package/dist/src/remote/tool_bridge.js +84 -0
  138. package/dist/src/remote/validate.d.ts +8 -0
  139. package/dist/src/remote/validate.js +248 -0
  140. package/dist/src/review/carry_over.d.ts +63 -0
  141. package/dist/src/review/carry_over.js +372 -0
  142. package/dist/src/review/engine.d.ts +13 -0
  143. package/dist/src/review/engine.js +4 -0
  144. package/dist/src/review/guides.d.ts +12 -0
  145. package/dist/src/review/guides.js +44 -0
  146. package/dist/src/review/revision.d.ts +26 -0
  147. package/dist/src/review/revision.js +57 -0
  148. package/dist/src/server/api/activity.d.ts +1 -0
  149. package/dist/src/server/api/activity.js +12 -0
  150. package/dist/src/server/api/analytics.d.ts +1 -0
  151. package/dist/src/server/api/analytics.js +9 -0
  152. package/dist/src/server/api/installations.d.ts +4 -0
  153. package/dist/src/server/api/installations.js +24 -0
  154. package/dist/src/server/api/jobs.d.ts +1 -0
  155. package/dist/src/server/api/jobs.js +86 -0
  156. package/dist/src/server/api/json_body.d.ts +1 -0
  157. package/dist/src/server/api/json_body.js +14 -0
  158. package/dist/src/server/api/remote_tokens.d.ts +1 -0
  159. package/dist/src/server/api/remote_tokens.js +71 -0
  160. package/dist/src/server/api/repos.d.ts +4 -0
  161. package/dist/src/server/api/repos.js +136 -0
  162. package/dist/src/server/api/settings.d.ts +1 -0
  163. package/dist/src/server/api/settings.js +210 -0
  164. package/dist/src/server/app.d.ts +27 -0
  165. package/dist/src/server/app.js +147 -0
  166. package/dist/src/server/assets.d.ts +3 -0
  167. package/dist/src/server/assets.js +21 -0
  168. package/dist/src/server/auth.d.ts +44 -0
  169. package/dist/src/server/auth.js +188 -0
  170. package/dist/src/server/errors.d.ts +8 -0
  171. package/dist/src/server/errors.js +10 -0
  172. package/dist/src/server/http.d.ts +8 -0
  173. package/dist/src/server/http.js +117 -0
  174. package/dist/src/server/logo.d.ts +1 -0
  175. package/dist/src/server/logo.js +2 -0
  176. package/dist/src/server/pages/activity.d.ts +4 -0
  177. package/dist/src/server/pages/activity.js +200 -0
  178. package/dist/src/server/pages/add_repo.d.ts +9 -0
  179. package/dist/src/server/pages/add_repo.js +56 -0
  180. package/dist/src/server/pages/analytics.d.ts +2 -0
  181. package/dist/src/server/pages/analytics.js +193 -0
  182. package/dist/src/server/pages/client.d.ts +2 -0
  183. package/dist/src/server/pages/client.js +2 -0
  184. package/dist/src/server/pages/home.d.ts +16 -0
  185. package/dist/src/server/pages/home.js +87 -0
  186. package/dist/src/server/pages/knowledge.d.ts +2 -0
  187. package/dist/src/server/pages/knowledge.js +64 -0
  188. package/dist/src/server/pages/layout.d.ts +32 -0
  189. package/dist/src/server/pages/layout.js +230 -0
  190. package/dist/src/server/pages/pr.d.ts +2 -0
  191. package/dist/src/server/pages/pr.js +109 -0
  192. package/dist/src/server/pages/repo.d.ts +2 -0
  193. package/dist/src/server/pages/repo.js +92 -0
  194. package/dist/src/server/pages/repo_prs.d.ts +2 -0
  195. package/dist/src/server/pages/repo_prs.js +75 -0
  196. package/dist/src/server/pages/repo_remote.d.ts +2 -0
  197. package/dist/src/server/pages/repo_remote.js +38 -0
  198. package/dist/src/server/pages/repo_settings.d.ts +3 -0
  199. package/dist/src/server/pages/repo_settings.js +127 -0
  200. package/dist/src/server/pages/router.d.ts +17 -0
  201. package/dist/src/server/pages/router.js +312 -0
  202. package/dist/src/server/pages/settings.d.ts +2 -0
  203. package/dist/src/server/pages/settings.js +337 -0
  204. package/dist/src/server/pages/setup.d.ts +5 -0
  205. package/dist/src/server/pages/setup.js +38 -0
  206. package/dist/src/server/pages/styles.d.ts +2 -0
  207. package/dist/src/server/pages/styles.js +2 -0
  208. package/dist/src/server/webhook/index.d.ts +1 -0
  209. package/dist/src/server/webhook/index.js +64 -0
  210. package/dist/src/server/webhook/signature.d.ts +7 -0
  211. package/dist/src/server/webhook/signature.js +41 -0
  212. package/dist/src/services/credentials.d.ts +19 -0
  213. package/dist/src/services/credentials.js +146 -0
  214. package/dist/src/services/dashboard.d.ts +165 -0
  215. package/dist/src/services/dashboard.js +200 -0
  216. package/dist/src/services/drift.d.ts +13 -0
  217. package/dist/src/services/drift.js +76 -0
  218. package/dist/src/services/jobs.d.ts +34 -0
  219. package/dist/src/services/jobs.js +222 -0
  220. package/dist/src/services/remote_review.d.ts +10 -0
  221. package/dist/src/services/remote_review.js +274 -0
  222. package/dist/src/services/remote_review_abort.d.ts +3 -0
  223. package/dist/src/services/remote_review_abort.js +11 -0
  224. package/dist/src/services/remote_token_jobs.d.ts +1 -0
  225. package/dist/src/services/remote_token_jobs.js +20 -0
  226. package/dist/src/services/remote_tokens.d.ts +13 -0
  227. package/dist/src/services/remote_tokens.js +57 -0
  228. package/dist/src/services/replies.d.ts +7 -0
  229. package/dist/src/services/replies.js +248 -0
  230. package/dist/src/services/review.d.ts +35 -0
  231. package/dist/src/services/review.js +671 -0
  232. package/dist/src/services/setup.d.ts +29 -0
  233. package/dist/src/services/setup.js +364 -0
  234. package/dist/src/services/webhook.d.ts +22 -0
  235. package/dist/src/services/webhook.js +401 -0
  236. package/dist/src/store/app_db.d.ts +10 -0
  237. package/dist/src/store/app_db.js +85 -0
  238. package/dist/src/store/cache_db.d.ts +4 -0
  239. package/dist/src/store/cache_db.js +63 -0
  240. package/dist/src/store/deliveries.d.ts +12 -0
  241. package/dist/src/store/deliveries.js +23 -0
  242. package/dist/src/store/drift.d.ts +4 -0
  243. package/dist/src/store/drift.js +22 -0
  244. package/dist/src/store/findings.d.ts +31 -0
  245. package/dist/src/store/findings.js +64 -0
  246. package/dist/src/store/installations.d.ts +9 -0
  247. package/dist/src/store/installations.js +35 -0
  248. package/dist/src/store/job_logs.d.ts +5 -0
  249. package/dist/src/store/job_logs.js +17 -0
  250. package/dist/src/store/jobs.d.ts +27 -0
  251. package/dist/src/store/jobs.js +86 -0
  252. package/dist/src/store/migrations.d.ts +5 -0
  253. package/dist/src/store/migrations.js +258 -0
  254. package/dist/src/store/remote_review_inputs.d.ts +11 -0
  255. package/dist/src/store/remote_review_inputs.js +25 -0
  256. package/dist/src/store/remote_tokens.d.ts +10 -0
  257. package/dist/src/store/remote_tokens.js +45 -0
  258. package/dist/src/store/replies.d.ts +24 -0
  259. package/dist/src/store/replies.js +79 -0
  260. package/dist/src/store/repos.d.ts +11 -0
  261. package/dist/src/store/repos.js +59 -0
  262. package/dist/src/store/reviews.d.ts +86 -0
  263. package/dist/src/store/reviews.js +220 -0
  264. package/dist/src/store/rows.d.ts +179 -0
  265. package/dist/src/store/rows.js +4 -0
  266. package/dist/src/store/sessions.d.ts +8 -0
  267. package/dist/src/store/sessions.js +34 -0
  268. package/dist/src/store/skill_state.d.ts +3 -0
  269. package/dist/src/store/skill_state.js +24 -0
  270. package/dist/src/store/sqlite.d.ts +39 -0
  271. package/dist/src/store/sqlite.js +77 -0
  272. package/dist/src/store/subjects.d.ts +15 -0
  273. package/dist/src/store/subjects.js +78 -0
  274. package/dist/src/tools/codegraph.d.ts +58 -0
  275. package/dist/src/tools/codegraph.js +165 -0
  276. package/dist/src/tools/codegraph_exec.d.ts +13 -0
  277. package/dist/src/tools/codegraph_exec.js +66 -0
  278. package/dist/src/types.d.ts +89 -0
  279. package/dist/src/types.js +1 -0
  280. package/dist/src/util/atomic.d.ts +4 -0
  281. package/dist/src/util/atomic.js +78 -0
  282. package/dist/src/util/keyed_lock.d.ts +3 -0
  283. package/dist/src/util/keyed_lock.js +33 -0
  284. package/dist/src/util/log.d.ts +11 -0
  285. package/dist/src/util/log.js +42 -0
  286. package/dist/src/util/redact.d.ts +5 -0
  287. package/dist/src/util/redact.js +30 -0
  288. package/dist/src/util/runtime.d.ts +90 -0
  289. package/dist/src/util/runtime.js +206 -0
  290. package/dist/src/util/time.d.ts +9 -0
  291. package/dist/src/util/time.js +36 -0
  292. package/dist/src/version.d.ts +1 -0
  293. package/dist/src/version.js +5 -0
  294. package/package.json +54 -0
@@ -0,0 +1,127 @@
1
+ import { html, layout, repoNav, skSlot, text } from "./layout.js";
2
+ export function renderRepoSettings(username, repo, config) {
3
+ const name = repo.full_name;
4
+ const skip = repo.skip_drafts && repo.skip_bots
5
+ ? "both"
6
+ : repo.skip_drafts
7
+ ? "drafts"
8
+ : repo.skip_bots
9
+ ? "bots"
10
+ : "none";
11
+ return html(layout({
12
+ title: `Settings · ${name}`,
13
+ username,
14
+ body: `<div class="wrap side">
15
+ ${repoNav(name, "settings")}
16
+ <div>
17
+ <div class="crumbs"><a href="/">Repositories</a> /
18
+ <a href="/repos/${text(name)}">${text(name)}</a> / Settings</div>
19
+ <div class="pagehead"><div><h1>Settings</h1></div></div>
20
+ <div class="card" data-async>
21
+ ${skSlot()}
22
+ <div class="hd"><h2>Automatic review</h2></div>
23
+ <div class="bd">
24
+ <div style="display:flex;align-items:center;gap:14px;margin-bottom:22px">
25
+ <button class="tg${repo.auto_review === 1 ? " on" : ""}" id="auto"></button>
26
+ <div>Review pull requests as they are pushed</div>
27
+ </div>
28
+ <div class="field">
29
+ <label>Skip</label>
30
+ <select id="skip">
31
+ <option value="both"${skip === "both" ? " selected" : ""}>Drafts and bot pull requests</option>
32
+ <option value="drafts"${skip === "drafts" ? " selected" : ""}>Drafts only</option>
33
+ <option value="bots"${skip === "bots" ? " selected" : ""}>Bots only</option>
34
+ <option value="none"${skip === "none" ? " selected" : ""}>Nothing</option>
35
+ </select>
36
+ </div>
37
+ <div class="field">
38
+ <label>Review</label>
39
+ <select id="scope">
40
+ <option value="whole-pr"${repo.review_scope !== "incremental" ? " selected" : ""}>The whole pull request every time</option>
41
+ <option value="incremental"${repo.review_scope === "incremental" ? " selected" : ""}>Only what changed since the last review</option>
42
+ </select>
43
+ <div class="hint">Reviewing only new changes costs less on long-running pull requests.</div>
44
+ </div>
45
+ <div style="display:flex;align-items:center;gap:14px;margin-top:22px">
46
+ <button class="tg${repo.use_codegraph === 1 ? " on" : ""}" id="codegraph"></button>
47
+ <div>Let the review query the codebase's call graph (codegraph)</div>
48
+ </div>
49
+ <div class="hint">Slower and costs more tokens per review, try it before turning it on everywhere.</div>
50
+ </div>
51
+ <div class="ft"><button class="primary" id="save-auto">Save</button></div>
52
+ </div>
53
+ <div class="card" data-async>
54
+ ${skSlot()}
55
+ <div class="hd"><h2>How this repository is analyzed</h2></div>
56
+ <div class="bd">
57
+ <p class="muted" style="margin:0 0 18px">Used when the knowledge is rebuilt. Leave blank to use your account defaults.</p>
58
+ <div class="two">
59
+ <div class="field"><label>Months of pull requests</label>
60
+ <input id="months" value="${text(config.maxPrMonths ?? "")}"></div>
61
+ <div class="field"><label>Commits</label>
62
+ <input id="commits" value="${text(config.maxCommits ?? "")}"></div>
63
+ <div class="field"><label>Max lines per pull request</label>
64
+ <input id="lines" value="${text(config.maxPullRequestChangeLines ?? "")}"></div>
65
+ <div class="field"><label>Max comments per pull request</label>
66
+ <input id="comments" value="${text(config.maxComments ?? "")}"></div>
67
+ </div>
68
+ </div>
69
+ <div class="ft"><button class="primary" id="save-init">Save</button></div>
70
+ </div>
71
+ <div class="card" data-async>
72
+ ${skSlot()}
73
+ <div class="hd"><h2>Remove</h2></div>
74
+ <div class="bd" style="display:flex;align-items:center;gap:16px">
75
+ <div class="txt">Stop reviewing this repository. Review history is kept.</div>
76
+ <button id="remove" style="margin-left:auto;color:var(--red)">Remove repository</button>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <script>
82
+ var repo = ${JSON.stringify(name)};
83
+ function save(btn, body) {
84
+ run(btn, btn.closest("[data-async]"), async function() {
85
+ await api("PATCH", "/api/repos/" + repo, body);
86
+ location.reload();
87
+ });
88
+ }
89
+ document.getElementById("save-auto").addEventListener("click", function() {
90
+ var skip = document.getElementById("skip").value;
91
+ save(this, {
92
+ autoReview: document.getElementById("auto").classList.contains("on"),
93
+ skipDrafts: skip === "both" || skip === "drafts",
94
+ skipBots: skip === "both" || skip === "bots",
95
+ reviewScope: document.getElementById("scope").value,
96
+ useCodegraph: document.getElementById("codegraph").classList.contains("on")
97
+ });
98
+ });
99
+ document.getElementById("auto").addEventListener("click", function() {
100
+ this.classList.toggle("on");
101
+ });
102
+ document.getElementById("codegraph").addEventListener("click", function() {
103
+ this.classList.toggle("on");
104
+ });
105
+ document.getElementById("save-init").addEventListener("click", function() {
106
+ var num = function(id) {
107
+ var v = document.getElementById(id).value.trim();
108
+ return v === "" ? null : Number(v);
109
+ };
110
+ save(this, {
111
+ maxPrMonths: num("months"),
112
+ maxCommits: num("commits"),
113
+ maxPullRequestChangeLines: num("lines"),
114
+ maxComments: num("comments")
115
+ });
116
+ });
117
+ document.getElementById("remove").addEventListener("click", function() {
118
+ if (!confirm("Stop reviewing this repository?")) return;
119
+ var btn = this;
120
+ run(btn, btn.closest("[data-async]"), async function() {
121
+ await api("DELETE", "/api/repos/" + repo);
122
+ location.href = "/";
123
+ });
124
+ });
125
+ </script>`,
126
+ }));
127
+ }
@@ -0,0 +1,17 @@
1
+ import type { AuthMethods } from "../auth.ts";
2
+ export type PageDeps = {
3
+ password: string;
4
+ webhookUrl?: string;
5
+ secureCookie?: boolean;
6
+ githubApp?: {
7
+ appId: string;
8
+ privateKeyPem: string;
9
+ };
10
+ auth?: AuthMethods;
11
+ githubOAuth?: {
12
+ clientId: string;
13
+ clientSecret: string;
14
+ allowedUser: string;
15
+ };
16
+ };
17
+ export declare function handlePageRequest(request: Request, deps: PageDeps, ip: string): Promise<Response | undefined>;
@@ -0,0 +1,312 @@
1
+ import { clearOauthStateCookieHeader, clearSessionCookieHeader, createSession, login, logout, oauthStateCookieHeader, randomToken, readOauthState, readSessionToken, sessionCookieHeader, verifySession, } from "../auth.js";
2
+ import { githubAuthorizeUrl, githubLoginFromCode } from "../../github/oauth.js";
3
+ import { handleClient, handleLogo, handleStyles } from "../assets.js";
4
+ import { readConfig } from "../../config.js";
5
+ import { activityFeed, listReposForHome, prDetail, repoKnowledge, RepoNotFound, repoOverview, repoPulls, repoRemoteReviews, runningJobs, skippedDeliveries, statsForRange, } from "../../services/dashboard.js";
6
+ import { money } from "./layout.js";
7
+ import { renderHome, renderLogin } from "./home.js";
8
+ import { renderSetup } from "./setup.js";
9
+ import { renderAddRepo } from "./add_repo.js";
10
+ import { renderRepo } from "./repo.js";
11
+ import { renderRepoPulls } from "./repo_prs.js";
12
+ import { renderRepoRemote } from "./repo_remote.js";
13
+ import { renderPr } from "./pr.js";
14
+ import { renderKnowledge } from "./knowledge.js";
15
+ import { renderRepoSettings } from "./repo_settings.js";
16
+ import { renderActivity, renderJob } from "./activity.js";
17
+ import { renderAnalytics } from "./analytics.js";
18
+ import { renderSettings } from "./settings.js";
19
+ import { getRepo } from "../../store/repos.js";
20
+ import { getJob } from "../../store/jobs.js";
21
+ import { getLogsSince } from "../../services/jobs.js";
22
+ import { listInstallationsWithRepos } from "../../github/app.js";
23
+ const REPO = /^\/repos\/([^/]+)\/([^/]+)(?:\/(pulls|knowledge|settings)(?:\/(\d+))?)?$/;
24
+ export async function handlePageRequest(request, deps, ip) {
25
+ const url = new URL(request.url);
26
+ if (url.pathname === "/styles.css" && request.method === "GET") {
27
+ return await handleStyles();
28
+ }
29
+ if (url.pathname === "/client.js" && request.method === "GET") {
30
+ return await handleClient();
31
+ }
32
+ if (url.pathname === "/logo.png" && request.method === "GET") {
33
+ return await handleLogo();
34
+ }
35
+ if (url.pathname === "/dashboard") {
36
+ return Response.redirect(`${url.origin}/`, 303);
37
+ }
38
+ const auth = deps.auth ?? { password: true, github: false };
39
+ if (url.pathname === "/login" && request.method === "GET") {
40
+ const session = await sessionOf(request);
41
+ if (session)
42
+ return Response.redirect(`${url.origin}/`, 303);
43
+ return renderLogin({
44
+ next: safeNext(url.searchParams.get("next")),
45
+ error: url.searchParams.get("error") ?? undefined,
46
+ showPassword: auth.password,
47
+ showGithub: auth.github,
48
+ });
49
+ }
50
+ if (url.pathname === "/login" && request.method === "POST") {
51
+ return await handleLoginForm(request, deps, ip, auth);
52
+ }
53
+ if (url.pathname === "/auth/github" && request.method === "GET") {
54
+ if (!auth.github || !deps.githubOAuth) {
55
+ return new Response("GitHub sign-in is not enabled.", { status: 404 });
56
+ }
57
+ const next = safeNext(url.searchParams.get("next"));
58
+ const state = randomToken();
59
+ const authorizeUrl = githubAuthorizeUrl(deps.githubOAuth.clientId, `${url.origin}/auth/github/callback`, state);
60
+ return new Response(null, {
61
+ status: 303,
62
+ headers: {
63
+ location: authorizeUrl,
64
+ "set-cookie": oauthStateCookieHeader(state, next, Boolean(deps.secureCookie)),
65
+ },
66
+ });
67
+ }
68
+ if (url.pathname === "/auth/github/callback" && request.method === "GET") {
69
+ if (!auth.github || !deps.githubOAuth) {
70
+ return new Response("GitHub sign-in is not enabled.", { status: 404 });
71
+ }
72
+ return await handleGithubCallback(request, url, deps.githubOAuth, deps);
73
+ }
74
+ if (url.pathname === "/logout" && request.method === "POST") {
75
+ const token = readSessionToken(request);
76
+ if (token)
77
+ await logout(token);
78
+ return new Response(null, {
79
+ status: 303,
80
+ headers: {
81
+ location: "/login",
82
+ "set-cookie": clearSessionCookieHeader(Boolean(deps.secureCookie)),
83
+ },
84
+ });
85
+ }
86
+ const session = await sessionOf(request);
87
+ if (!session) {
88
+ if (isPagePath(url.pathname)) {
89
+ const next = encodeURIComponent(url.pathname + url.search);
90
+ return Response.redirect(`${url.origin}/login?next=${next}`, 303);
91
+ }
92
+ return undefined;
93
+ }
94
+ const username = session.username;
95
+ try {
96
+ if (url.pathname === "/" && request.method === "GET") {
97
+ return renderHome(username, listReposForHome().map(toHomeRow));
98
+ }
99
+ if (url.pathname === "/setup" && request.method === "GET") {
100
+ const config = readConfig();
101
+ return renderSetup(username, {
102
+ ai: Boolean(config.ai && config.ai !== "none" && config.token),
103
+ github: Boolean(config.auth === "gh" || (config.auth === "pat" && config.githubPat)),
104
+ app: Boolean(config.githubAppId && config.githubAppPrivateKey),
105
+ });
106
+ }
107
+ if (url.pathname === "/repos/new" && request.method === "GET") {
108
+ return renderAddRepo(username, await repoPicker(deps.githubApp));
109
+ }
110
+ if (url.pathname === "/activity" && request.method === "GET") {
111
+ const page = Number(url.searchParams.get("page") ?? 1);
112
+ return renderActivity(username, runningJobs(), activityFeed(page, 20), skippedDeliveries());
113
+ }
114
+ const jobMatch = /^\/activity\/([^/]+)$/.exec(url.pathname);
115
+ if (jobMatch && request.method === "GET") {
116
+ const job = getJob(jobMatch[1]);
117
+ if (!job)
118
+ return new Response("not found", { status: 404 });
119
+ return renderJob(username, job, getLogsSince(job.id));
120
+ }
121
+ if (url.pathname === "/analytics" && request.method === "GET") {
122
+ const range = url.searchParams.get("range") ?? "30d";
123
+ return renderAnalytics(username, range, statsForRange(range));
124
+ }
125
+ if (url.pathname === "/settings" && request.method === "GET") {
126
+ const config = readConfig();
127
+ return renderSettings(username, config, config.webhookUrl || deps.webhookUrl || "");
128
+ }
129
+ const match = REPO.exec(url.pathname);
130
+ if (match && request.method === "GET") {
131
+ const fullName = `${decodeURIComponent(match[1])}/${decodeURIComponent(match[2])}`;
132
+ const row = requireRepo(fullName);
133
+ const sub = match[3];
134
+ const pr = match[4] ? Number(match[4]) : undefined;
135
+ if (!sub)
136
+ return renderRepo(username, repoOverview(fullName));
137
+ if (sub === "pulls" && pr) {
138
+ return renderPr(username, fullName, prDetail(fullName, pr));
139
+ }
140
+ if (sub === "pulls") {
141
+ const page = Number(url.searchParams.get("page") ?? 1);
142
+ return renderRepoPulls(username, fullName, repoPulls(fullName, page, 20));
143
+ }
144
+ if (sub === "remote") {
145
+ return renderRepoRemote(username, fullName, repoRemoteReviews(fullName));
146
+ }
147
+ if (sub === "knowledge") {
148
+ return renderKnowledge(username, await repoKnowledge(fullName), url.searchParams.get("view") ?? undefined);
149
+ }
150
+ if (sub === "settings") {
151
+ const config = readConfig();
152
+ return renderRepoSettings(username, row, config.repos?.[fullName] ?? {});
153
+ }
154
+ }
155
+ }
156
+ catch (error) {
157
+ if (error instanceof RepoNotFound) {
158
+ return new Response("not found", { status: 404 });
159
+ }
160
+ throw error;
161
+ }
162
+ return undefined;
163
+ }
164
+ function toHomeRow(item) {
165
+ const job = item.latestJob;
166
+ let statusKind = "ok";
167
+ let status = "Active";
168
+ if (job && (job.status === "running" || job.status === "queued")) {
169
+ statusKind = "run";
170
+ status = "Setting up";
171
+ }
172
+ else if (job?.status === "failed" && !item.repo.knowledge_built_at) {
173
+ statusKind = "err";
174
+ status = "Setup failed";
175
+ }
176
+ let knowledge = "Not built";
177
+ let knowledgeAt;
178
+ if (item.repo.knowledge_built_at) {
179
+ knowledge =
180
+ (item.drift?.prs_since ?? 0) > 0 ? "Needs update" : "Up to date";
181
+ knowledgeAt = item.repo.knowledge_built_at;
182
+ }
183
+ return {
184
+ fullName: item.repo.full_name,
185
+ statusKind,
186
+ status,
187
+ knowledge,
188
+ knowledgeAt,
189
+ autoOn: item.repo.auto_review === 1,
190
+ reviews: String(item.stats.reviews),
191
+ cost: money(item.stats.cost),
192
+ };
193
+ }
194
+ function requireRepo(fullName) {
195
+ const row = getRepo(fullName);
196
+ if (!row || row.active !== 1)
197
+ throw new RepoNotFound(fullName);
198
+ return row;
199
+ }
200
+ async function repoPicker(githubApp) {
201
+ if (!githubApp) {
202
+ return {
203
+ repos: [],
204
+ error: "Configure the GitHub App in Settings first.",
205
+ };
206
+ }
207
+ try {
208
+ const installations = await listInstallationsWithRepos(githubApp.appId, githubApp.privateKeyPem);
209
+ return {
210
+ repos: installations.flatMap(({ installation, repos }) => repos
211
+ .filter((repo) => repo.fullName)
212
+ .map((repo) => ({
213
+ fullName: repo.fullName,
214
+ account: installation.account?.login ?? "unknown",
215
+ alreadyActive: Boolean(getRepo(repo.fullName)?.active),
216
+ }))),
217
+ };
218
+ }
219
+ catch (error) {
220
+ console.error("[dashboard] Could not list repositories from GitHub:", error instanceof Error ? (error.stack ?? error.message) : String(error));
221
+ return {
222
+ repos: [],
223
+ error: "Could not list repositories from GitHub.",
224
+ };
225
+ }
226
+ }
227
+ function isPagePath(pathname) {
228
+ return (pathname === "/" ||
229
+ pathname === "/setup" ||
230
+ pathname === "/activity" ||
231
+ pathname.startsWith("/activity/") ||
232
+ pathname === "/analytics" ||
233
+ pathname === "/settings" ||
234
+ pathname === "/repos/new" ||
235
+ pathname.startsWith("/repos/"));
236
+ }
237
+ async function sessionOf(request) {
238
+ const token = readSessionToken(request);
239
+ if (!token)
240
+ return undefined;
241
+ return await verifySession(token);
242
+ }
243
+ function safeNext(value) {
244
+ if (!value || !value.startsWith("/") || value.startsWith("//"))
245
+ return "/";
246
+ return value;
247
+ }
248
+ async function handleLoginForm(request, deps, ip, auth) {
249
+ const form = await request.formData();
250
+ const password = String(form.get("password") ?? "");
251
+ const next = safeNext(String(form.get("next") ?? "/"));
252
+ if (!auth.password) {
253
+ return renderLogin({
254
+ next,
255
+ error: "Password sign-in is disabled.",
256
+ showPassword: auth.password,
257
+ showGithub: auth.github,
258
+ });
259
+ }
260
+ const result = await login(password, deps.password, ip);
261
+ if (!result) {
262
+ return renderLogin({
263
+ next,
264
+ error: "Wrong password.",
265
+ showPassword: auth.password,
266
+ showGithub: auth.github,
267
+ });
268
+ }
269
+ return new Response(null, {
270
+ status: 303,
271
+ headers: {
272
+ location: next,
273
+ "set-cookie": sessionCookieHeader(result.token, Boolean(deps.secureCookie)),
274
+ },
275
+ });
276
+ }
277
+ /** `state` round-trips through an HttpOnly cookie set by `/auth/github`
278
+ * (see `oauthStateCookieHeader`), not server memory — comparing it against
279
+ * the query param is what stops a forged callback from creating a session. */
280
+ async function handleGithubCallback(request, url, githubOAuth, deps) {
281
+ const clearState = clearOauthStateCookieHeader(Boolean(deps.secureCookie));
282
+ const fail = (message) => new Response(null, {
283
+ status: 303,
284
+ headers: {
285
+ location: `${url.origin}/login?error=${encodeURIComponent(message)}`,
286
+ "set-cookie": clearState,
287
+ },
288
+ });
289
+ const saved = readOauthState(request);
290
+ const code = url.searchParams.get("code");
291
+ const state = url.searchParams.get("state");
292
+ if (!saved || !code || !state || saved.state !== state) {
293
+ return fail("GitHub sign-in failed.");
294
+ }
295
+ const login = await githubLoginFromCode({
296
+ clientId: githubOAuth.clientId,
297
+ clientSecret: githubOAuth.clientSecret,
298
+ code,
299
+ redirectUri: `${url.origin}/auth/github/callback`,
300
+ });
301
+ if (!login || login.toLowerCase() !== githubOAuth.allowedUser.toLowerCase()) {
302
+ return fail("This GitHub account is not allowed to sign in.");
303
+ }
304
+ const result = await createSession();
305
+ return new Response(null, {
306
+ status: 303,
307
+ headers: {
308
+ location: saved.next,
309
+ "set-cookie": sessionCookieHeader(result.token, Boolean(deps.secureCookie)),
310
+ },
311
+ });
312
+ }
@@ -0,0 +1,2 @@
1
+ import type { UserConfig } from "../../config.ts";
2
+ export declare function renderSettings(username: string, config: UserConfig, webhookUrl: string): Response;