co-maintainer 0.4.13 → 0.5.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.
- package/LICENSE +21 -21
- package/README.md +57 -38
- package/dist/main.js +2 -3
- package/dist/package.json +21 -15
- package/dist/src/ai/batch.d.ts +24 -1
- package/dist/src/ai/batch.js +75 -23
- package/dist/src/ai/estimate.d.ts +40 -0
- package/dist/src/ai/estimate.js +113 -0
- package/dist/src/ai/fake.d.ts +1 -1
- package/dist/src/ai/fake.js +1 -1
- package/dist/src/ai/hetzner.js +1 -1
- package/dist/src/ai/openrouter.d.ts +13 -0
- package/dist/src/ai/openrouter.js +66 -12
- package/dist/src/ai/pricing.d.ts +14 -0
- package/dist/src/ai/pricing.js +77 -0
- package/dist/src/ai/provider.js +8 -0
- package/dist/src/ai/verify.d.ts +25 -0
- package/dist/src/ai/verify.js +78 -0
- package/dist/src/cli/args.js +25 -29
- package/dist/src/cli/commands/config.d.ts +44 -0
- package/dist/src/cli/commands/config.js +220 -0
- package/dist/src/cli/commands/probe.js +70 -103
- package/dist/src/cli/commands/registry.d.ts +57 -0
- package/dist/src/cli/commands/registry.js +713 -0
- package/dist/src/cli/commands/review.js +66 -43
- package/dist/src/cli/commands/serve.d.ts +4 -2
- package/dist/src/cli/commands/serve.js +14 -10
- package/dist/src/cli/commands/set.d.ts +2 -1
- package/dist/src/cli/commands/set.js +63 -17
- package/dist/src/cli/commands/view.d.ts +1 -0
- package/dist/src/cli/commands/view.js +180 -0
- package/dist/src/cli/error.d.ts +39 -0
- package/dist/src/cli/error.js +64 -0
- package/dist/src/cli/main.d.ts +7 -0
- package/dist/src/cli/main.js +82 -1
- package/dist/src/cli/prompt.js +21 -7
- package/dist/src/cli/review_args.d.ts +4 -0
- package/dist/src/cli/review_args.js +22 -9
- package/dist/src/cli/review_output.d.ts +2 -2
- package/dist/src/cli/review_output.js +7 -6
- package/dist/src/cli/review_result.d.ts +59 -8
- package/dist/src/cli/review_result.js +145 -103
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +31 -0
- package/dist/src/github/app.d.ts +13 -0
- package/dist/src/github/app.js +23 -0
- package/dist/src/github/app_manifest.d.ts +50 -0
- package/dist/src/github/app_manifest.js +138 -0
- package/dist/src/github/client.js +7 -1
- package/dist/src/github/collect.js +1 -1
- package/dist/src/github/gh.js +75 -18
- package/dist/src/knowledge/facts.js +8 -3
- package/dist/src/knowledge/guide.d.ts +8 -0
- package/dist/src/knowledge/guide.js +32 -8
- package/dist/src/knowledge/probe.js +13 -3
- package/dist/src/knowledge/sections.d.ts +9 -0
- package/dist/src/knowledge/sections.js +16 -0
- package/dist/src/knowledge/skill.d.ts +5 -0
- package/dist/src/knowledge/skill.js +68 -21
- package/dist/src/knowledge/synthesis.d.ts +17 -1
- package/dist/src/knowledge/synthesis.js +70 -15
- package/dist/src/knowledge/types.d.ts +5 -0
- package/dist/src/local/codegraph_prepare.d.ts +3 -2
- package/dist/src/local/codegraph_prepare.js +4 -2
- package/dist/src/local/git_ops.d.ts +5 -4
- package/dist/src/local/git_ops.js +8 -9
- package/dist/src/local/review_local.js +36 -14
- package/dist/src/pr/checkout.js +14 -3
- package/dist/src/pr/codegraph_tools.js +3 -3
- package/dist/src/pr/diff_summary.js +3 -3
- package/dist/src/pr/findings.js +16 -5
- package/dist/src/pr/findings_json.d.ts +26 -0
- package/dist/src/pr/findings_json.js +187 -0
- package/dist/src/pr/review_copy.d.ts +20 -0
- package/dist/src/pr/review_copy.js +40 -0
- package/dist/src/pr/reviewer.d.ts +2 -1
- package/dist/src/pr/reviewer.js +84 -91
- package/dist/src/remote/client.js +42 -42
- package/dist/src/remote/http.d.ts +5 -0
- package/dist/src/remote/http.js +49 -0
- package/dist/src/remote/server/guides.d.ts +22 -0
- package/dist/src/remote/server/guides.js +81 -0
- package/dist/src/remote/server/routes.js +24 -0
- package/dist/src/review/blocking.d.ts +35 -0
- package/dist/src/review/blocking.js +45 -0
- package/dist/src/review/carry_over.d.ts +12 -1
- package/dist/src/review/carry_over.js +35 -9
- package/dist/src/review/engine.d.ts +1 -0
- package/dist/src/review/guides.js +28 -1
- package/dist/src/server/api/installations.js +1 -1
- package/dist/src/server/api/repos.js +86 -3
- package/dist/src/server/api/settings.js +3 -3
- package/dist/src/server/app.d.ts +3 -0
- package/dist/src/server/app.js +12 -5
- package/dist/src/server/pages/activity.d.ts +1 -0
- package/dist/src/server/pages/activity.js +2 -2
- package/dist/src/server/pages/add_repo.js +73 -5
- package/dist/src/server/pages/client.d.ts +1 -1
- package/dist/src/server/pages/client.js +1 -1
- package/dist/src/server/pages/home.d.ts +6 -1
- package/dist/src/server/pages/home.js +25 -2
- package/dist/src/server/pages/knowledge.js +1 -1
- package/dist/src/server/pages/layout.js +2 -2
- package/dist/src/server/pages/repo.d.ts +1 -1
- package/dist/src/server/pages/repo.js +21 -2
- package/dist/src/server/pages/repo_prs.d.ts +5 -1
- package/dist/src/server/pages/repo_prs.js +36 -1
- package/dist/src/server/pages/repo_remote.js +2 -2
- package/dist/src/server/pages/repo_settings.js +2 -2
- package/dist/src/server/pages/router.d.ts +6 -0
- package/dist/src/server/pages/router.js +129 -9
- package/dist/src/server/pages/settings.d.ts +1 -1
- package/dist/src/server/pages/settings.js +87 -9
- package/dist/src/server/pages/styles.d.ts +1 -1
- package/dist/src/server/pages/styles.js +1 -1
- package/dist/src/services/probe.d.ts +28 -0
- package/dist/src/services/probe.js +133 -0
- package/dist/src/services/remake_cron.d.ts +1 -1
- package/dist/src/services/remake_cron.js +3 -3
- package/dist/src/services/remote_review.js +14 -4
- package/dist/src/services/review.d.ts +11 -0
- package/dist/src/services/review.js +39 -11
- package/dist/src/services/setup.js +37 -19
- package/dist/src/services/setup_checklist.d.ts +10 -0
- package/dist/src/services/setup_checklist.js +87 -0
- package/dist/src/store/app_db.js +5 -2
- package/dist/src/store/cache_db.d.ts +4 -0
- package/dist/src/store/cache_db.js +27 -4
- package/dist/src/store/deliveries.d.ts +7 -0
- package/dist/src/store/deliveries.js +15 -0
- package/dist/src/tools/codegraph.d.ts +30 -0
- package/dist/src/tools/codegraph.js +49 -12
- package/dist/src/types.d.ts +10 -0
- package/dist/src/util/log.d.ts +3 -0
- package/dist/src/util/log.js +12 -0
- package/dist/src/util/run_summary.d.ts +33 -0
- package/dist/src/util/run_summary.js +47 -0
- package/dist/src/util/webhook_reachability.d.ts +11 -0
- package/dist/src/util/webhook_reachability.js +64 -0
- package/package.json +21 -15
|
@@ -90,9 +90,9 @@ export function renderRepoSettings(username, repo, config) {
|
|
|
90
90
|
</div>
|
|
91
91
|
<div class="card" data-async>
|
|
92
92
|
${skSlot()}
|
|
93
|
-
<div class="hd"><h2>Scheduled
|
|
93
|
+
<div class="hd"><h2>Scheduled sync</h2></div>
|
|
94
94
|
<div class="bd">
|
|
95
|
-
<p class="muted" style="margin:0 0 18px">Rebuild the knowledge for this repository on a schedule. Times are UTC and a
|
|
95
|
+
<p class="muted" style="margin:0 0 18px">Rebuild the knowledge for this repository on a schedule. Times are UTC and a sync runs at most once an hour.</p>
|
|
96
96
|
<div class="field wide"><label>Cron schedule</label>
|
|
97
97
|
<input id="cron" placeholder="0 3 * * 1" value="${text(config.remakeCron ?? "")}">
|
|
98
98
|
<div class="hint">${text(cronHint(repo, config.remakeCron))}</div></div>
|
|
@@ -13,5 +13,11 @@ export type PageDeps = {
|
|
|
13
13
|
clientSecret: string;
|
|
14
14
|
allowedUser: string;
|
|
15
15
|
};
|
|
16
|
+
/** Behind a reverse proxy this process trusts, absolute URLs are built
|
|
17
|
+
* from `x-forwarded-host` and `x-forwarded-proto`. */
|
|
18
|
+
trustProxy?: boolean;
|
|
19
|
+
/** `CM_LOGIN_HINT`, shown on the sign-in page in place of the default
|
|
20
|
+
* "printed when serve started" line. */
|
|
21
|
+
loginHint?: string;
|
|
16
22
|
};
|
|
17
23
|
export declare function handlePageRequest(request: Request, deps: PageDeps, ip: string): Promise<Response | undefined>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { clearOauthStateCookieHeader, clearSessionCookieHeader, createSession, login, logout, oauthStateCookieHeader, randomToken, readOauthState, readSessionToken, sessionCookieHeader, verifySession, } from "../auth.js";
|
|
2
2
|
import { githubAuthorizeUrl, githubLoginFromCode } from "../../github/oauth.js";
|
|
3
3
|
import { handleClient, handleLogo, handleStyles } from "../assets.js";
|
|
4
|
-
import { readConfig } from "../../config.js";
|
|
5
4
|
import { activityFeed, listReposForHome, prDetail, repoKnowledge, RepoNotFound, repoOverview, repoPulls, repoRemoteReviews, runningJobs, skippedDeliveries, statsForRange, } from "../../services/dashboard.js";
|
|
6
|
-
import { money } from "./layout.js";
|
|
5
|
+
import { escapeHtml, html, money } from "./layout.js";
|
|
6
|
+
import { readConfig, resolveAppPrivateKey, writeUserConfig, } from "../../config.js";
|
|
7
|
+
import { appNameProblem, beginManifestState, buildAppManifest, consumeManifestState, convertManifest, } from "../../github/app_manifest.js";
|
|
7
8
|
import { renderHome, renderLogin } from "./home.js";
|
|
8
9
|
import { renderSetup } from "./setup.js";
|
|
9
10
|
import { renderAddRepo } from "./add_repo.js";
|
|
@@ -19,7 +20,8 @@ import { renderSettings } from "./settings.js";
|
|
|
19
20
|
import { getRepo } from "../../store/repos.js";
|
|
20
21
|
import { getJob } from "../../store/jobs.js";
|
|
21
22
|
import { getLogsSince } from "../../services/jobs.js";
|
|
22
|
-
import { listInstallationsWithRepos } from "../../github/app.js";
|
|
23
|
+
import { listInstallationsWithRepos, listOpenPulls } from "../../github/app.js";
|
|
24
|
+
import { setupChecklist } from "../../services/setup_checklist.js";
|
|
23
25
|
const REPO = /^\/repos\/([^/]+)\/([^/]+)(?:\/(pulls|knowledge|settings|remote)(?:\/(\d+))?)?$/;
|
|
24
26
|
/** Matches a repository page URL. Only `pulls` is keyed by a number, so a stray
|
|
25
27
|
* suffix such as `/repos/o/r/remote/7` must fall through to a 404 rather than
|
|
@@ -54,6 +56,7 @@ export async function handlePageRequest(request, deps, ip) {
|
|
|
54
56
|
error: url.searchParams.get("error") ?? undefined,
|
|
55
57
|
showPassword: auth.password,
|
|
56
58
|
showGithub: auth.github,
|
|
59
|
+
hint: deps.loginHint,
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
if (url.pathname === "/login" && request.method === "POST") {
|
|
@@ -103,19 +106,27 @@ export async function handlePageRequest(request, deps, ip) {
|
|
|
103
106
|
const username = session.username;
|
|
104
107
|
try {
|
|
105
108
|
if (url.pathname === "/" && request.method === "GET") {
|
|
106
|
-
|
|
109
|
+
const config = readConfig();
|
|
110
|
+
return renderHome(username, listReposForHome().map(toHomeRow), setupChecklist(config.webhookUrl || deps.webhookUrl || ""));
|
|
107
111
|
}
|
|
108
112
|
if (url.pathname === "/setup" && request.method === "GET") {
|
|
109
113
|
const config = readConfig();
|
|
110
114
|
return renderSetup(username, {
|
|
111
115
|
ai: Boolean(config.ai && config.ai !== "none" && config.token),
|
|
112
116
|
github: Boolean(config.auth === "gh" || (config.auth === "pat" && config.githubPat)),
|
|
113
|
-
app: Boolean(config.githubAppId && config
|
|
117
|
+
app: Boolean(config.githubAppId && resolveAppPrivateKey(config)),
|
|
114
118
|
});
|
|
115
119
|
}
|
|
116
120
|
if (url.pathname === "/repos/new" && request.method === "GET") {
|
|
117
121
|
return renderAddRepo(username, await repoPicker(deps.githubApp));
|
|
118
122
|
}
|
|
123
|
+
if (url.pathname === "/github/app-manifest" && request.method === "POST") {
|
|
124
|
+
return await beginAppManifest(request, url, deps);
|
|
125
|
+
}
|
|
126
|
+
if (url.pathname === "/github/app-manifest/callback" &&
|
|
127
|
+
request.method === "GET") {
|
|
128
|
+
return await finishAppManifest(request, url);
|
|
129
|
+
}
|
|
119
130
|
if (url.pathname === "/activity" && request.method === "GET") {
|
|
120
131
|
const page = Number(url.searchParams.get("page") ?? 1);
|
|
121
132
|
return renderActivity(username, runningJobs(), activityFeed(page, 20), skippedDeliveries());
|
|
@@ -133,7 +144,7 @@ export async function handlePageRequest(request, deps, ip) {
|
|
|
133
144
|
}
|
|
134
145
|
if (url.pathname === "/settings" && request.method === "GET") {
|
|
135
146
|
const config = readConfig();
|
|
136
|
-
return renderSettings(username, config, config.webhookUrl || deps.webhookUrl || "");
|
|
147
|
+
return renderSettings(username, config, config.webhookUrl || deps.webhookUrl || "", requestBaseUrl(request, url, Boolean(deps.trustProxy)));
|
|
137
148
|
}
|
|
138
149
|
const match = matchRepo(url.pathname);
|
|
139
150
|
if (match && request.method === "GET") {
|
|
@@ -141,14 +152,16 @@ export async function handlePageRequest(request, deps, ip) {
|
|
|
141
152
|
const row = requireRepo(fullName);
|
|
142
153
|
const sub = match[3];
|
|
143
154
|
const pr = match[4] ? Number(match[4]) : undefined;
|
|
144
|
-
if (!sub)
|
|
145
|
-
|
|
155
|
+
if (!sub) {
|
|
156
|
+
const config = readConfig();
|
|
157
|
+
return renderRepo(username, repoOverview(fullName), config.webhookUrl || deps.webhookUrl || "");
|
|
158
|
+
}
|
|
146
159
|
if (sub === "pulls" && pr) {
|
|
147
160
|
return renderPr(username, fullName, prDetail(fullName, pr));
|
|
148
161
|
}
|
|
149
162
|
if (sub === "pulls") {
|
|
150
163
|
const page = Number(url.searchParams.get("page") ?? 1);
|
|
151
|
-
return renderRepoPulls(username, fullName, repoPulls(fullName, page, 20));
|
|
164
|
+
return renderRepoPulls(username, fullName, repoPulls(fullName, page, 20), await openPullsFor(fullName, deps.githubApp));
|
|
152
165
|
}
|
|
153
166
|
if (sub === "remote") {
|
|
154
167
|
return renderRepoRemote(username, fullName, repoRemoteReviews(fullName));
|
|
@@ -243,6 +256,21 @@ function isPagePath(pathname) {
|
|
|
243
256
|
pathname === "/repos/new" ||
|
|
244
257
|
pathname.startsWith("/repos/"));
|
|
245
258
|
}
|
|
259
|
+
/** Open pull requests for the Pull requests tab, or `undefined` when the App
|
|
260
|
+
* is not configured or cannot see the repo. A GitHub failure degrades to
|
|
261
|
+
* `undefined` (the tab still works by number) rather than blanking the page. */
|
|
262
|
+
async function openPullsFor(fullName, githubApp) {
|
|
263
|
+
if (!githubApp)
|
|
264
|
+
return { pulls: undefined, appConfigured: false };
|
|
265
|
+
try {
|
|
266
|
+
const pulls = await listOpenPulls(githubApp.appId, githubApp.privateKeyPem, fullName);
|
|
267
|
+
return { pulls, appConfigured: true };
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.error("[dashboard] Could not list open pull requests:", error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
271
|
+
return { pulls: undefined, appConfigured: true };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
246
274
|
async function sessionOf(request) {
|
|
247
275
|
const token = readSessionToken(request);
|
|
248
276
|
if (!token)
|
|
@@ -254,6 +282,96 @@ function safeNext(value) {
|
|
|
254
282
|
return "/";
|
|
255
283
|
return value;
|
|
256
284
|
}
|
|
285
|
+
/** The externally visible origin. Behind a trusted proxy the browser reached
|
|
286
|
+
* `x-forwarded-host`/`-proto`, which is what GitHub must be told for the
|
|
287
|
+
* callback and webhook URLs. Without the flag, `new URL(request.url).origin`
|
|
288
|
+
* is the socket's own view and a spoofed header is ignored. */
|
|
289
|
+
function requestBaseUrl(request, url, trustProxy) {
|
|
290
|
+
if (!trustProxy)
|
|
291
|
+
return url.origin;
|
|
292
|
+
const last = (name) => request.headers.get(name)?.split(",").pop()?.trim();
|
|
293
|
+
const host = last("x-forwarded-host");
|
|
294
|
+
if (!host)
|
|
295
|
+
return url.origin;
|
|
296
|
+
const proto = last("x-forwarded-proto");
|
|
297
|
+
const scheme = proto === "https" || proto === "http"
|
|
298
|
+
? proto
|
|
299
|
+
: url.protocol.replace(":", "");
|
|
300
|
+
return `${scheme}://${host}`;
|
|
301
|
+
}
|
|
302
|
+
/** Step one of the App manifest flow: build the manifest from the posted
|
|
303
|
+
* name and auto-submit it to GitHub. The `state` is kept server-side (ten
|
|
304
|
+
* minutes, single use) so a forged callback cannot write credentials. */
|
|
305
|
+
async function beginAppManifest(request, url, deps) {
|
|
306
|
+
let name = "";
|
|
307
|
+
try {
|
|
308
|
+
const form = await request.formData();
|
|
309
|
+
name = String(form.get("name") ?? "");
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return new Response("Expected a form post.", { status: 400 });
|
|
313
|
+
}
|
|
314
|
+
const problem = appNameProblem(name);
|
|
315
|
+
if (problem)
|
|
316
|
+
return new Response(problem, { status: 422 });
|
|
317
|
+
const config = readConfig();
|
|
318
|
+
const baseUrl = requestBaseUrl(request, url, Boolean(deps.trustProxy));
|
|
319
|
+
const webhookUrl = config.webhookUrl || deps.webhookUrl || "";
|
|
320
|
+
const manifest = buildAppManifest({ name, baseUrl, webhookUrl });
|
|
321
|
+
const state = beginManifestState();
|
|
322
|
+
return html(`<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
323
|
+
<title>Creating the GitHub App · co-maintainer</title></head>
|
|
324
|
+
<body>
|
|
325
|
+
<form id="manifest" action="https://github.com/settings/apps/new?state=${escapeHtml(state)}" method="post">
|
|
326
|
+
<input type="hidden" name="manifest" value="${escapeHtml(JSON.stringify(manifest).replace(/</g, "\\u003c"))}">
|
|
327
|
+
<noscript><button type="submit">Continue to GitHub</button></noscript>
|
|
328
|
+
</form>
|
|
329
|
+
<p>Taking you to GitHub to create the App.</p>
|
|
330
|
+
<script>document.getElementById("manifest").submit();</script>
|
|
331
|
+
</body></html>`);
|
|
332
|
+
}
|
|
333
|
+
/** Step three, after GitHub redirects back with a one-time `code`. The code
|
|
334
|
+
* is converted into real credentials and written to the config keys the
|
|
335
|
+
* manual fields already use, then the browser is sent to the install page. */
|
|
336
|
+
async function finishAppManifest(request, url) {
|
|
337
|
+
const state = url.searchParams.get("state") ?? "";
|
|
338
|
+
if (!consumeManifestState(state)) {
|
|
339
|
+
return new Response("This App creation link is no longer valid. Start again from Settings.", { status: 400 });
|
|
340
|
+
}
|
|
341
|
+
const code = url.searchParams.get("code") ?? "";
|
|
342
|
+
if (!code) {
|
|
343
|
+
return new Response("GitHub did not return a manifest code.", {
|
|
344
|
+
status: 400,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const config = readConfig();
|
|
348
|
+
let conversion;
|
|
349
|
+
try {
|
|
350
|
+
conversion = await convertManifest(code);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
console.error("[dashboard] GitHub App manifest conversion failed:", error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
354
|
+
return new Response("Could not create the GitHub App. Try again from Settings.", { status: 502 });
|
|
355
|
+
}
|
|
356
|
+
const patch = {
|
|
357
|
+
githubAppId: conversion.appId,
|
|
358
|
+
githubAppPrivateKey: conversion.pem,
|
|
359
|
+
};
|
|
360
|
+
if (conversion.webhookSecret) {
|
|
361
|
+
patch.githubWebhookSecret = conversion.webhookSecret;
|
|
362
|
+
}
|
|
363
|
+
if (conversion.clientId && !config.githubOAuthClientId) {
|
|
364
|
+
patch.githubOAuthClientId = conversion.clientId;
|
|
365
|
+
}
|
|
366
|
+
if (conversion.clientSecret && !config.githubOAuthClientSecret) {
|
|
367
|
+
patch.githubOAuthClientSecret = conversion.clientSecret;
|
|
368
|
+
}
|
|
369
|
+
await writeUserConfig(patch);
|
|
370
|
+
const installUrl = conversion.slug
|
|
371
|
+
? `https://github.com/apps/${encodeURIComponent(conversion.slug)}/installations/new`
|
|
372
|
+
: "https://github.com/settings/apps";
|
|
373
|
+
return Response.redirect(installUrl, 303);
|
|
374
|
+
}
|
|
257
375
|
async function handleLoginForm(request, deps, ip, auth) {
|
|
258
376
|
const form = await request.formData();
|
|
259
377
|
const password = String(form.get("password") ?? "");
|
|
@@ -264,6 +382,7 @@ async function handleLoginForm(request, deps, ip, auth) {
|
|
|
264
382
|
error: "Password sign-in is disabled.",
|
|
265
383
|
showPassword: auth.password,
|
|
266
384
|
showGithub: auth.github,
|
|
385
|
+
hint: deps.loginHint,
|
|
267
386
|
});
|
|
268
387
|
}
|
|
269
388
|
const result = await login(password, deps.passwordStore, ip);
|
|
@@ -273,6 +392,7 @@ async function handleLoginForm(request, deps, ip, auth) {
|
|
|
273
392
|
error: "Wrong password.",
|
|
274
393
|
showPassword: auth.password,
|
|
275
394
|
showGithub: auth.github,
|
|
395
|
+
hint: deps.loginHint,
|
|
276
396
|
});
|
|
277
397
|
}
|
|
278
398
|
return new Response(null, {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { UserConfig } from "../../config.ts";
|
|
2
|
-
export declare function renderSettings(username: string, config: UserConfig, webhookUrl: string): Response;
|
|
2
|
+
export declare function renderSettings(username: string, config: UserConfig, webhookUrl: string, baseUrl: string): Response;
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { html, layout, skSlot, text } from "./layout.js";
|
|
2
|
+
import { resolveAppPrivateKey } from "../../config.js";
|
|
2
3
|
import { VERSION } from "../../version.js";
|
|
4
|
+
import { defaultAppName, manifestBlockedReason, } from "../../github/app_manifest.js";
|
|
3
5
|
const SOURCE_URL = "https://github.com/GroophyLifefor/co-maintainer";
|
|
4
|
-
export function renderSettings(username, config, webhookUrl) {
|
|
6
|
+
export function renderSettings(username, config, webhookUrl, baseUrl) {
|
|
5
7
|
const aiOk = Boolean(config.ai && config.ai !== "none" && config.token);
|
|
6
8
|
const ghOk = Boolean(config.auth === "gh" || (config.auth === "pat" && config.githubPat));
|
|
7
|
-
const appOk = Boolean(config.githubAppId && config
|
|
9
|
+
const appOk = Boolean(config.githubAppId && resolveAppPrivateKey(config));
|
|
10
|
+
let host = "localhost";
|
|
11
|
+
try {
|
|
12
|
+
host = new URL(baseUrl).hostname || host;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// baseUrl is built by the router from a valid Request URL; keep the
|
|
16
|
+
// fallback rather than blanking the default App name.
|
|
17
|
+
}
|
|
18
|
+
const manifestBlock = manifestBlockedReason(webhookUrl);
|
|
8
19
|
return html(layout({
|
|
9
20
|
title: "Settings · co-maintainer",
|
|
10
21
|
username,
|
|
@@ -25,7 +36,7 @@ export function renderSettings(username, config, webhookUrl) {
|
|
|
25
36
|
<div>
|
|
26
37
|
<div class="pagehead"><div><h1>Settings</h1>
|
|
27
38
|
<p class="lead">Used by every repository unless it overrides them.</p></div></div>
|
|
28
|
-
<div class="card" data-async>
|
|
39
|
+
<div class="card" id="ai" data-async>
|
|
29
40
|
${skSlot()}
|
|
30
41
|
<div class="hd"><h2>Models and API key</h2>
|
|
31
42
|
<span class="st ${aiOk ? "ok" : "warn"}" style="margin-left:auto">${aiOk ? "Configured" : "Not set"}</span></div>
|
|
@@ -40,12 +51,12 @@ export function renderSettings(username, config, webhookUrl) {
|
|
|
40
51
|
<input id="token" type="password" placeholder="Leave blank to keep the current key">
|
|
41
52
|
<div class="hint">${config.token ? "A key is saved" : "No key saved"}</div></div>
|
|
42
53
|
<div class="two" style="max-width:none">
|
|
43
|
-
<div class="field"><label>
|
|
54
|
+
<div class="field"><label>High model</label>
|
|
44
55
|
<input id="high" value="${text(config.highModel ?? "")}">
|
|
45
|
-
<div class="hint">Writes reviews</div></div>
|
|
46
|
-
<div class="field"><label>
|
|
56
|
+
<div class="hint">Writes reviews and synthesizes the guides</div></div>
|
|
57
|
+
<div class="field"><label>Low model</label>
|
|
47
58
|
<input id="low" value="${text(config.lowModel ?? "")}">
|
|
48
|
-
<div class="hint">
|
|
59
|
+
<div class="hint">Extracts facts from history during init and sync</div></div>
|
|
49
60
|
</div>
|
|
50
61
|
</div>
|
|
51
62
|
<div class="ft"><button class="primary" id="save-ai">Save</button></div>
|
|
@@ -71,6 +82,17 @@ export function renderSettings(username, config, webhookUrl) {
|
|
|
71
82
|
<span class="st ${appOk ? "ok" : "warn"}" style="margin-left:auto">${appOk ? "Configured" : "Not set"}</span></div>
|
|
72
83
|
<div class="bd">
|
|
73
84
|
<p class="muted" style="margin:0 0 16px">Needed to post reviews on pull requests.</p>
|
|
85
|
+
<div style="margin:0 0 16px">
|
|
86
|
+
<div class="two" style="max-width:none;margin:0 0 12px">
|
|
87
|
+
<div class="field"><label>App name</label>
|
|
88
|
+
<input id="app-name" value="${text(defaultAppName(host))}">
|
|
89
|
+
<div class="hint">Must be unique on GitHub. Editable.</div></div>
|
|
90
|
+
</div>
|
|
91
|
+
<button class="btn" id="create-app">Create GitHub App</button>
|
|
92
|
+
<div class="hint" style="margin:6px 0 0">${manifestBlock
|
|
93
|
+
? `${text(manifestBlock)}`
|
|
94
|
+
: "Opens GitHub, then fills App ID, private key, webhook secret, and OAuth below for you."}</div>
|
|
95
|
+
</div>
|
|
74
96
|
<div class="two" style="max-width:none">
|
|
75
97
|
<div class="field"><label>App ID</label>
|
|
76
98
|
<input id="app-id" value="${text(config.githubAppId ?? "")}"></div>
|
|
@@ -111,7 +133,7 @@ Leave blank to keep the current key"></textarea></div>
|
|
|
111
133
|
<div class="bd">
|
|
112
134
|
<div class="field"><label>Max concurrent jobs</label>
|
|
113
135
|
<input id="max-jobs" value="${text(config.maxConcurrentJobs ?? "")}" placeholder="No limit">
|
|
114
|
-
<div class="hint">Cap how many background jobs run at once across init,
|
|
136
|
+
<div class="hint">Cap how many background jobs run at once across init, sync, and review. Leave blank for no limit.</div></div>
|
|
115
137
|
<div class="two" style="max-width:none;margin-top:16px">
|
|
116
138
|
<div class="field"><label>Remote sync timeout (seconds)</label>
|
|
117
139
|
<input id="remote-timeout" value="${text(config.remoteSyncTimeoutSeconds ?? "")}" placeholder="10"></div>
|
|
@@ -128,6 +150,7 @@ Leave blank to keep the current key"></textarea></div>
|
|
|
128
150
|
<div class="hd"><h2>Remote review tokens</h2></div>
|
|
129
151
|
<div class="bd">
|
|
130
152
|
<p class="muted" style="margin:0 0 16px">Bearer tokens for <code>co-maintainer review --remote</code>. The secret is shown once when created.</p>
|
|
153
|
+
<div id="remote-token-secret" class="secret" hidden></div>
|
|
131
154
|
<div id="remote-token-list" class="muted">Loading…</div>
|
|
132
155
|
<div class="two" style="max-width:none;margin-top:16px">
|
|
133
156
|
<div class="field"><label>New token name</label>
|
|
@@ -223,6 +246,25 @@ document.getElementById("save-app").addEventListener("click", function() {
|
|
|
223
246
|
webhookUrl: document.getElementById("webhook-url").value
|
|
224
247
|
});
|
|
225
248
|
});
|
|
249
|
+
document.getElementById("create-app").addEventListener("click", function() {
|
|
250
|
+
var card = this.closest("[data-async]");
|
|
251
|
+
var name = document.getElementById("app-name").value.trim();
|
|
252
|
+
if (!name) {
|
|
253
|
+
fail(card, "Enter a name for the GitHub App.", function () {});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
var form = document.createElement("form");
|
|
257
|
+
form.method = "POST";
|
|
258
|
+
form.action = "/github/app-manifest";
|
|
259
|
+
form.style.display = "none";
|
|
260
|
+
var field = document.createElement("input");
|
|
261
|
+
field.type = "hidden";
|
|
262
|
+
field.name = "name";
|
|
263
|
+
field.value = name;
|
|
264
|
+
form.appendChild(field);
|
|
265
|
+
document.body.appendChild(form);
|
|
266
|
+
form.submit();
|
|
267
|
+
});
|
|
226
268
|
document.getElementById("save-access").addEventListener("click", function() {
|
|
227
269
|
save(this, {
|
|
228
270
|
passwordAuthDisabled: !document.getElementById("password-auth").checked,
|
|
@@ -320,6 +362,42 @@ async function loadRemoteTokens() {
|
|
|
320
362
|
}
|
|
321
363
|
}
|
|
322
364
|
function cardFor(btn) { return btn.closest("[data-async]"); }
|
|
365
|
+
function showSecret(token) {
|
|
366
|
+
var box = document.getElementById("remote-token-secret");
|
|
367
|
+
box.hidden = false;
|
|
368
|
+
box.replaceChildren();
|
|
369
|
+
var title = document.createElement("b");
|
|
370
|
+
title.textContent = "Copy this token now. It is not shown again.";
|
|
371
|
+
var row = document.createElement("div");
|
|
372
|
+
row.className = "secret-row";
|
|
373
|
+
var code = document.createElement("code");
|
|
374
|
+
code.textContent = token;
|
|
375
|
+
var copy = document.createElement("button");
|
|
376
|
+
copy.type = "button";
|
|
377
|
+
copy.className = "btn sm";
|
|
378
|
+
copy.textContent = "Copy";
|
|
379
|
+
copy.addEventListener("click", async function() {
|
|
380
|
+
try {
|
|
381
|
+
await navigator.clipboard.writeText(token);
|
|
382
|
+
copy.textContent = "Copied";
|
|
383
|
+
} catch (err) {
|
|
384
|
+
toast("Copy failed. Select the token and copy it by hand.");
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
var dismiss = document.createElement("button");
|
|
388
|
+
dismiss.type = "button";
|
|
389
|
+
dismiss.className = "btn sm";
|
|
390
|
+
dismiss.textContent = "I have saved it";
|
|
391
|
+
dismiss.addEventListener("click", function() {
|
|
392
|
+
box.hidden = true;
|
|
393
|
+
box.replaceChildren();
|
|
394
|
+
});
|
|
395
|
+
row.appendChild(code);
|
|
396
|
+
row.appendChild(copy);
|
|
397
|
+
row.appendChild(dismiss);
|
|
398
|
+
box.appendChild(title);
|
|
399
|
+
box.appendChild(row);
|
|
400
|
+
}
|
|
323
401
|
loadRemoteTokens();
|
|
324
402
|
document.getElementById("create-remote-token").addEventListener("click", function() {
|
|
325
403
|
var name = document.getElementById("remote-token-name").value.trim();
|
|
@@ -329,7 +407,7 @@ document.getElementById("create-remote-token").addEventListener("click", functio
|
|
|
329
407
|
}
|
|
330
408
|
run(this, this.closest("[data-async]"), async function() {
|
|
331
409
|
var created = await api("POST", "/api/remote-tokens", { name: name });
|
|
332
|
-
|
|
410
|
+
showSecret(created.token);
|
|
333
411
|
document.getElementById("remote-token-name").value = "";
|
|
334
412
|
await loadRemoteTokens();
|
|
335
413
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Generated from src/server/pages/styles.css by scripts/embed_assets.mjs — do not edit. */
|
|
2
|
-
export declare const styles = ":root {\n --bg: #f6f6f7;\n --card: #fff;\n --border: #e2e2e4;\n --border2: #ededef;\n --text: #1f2124;\n --muted: #6a6f75;\n --dim: #8b9096;\n --accent: #80c580;\n --accent-d: #6cae6c;\n --link: #0b5cd5;\n --green: #1a9d5a;\n --amber: #c98a00;\n --red: #cf3c3c;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n* {\n box-sizing: border-box;\n}\nbody {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font:\n 14px/1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n}\na {\n color: var(--link);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\ncode,\n.mono {\n font-family: var(--mono);\n font-size: 0.9em;\n}\n\n/* top bar */\n.top {\n background: #fff;\n border-bottom: 1px solid var(--border);\n}\n.top .in {\n max-width: 1100px;\n margin: 0 auto;\n padding: 0 24px;\n height: 56px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.logo {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n font-size: 15px;\n color: var(--text);\n}\n.logo:hover {\n text-decoration: none;\n}\n.logo img {\n height: 32px;\n width: 32px;\n display: block;\n border-radius: 5px;\n image-rendering: pixelated;\n object-fit: cover;\n}\n.brand {\n width: 180px;\n height: auto;\n display: block;\n margin: 0 0 14px;\n border-radius: 8px;\n image-rendering: pixelated;\n}\n.top .right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n font-size: 13px;\n color: var(--muted);\n}\n\n/* layout */\n.wrap {\n max-width: 1100px;\n margin: 0 auto;\n padding: 28px 24px 72px;\n}\n.wrap.side {\n display: grid;\n grid-template-columns: 196px 1fr;\n gap: 32px;\n}\naside nav {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: sticky;\n top: 24px;\n}\naside nav a {\n padding: 7px 12px;\n border-radius: 4px;\n color: var(--text);\n font-size: 14px;\n}\naside nav a:hover {\n background: #ededef;\n text-decoration: none;\n}\naside nav a.on {\n background: #fff;\n box-shadow: inset 2px 0 0 var(--accent);\n border: 1px solid var(--border);\n font-weight: 500;\n}\naside .lbl {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--dim);\n padding: 0 12px;\n margin: 0 0 8px;\n}\n\nh1 {\n font-size: 22px;\n font-weight: 600;\n margin: 0;\n}\nh2 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n}\np.lead {\n color: var(--muted);\n margin: 6px 0 0;\n font-size: 14px;\n}\n.pagehead {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 22px;\n}\n.pagehead .actions {\n margin-left: auto;\n display: flex;\n gap: 8px;\n}\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: 6px;\n margin-bottom: 18px;\n}\n.card .hd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.card .bd {\n padding: 18px;\n}\n.card .bd.flush {\n padding: 0;\n}\n.card .ft {\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n background: #fbfbfc;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n color: var(--muted);\n border-radius: 0 0 6px 6px;\n}\n\n/* table */\ntable {\n width: 100%;\n border-collapse: collapse;\n}\nth {\n text-align: left;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n padding: 10px 18px;\n border-bottom: 1px solid var(--border);\n background: #fbfbfc;\n}\ntd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border2);\n font-size: 14px;\n}\ntr:last-child td {\n border-bottom: none;\n}\ntbody tr:hover {\n background: #fafafb;\n}\n.num {\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* status text, not badges */\n.st {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n.st::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dim);\n}\n.st.ok::before {\n background: var(--green);\n}\n.st.warn::before {\n background: var(--amber);\n}\n.st.err::before {\n background: var(--red);\n}\n.st.run::before {\n background: var(--link);\n}\n\n/* buttons */\nbutton,\n.btn {\n font: inherit;\n font-size: 14px;\n padding: 6px 14px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: #fff;\n color: var(--text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n}\nbutton:hover,\n.btn:hover {\n background: #f4f4f5;\n text-decoration: none;\n}\n.btn.primary,\nbutton.primary {\n background: var(--accent);\n border-color: var(--accent);\n color: #fff;\n font-weight: 500;\n}\n.btn.primary:hover,\nbutton.primary:hover {\n background: var(--accent-d);\n border-color: var(--accent-d);\n}\n.btn.sm {\n padding: 4px 10px;\n font-size: 13px;\n}\n.btn.link {\n border: none;\n background: none;\n color: var(--link);\n padding: 0;\n}\n.btn.link:hover {\n background: none;\n text-decoration: underline;\n}\nbutton[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* toggle */\n.tg {\n position: relative;\n width: 38px;\n height: 22px;\n border-radius: 22px;\n background: #cfd2d6;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n.tg::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n transition: left 0.12s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n.tg.on {\n background: var(--accent);\n}\n.tg.on::after {\n left: 18px;\n}\n.tg:hover {\n filter: brightness(0.97);\n}\n\n/* forms */\nlabel {\n display: block;\n font-size: 13px;\n font-weight: 500;\n margin-bottom: 6px;\n}\ninput,\nselect,\ntextarea {\n width: 100%;\n background: #fff;\n border: 1px solid #cfd2d6;\n border-radius: 4px;\n color: var(--text);\n padding: 8px 10px;\n font: inherit;\n font-size: 14px;\n}\ninput:focus,\nselect:focus,\ntextarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 2px rgba(128, 197, 128, 0.35);\n}\ntextarea {\n resize: vertical;\n line-height: 1.35;\n}\n.pem {\n min-height: 150px;\n}\n.field {\n margin-bottom: 18px;\n max-width: 420px;\n}\n.field.wide {\n max-width: none;\n}\n.hint {\n font-size: 13px;\n color: var(--muted);\n margin-top: 6px;\n font-weight: 400;\n}\n.two {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0 24px;\n}\n\n/* bits */\n.muted {\n color: var(--muted);\n}\n.dim {\n color: var(--dim);\n}\n.big {\n font-size: 26px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n.kfig {\n display: flex;\n gap: 44px;\n}\n.kfig .k {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 2px;\n}\n.notice {\n background: #f3faf3;\n border: 1px solid #cfe6cf;\n border-radius: 6px;\n padding: 14px 18px;\n display: flex;\n align-items: center;\n gap: 14px;\n margin-bottom: 18px;\n}\n.notice.info {\n background: #f2f7ff;\n border-color: #cfe0fb;\n}\n.notice.bad {\n background: #fdf3f3;\n border-color: #f3d0d0;\n}\n.notice .txt {\n flex: 1;\n}\n.notice b {\n font-weight: 600;\n}\n.notice .dismiss-worth {\n margin-left: auto;\n border: 0;\n background: transparent;\n color: var(--muted);\n padding: 0 4px;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n}\n.notice .dismiss-worth:hover {\n background: transparent;\n color: var(--text);\n}\n.markdown > :first-child {\n margin-top: 0;\n}\n.markdown > :last-child {\n margin-bottom: 0;\n}\n.markdown p,\n.markdown h1,\n.markdown h2,\n.markdown h3,\n.markdown ul,\n.markdown ol,\n.markdown pre {\n margin: 0 0 12px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3 {\n font-size: 15px;\n}\n.markdown ul,\n.markdown ol {\n padding-left: 22px;\n}\n.markdown pre {\n overflow-x: auto;\n padding: 10px 12px;\n border-radius: 4px;\n background: #f1f2f3;\n font-family: var(--mono);\n font-size: 13px;\n white-space: pre;\n}\n.markdown code {\n padding: 1px 4px;\n border-radius: 3px;\n background: #f1f2f3;\n}\n.markdown pre code {\n padding: 0;\n background: transparent;\n}\n.log {\n background: #1f2124;\n color: #e6e6e6;\n border-radius: 6px;\n padding: 14px 16px;\n font-family: var(--mono);\n font-size: 12.5px;\n line-height: 1.7;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n}\n.log .t {\n color: #8b9096;\n}\n.crumbs {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 14px;\n}\n.pager {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n font-size: 13px;\n color: var(--muted);\n}\n.pager .sp {\n margin-left: auto;\n}\n.empty {\n padding: 56px 24px;\n text-align: center;\n color: var(--muted);\n}\n.empty h2 {\n margin-bottom: 6px;\n}\n.steps {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.steps li {\n display: flex;\n gap: 14px;\n padding: 16px 0;\n border-bottom: 1px solid var(--border2);\n}\n.steps li:last-child {\n border-bottom: none;\n}\n.steps .n {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #ededef;\n color: var(--muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n flex: none;\n}\n.steps li.done .n {\n background: var(--green);\n color: #fff;\n}\n.steps li.now .n {\n background: var(--accent);\n color: #fff;\n}\n.steps .body {\n flex: 1;\n}\n.steps .body b {\n display: block;\n font-weight: 600;\n margin-bottom: 2px;\n}\n\nbutton,\n.btn,\n.tg,\n.tg::after,\ntbody tr {\n transition:\n background 0.12s ease,\n border-color 0.12s ease,\n filter 0.12s ease,\n left 0.12s ease,\n color 0.12s ease;\n}\n:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n\n#toasts {\n position: fixed;\n right: 20px;\n bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 20;\n max-width: 360px;\n}\n.toast {\n background: #1f2124;\n color: #fff;\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 13px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n}\n.fail {\n margin: 12px 18px;\n padding: 12px 14px;\n background: #fdf3f3;\n border: 1px solid #f3d0d0;\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 13px;\n}\n.fail p {\n margin: 0;\n flex: 1;\n}\n\n.sk-slot {\n display: none;\n padding: 18px;\n}\n.sk,\n.sk-row {\n height: 12px;\n background: #ededef;\n border-radius: 4px;\n margin-bottom: 10px;\n}\n.sk-row {\n height: 36px;\n}\n[data-async].busy > :not(.sk-slot):not([data-fail]) {\n visibility: hidden;\n}\n[data-async].busy > .sk-slot {\n display: block !important;\n visibility: visible;\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n.bars {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 56px;\n}\n.bars i {\n flex: 1 1 0;\n min-width: 2px;\n align-self: stretch;\n background-image: linear-gradient(\n to top,\n var(--bar, var(--accent)) var(--h),\n transparent var(--h)\n );\n}\n.bars i:hover {\n --bar: var(--accent-d);\n background-color: var(--border2);\n}\n.trend {\n font-size: 12px;\n margin-top: 3px;\n}\n.trend.up {\n color: var(--green);\n}\n.trend.down {\n color: var(--red);\n}\n.kfig.wrap4 {\n flex-wrap: wrap;\n gap: 28px 44px;\n}\n";
|
|
2
|
+
export declare const styles = ":root {\n --bg: #f6f6f7;\n --card: #fff;\n --border: #e2e2e4;\n --border2: #ededef;\n --text: #1f2124;\n --muted: #6a6f75;\n --dim: #8b9096;\n --accent: #80c580;\n --accent-d: #6cae6c;\n --link: #0b5cd5;\n --green: #1a9d5a;\n --amber: #c98a00;\n --red: #cf3c3c;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n* {\n box-sizing: border-box;\n}\nbody {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font:\n 14px/1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n}\na {\n color: var(--link);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\ncode,\n.mono {\n font-family: var(--mono);\n font-size: 0.9em;\n}\n\n/* top bar */\n.top {\n background: #fff;\n border-bottom: 1px solid var(--border);\n}\n.top .in {\n max-width: 1100px;\n margin: 0 auto;\n padding: 0 24px;\n height: 56px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.logo {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n font-size: 15px;\n color: var(--text);\n}\n.logo:hover {\n text-decoration: none;\n}\n.logo img {\n height: 32px;\n width: 32px;\n display: block;\n border-radius: 5px;\n image-rendering: pixelated;\n object-fit: cover;\n}\n.brand {\n width: 180px;\n height: auto;\n display: block;\n margin: 0 0 14px;\n border-radius: 8px;\n image-rendering: pixelated;\n}\n.top .right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n font-size: 13px;\n color: var(--muted);\n}\n\n/* layout */\n.wrap {\n max-width: 1100px;\n margin: 0 auto;\n padding: 28px 24px 72px;\n}\n.wrap.side {\n display: grid;\n grid-template-columns: 196px 1fr;\n gap: 32px;\n}\naside nav {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: sticky;\n top: 24px;\n}\naside nav a {\n padding: 7px 12px;\n border-radius: 4px;\n color: var(--text);\n font-size: 14px;\n}\naside nav a:hover {\n background: #ededef;\n text-decoration: none;\n}\naside nav a.on {\n background: #fff;\n box-shadow: inset 2px 0 0 var(--accent);\n border: 1px solid var(--border);\n font-weight: 500;\n}\naside .lbl {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--dim);\n padding: 0 12px;\n margin: 0 0 8px;\n}\n\nh1 {\n font-size: 22px;\n font-weight: 600;\n margin: 0;\n}\nh2 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n}\np.lead {\n color: var(--muted);\n margin: 6px 0 0;\n font-size: 14px;\n}\n.pagehead {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 22px;\n}\n.pagehead .actions {\n margin-left: auto;\n display: flex;\n gap: 8px;\n}\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: 6px;\n margin-bottom: 18px;\n}\n.card .hd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.card .bd {\n padding: 18px;\n}\n.card .bd.flush {\n padding: 0;\n}\n.card .ft {\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n background: #fbfbfc;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n color: var(--muted);\n border-radius: 0 0 6px 6px;\n}\n\n/* table */\ntable {\n width: 100%;\n border-collapse: collapse;\n}\nth {\n text-align: left;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n padding: 10px 18px;\n border-bottom: 1px solid var(--border);\n background: #fbfbfc;\n}\ntd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border2);\n font-size: 14px;\n}\ntr:last-child td {\n border-bottom: none;\n}\ntbody tr:hover {\n background: #fafafb;\n}\n.num {\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* status text, not badges */\n.st {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n.st::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dim);\n}\n.st.ok::before {\n background: var(--green);\n}\n.st.warn::before {\n background: var(--amber);\n}\n.st.err::before {\n background: var(--red);\n}\n.st.run::before {\n background: var(--link);\n}\n\n/* buttons */\nbutton,\n.btn {\n font: inherit;\n font-size: 14px;\n padding: 6px 14px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: #fff;\n color: var(--text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n}\nbutton:hover,\n.btn:hover {\n background: #f4f4f5;\n text-decoration: none;\n}\n.btn.primary,\nbutton.primary {\n background: var(--accent);\n border-color: var(--accent);\n color: #fff;\n font-weight: 500;\n}\n.btn.primary:hover,\nbutton.primary:hover {\n background: var(--accent-d);\n border-color: var(--accent-d);\n}\n.btn.sm {\n padding: 4px 10px;\n font-size: 13px;\n}\n.btn.link {\n border: none;\n background: none;\n color: var(--link);\n padding: 0;\n}\n.btn.link:hover {\n background: none;\n text-decoration: underline;\n}\nbutton[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* toggle */\n.tg {\n position: relative;\n width: 38px;\n height: 22px;\n border-radius: 22px;\n background: #cfd2d6;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n.tg::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n transition: left 0.12s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n.tg.on {\n background: var(--accent);\n}\n.tg.on::after {\n left: 18px;\n}\n.tg:hover {\n filter: brightness(0.97);\n}\n\n/* forms */\nlabel {\n display: block;\n font-size: 13px;\n font-weight: 500;\n margin-bottom: 6px;\n}\ninput,\nselect,\ntextarea {\n width: 100%;\n background: #fff;\n border: 1px solid #cfd2d6;\n border-radius: 4px;\n color: var(--text);\n padding: 8px 10px;\n font: inherit;\n font-size: 14px;\n}\ninput:focus,\nselect:focus,\ntextarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 2px rgba(128, 197, 128, 0.35);\n}\ntextarea {\n resize: vertical;\n line-height: 1.35;\n}\n.pem {\n min-height: 150px;\n}\n.field {\n margin-bottom: 18px;\n max-width: 420px;\n}\n.field.wide {\n max-width: none;\n}\n.hint {\n font-size: 13px;\n color: var(--muted);\n margin-top: 6px;\n font-weight: 400;\n}\n.two {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0 24px;\n}\n\n/* bits */\n.muted {\n color: var(--muted);\n}\n.dim {\n color: var(--dim);\n}\n.big {\n font-size: 26px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n.kfig {\n display: flex;\n gap: 44px;\n}\n.kfig .k {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 2px;\n}\n.notice {\n background: #f3faf3;\n border: 1px solid #cfe6cf;\n border-radius: 6px;\n padding: 14px 18px;\n display: flex;\n align-items: center;\n gap: 14px;\n margin-bottom: 18px;\n}\n.notice.info {\n background: #f2f7ff;\n border-color: #cfe0fb;\n}\n.notice.bad {\n background: #fdf3f3;\n border-color: #f3d0d0;\n}\n.notice .txt {\n flex: 1;\n}\n.notice b {\n font-weight: 600;\n}\n.notice .dismiss-worth {\n margin-left: auto;\n border: 0;\n background: transparent;\n color: var(--muted);\n padding: 0 4px;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n}\n.notice .dismiss-worth:hover {\n background: transparent;\n color: var(--text);\n}\n.markdown > :first-child {\n margin-top: 0;\n}\n.markdown > :last-child {\n margin-bottom: 0;\n}\n.markdown p,\n.markdown h1,\n.markdown h2,\n.markdown h3,\n.markdown ul,\n.markdown ol,\n.markdown pre {\n margin: 0 0 12px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3 {\n font-size: 15px;\n}\n.markdown ul,\n.markdown ol {\n padding-left: 22px;\n}\n.markdown pre {\n overflow-x: auto;\n padding: 10px 12px;\n border-radius: 4px;\n background: #f1f2f3;\n font-family: var(--mono);\n font-size: 13px;\n white-space: pre;\n}\n.markdown code {\n padding: 1px 4px;\n border-radius: 3px;\n background: #f1f2f3;\n}\n.markdown pre code {\n padding: 0;\n background: transparent;\n}\n.log {\n background: #1f2124;\n color: #e6e6e6;\n border-radius: 6px;\n padding: 14px 16px;\n font-family: var(--mono);\n font-size: 12.5px;\n line-height: 1.7;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n}\n.log .t {\n color: #8b9096;\n}\n.crumbs {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 14px;\n}\n.pager {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n font-size: 13px;\n color: var(--muted);\n}\n.pager .sp {\n margin-left: auto;\n}\n.empty {\n padding: 56px 24px;\n text-align: center;\n color: var(--muted);\n}\n.empty h2 {\n margin-bottom: 6px;\n}\n.steps {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.steps li {\n display: flex;\n gap: 14px;\n padding: 16px 0;\n border-bottom: 1px solid var(--border2);\n}\n.steps li:last-child {\n border-bottom: none;\n}\n.steps .n {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #ededef;\n color: var(--muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n flex: none;\n}\n.steps li.done .n {\n background: var(--green);\n color: #fff;\n}\n.steps li.now .n {\n background: var(--accent);\n color: #fff;\n}\n.steps .body {\n flex: 1;\n}\n.steps .body b {\n display: block;\n font-weight: 600;\n margin-bottom: 2px;\n}\n\nbutton,\n.btn,\n.tg,\n.tg::after,\ntbody tr {\n transition:\n background 0.12s ease,\n border-color 0.12s ease,\n filter 0.12s ease,\n left 0.12s ease,\n color 0.12s ease;\n}\n:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n\n#toasts {\n position: fixed;\n right: 20px;\n bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 20;\n max-width: 360px;\n}\n.toast {\n background: #1f2124;\n color: #fff;\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 13px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n}\n.fail {\n margin: 12px 18px;\n padding: 12px 14px;\n background: #fdf3f3;\n border: 1px solid #f3d0d0;\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 13px;\n}\n.fail p {\n margin: 0;\n flex: 1;\n}\n\n.secret {\n margin: 0 0 16px;\n padding: 12px 14px;\n background: #fffbea;\n border: 1px solid #ecd9a0;\n border-radius: 6px;\n font-size: 13px;\n}\n.secret b {\n display: block;\n margin-bottom: 8px;\n}\n.secret-row {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n.secret-row code {\n flex: 1;\n min-width: 220px;\n word-break: break-all;\n}\n\n.sk-slot {\n display: none;\n padding: 18px;\n}\n.sk,\n.sk-row {\n height: 12px;\n background: #ededef;\n border-radius: 4px;\n margin-bottom: 10px;\n}\n.sk-row {\n height: 36px;\n}\n[data-async].busy > :not(.sk-slot):not([data-fail]) {\n visibility: hidden;\n}\n[data-async].busy > .sk-slot {\n display: block !important;\n visibility: visible;\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n.bars {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 56px;\n}\n.bars i {\n flex: 1 1 0;\n min-width: 2px;\n align-self: stretch;\n background-image: linear-gradient(\n to top,\n var(--bar, var(--accent)) var(--h),\n transparent var(--h)\n );\n}\n.bars i:hover {\n --bar: var(--accent-d);\n background-color: var(--border2);\n}\n.trend {\n font-size: 12px;\n margin-top: 3px;\n}\n.trend.up {\n color: var(--green);\n}\n.trend.down {\n color: var(--red);\n}\n.kfig.wrap4 {\n flex-wrap: wrap;\n gap: 28px 44px;\n}\n";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Generated from src/server/pages/styles.css by scripts/embed_assets.mjs — do not edit. */
|
|
2
|
-
export const styles = ":root {\n --bg: #f6f6f7;\n --card: #fff;\n --border: #e2e2e4;\n --border2: #ededef;\n --text: #1f2124;\n --muted: #6a6f75;\n --dim: #8b9096;\n --accent: #80c580;\n --accent-d: #6cae6c;\n --link: #0b5cd5;\n --green: #1a9d5a;\n --amber: #c98a00;\n --red: #cf3c3c;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n* {\n box-sizing: border-box;\n}\nbody {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font:\n 14px/1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n}\na {\n color: var(--link);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\ncode,\n.mono {\n font-family: var(--mono);\n font-size: 0.9em;\n}\n\n/* top bar */\n.top {\n background: #fff;\n border-bottom: 1px solid var(--border);\n}\n.top .in {\n max-width: 1100px;\n margin: 0 auto;\n padding: 0 24px;\n height: 56px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.logo {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n font-size: 15px;\n color: var(--text);\n}\n.logo:hover {\n text-decoration: none;\n}\n.logo img {\n height: 32px;\n width: 32px;\n display: block;\n border-radius: 5px;\n image-rendering: pixelated;\n object-fit: cover;\n}\n.brand {\n width: 180px;\n height: auto;\n display: block;\n margin: 0 0 14px;\n border-radius: 8px;\n image-rendering: pixelated;\n}\n.top .right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n font-size: 13px;\n color: var(--muted);\n}\n\n/* layout */\n.wrap {\n max-width: 1100px;\n margin: 0 auto;\n padding: 28px 24px 72px;\n}\n.wrap.side {\n display: grid;\n grid-template-columns: 196px 1fr;\n gap: 32px;\n}\naside nav {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: sticky;\n top: 24px;\n}\naside nav a {\n padding: 7px 12px;\n border-radius: 4px;\n color: var(--text);\n font-size: 14px;\n}\naside nav a:hover {\n background: #ededef;\n text-decoration: none;\n}\naside nav a.on {\n background: #fff;\n box-shadow: inset 2px 0 0 var(--accent);\n border: 1px solid var(--border);\n font-weight: 500;\n}\naside .lbl {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--dim);\n padding: 0 12px;\n margin: 0 0 8px;\n}\n\nh1 {\n font-size: 22px;\n font-weight: 600;\n margin: 0;\n}\nh2 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n}\np.lead {\n color: var(--muted);\n margin: 6px 0 0;\n font-size: 14px;\n}\n.pagehead {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 22px;\n}\n.pagehead .actions {\n margin-left: auto;\n display: flex;\n gap: 8px;\n}\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: 6px;\n margin-bottom: 18px;\n}\n.card .hd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.card .bd {\n padding: 18px;\n}\n.card .bd.flush {\n padding: 0;\n}\n.card .ft {\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n background: #fbfbfc;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n color: var(--muted);\n border-radius: 0 0 6px 6px;\n}\n\n/* table */\ntable {\n width: 100%;\n border-collapse: collapse;\n}\nth {\n text-align: left;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n padding: 10px 18px;\n border-bottom: 1px solid var(--border);\n background: #fbfbfc;\n}\ntd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border2);\n font-size: 14px;\n}\ntr:last-child td {\n border-bottom: none;\n}\ntbody tr:hover {\n background: #fafafb;\n}\n.num {\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* status text, not badges */\n.st {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n.st::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dim);\n}\n.st.ok::before {\n background: var(--green);\n}\n.st.warn::before {\n background: var(--amber);\n}\n.st.err::before {\n background: var(--red);\n}\n.st.run::before {\n background: var(--link);\n}\n\n/* buttons */\nbutton,\n.btn {\n font: inherit;\n font-size: 14px;\n padding: 6px 14px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: #fff;\n color: var(--text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n}\nbutton:hover,\n.btn:hover {\n background: #f4f4f5;\n text-decoration: none;\n}\n.btn.primary,\nbutton.primary {\n background: var(--accent);\n border-color: var(--accent);\n color: #fff;\n font-weight: 500;\n}\n.btn.primary:hover,\nbutton.primary:hover {\n background: var(--accent-d);\n border-color: var(--accent-d);\n}\n.btn.sm {\n padding: 4px 10px;\n font-size: 13px;\n}\n.btn.link {\n border: none;\n background: none;\n color: var(--link);\n padding: 0;\n}\n.btn.link:hover {\n background: none;\n text-decoration: underline;\n}\nbutton[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* toggle */\n.tg {\n position: relative;\n width: 38px;\n height: 22px;\n border-radius: 22px;\n background: #cfd2d6;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n.tg::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n transition: left 0.12s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n.tg.on {\n background: var(--accent);\n}\n.tg.on::after {\n left: 18px;\n}\n.tg:hover {\n filter: brightness(0.97);\n}\n\n/* forms */\nlabel {\n display: block;\n font-size: 13px;\n font-weight: 500;\n margin-bottom: 6px;\n}\ninput,\nselect,\ntextarea {\n width: 100%;\n background: #fff;\n border: 1px solid #cfd2d6;\n border-radius: 4px;\n color: var(--text);\n padding: 8px 10px;\n font: inherit;\n font-size: 14px;\n}\ninput:focus,\nselect:focus,\ntextarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 2px rgba(128, 197, 128, 0.35);\n}\ntextarea {\n resize: vertical;\n line-height: 1.35;\n}\n.pem {\n min-height: 150px;\n}\n.field {\n margin-bottom: 18px;\n max-width: 420px;\n}\n.field.wide {\n max-width: none;\n}\n.hint {\n font-size: 13px;\n color: var(--muted);\n margin-top: 6px;\n font-weight: 400;\n}\n.two {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0 24px;\n}\n\n/* bits */\n.muted {\n color: var(--muted);\n}\n.dim {\n color: var(--dim);\n}\n.big {\n font-size: 26px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n.kfig {\n display: flex;\n gap: 44px;\n}\n.kfig .k {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 2px;\n}\n.notice {\n background: #f3faf3;\n border: 1px solid #cfe6cf;\n border-radius: 6px;\n padding: 14px 18px;\n display: flex;\n align-items: center;\n gap: 14px;\n margin-bottom: 18px;\n}\n.notice.info {\n background: #f2f7ff;\n border-color: #cfe0fb;\n}\n.notice.bad {\n background: #fdf3f3;\n border-color: #f3d0d0;\n}\n.notice .txt {\n flex: 1;\n}\n.notice b {\n font-weight: 600;\n}\n.notice .dismiss-worth {\n margin-left: auto;\n border: 0;\n background: transparent;\n color: var(--muted);\n padding: 0 4px;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n}\n.notice .dismiss-worth:hover {\n background: transparent;\n color: var(--text);\n}\n.markdown > :first-child {\n margin-top: 0;\n}\n.markdown > :last-child {\n margin-bottom: 0;\n}\n.markdown p,\n.markdown h1,\n.markdown h2,\n.markdown h3,\n.markdown ul,\n.markdown ol,\n.markdown pre {\n margin: 0 0 12px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3 {\n font-size: 15px;\n}\n.markdown ul,\n.markdown ol {\n padding-left: 22px;\n}\n.markdown pre {\n overflow-x: auto;\n padding: 10px 12px;\n border-radius: 4px;\n background: #f1f2f3;\n font-family: var(--mono);\n font-size: 13px;\n white-space: pre;\n}\n.markdown code {\n padding: 1px 4px;\n border-radius: 3px;\n background: #f1f2f3;\n}\n.markdown pre code {\n padding: 0;\n background: transparent;\n}\n.log {\n background: #1f2124;\n color: #e6e6e6;\n border-radius: 6px;\n padding: 14px 16px;\n font-family: var(--mono);\n font-size: 12.5px;\n line-height: 1.7;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n}\n.log .t {\n color: #8b9096;\n}\n.crumbs {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 14px;\n}\n.pager {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n font-size: 13px;\n color: var(--muted);\n}\n.pager .sp {\n margin-left: auto;\n}\n.empty {\n padding: 56px 24px;\n text-align: center;\n color: var(--muted);\n}\n.empty h2 {\n margin-bottom: 6px;\n}\n.steps {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.steps li {\n display: flex;\n gap: 14px;\n padding: 16px 0;\n border-bottom: 1px solid var(--border2);\n}\n.steps li:last-child {\n border-bottom: none;\n}\n.steps .n {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #ededef;\n color: var(--muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n flex: none;\n}\n.steps li.done .n {\n background: var(--green);\n color: #fff;\n}\n.steps li.now .n {\n background: var(--accent);\n color: #fff;\n}\n.steps .body {\n flex: 1;\n}\n.steps .body b {\n display: block;\n font-weight: 600;\n margin-bottom: 2px;\n}\n\nbutton,\n.btn,\n.tg,\n.tg::after,\ntbody tr {\n transition:\n background 0.12s ease,\n border-color 0.12s ease,\n filter 0.12s ease,\n left 0.12s ease,\n color 0.12s ease;\n}\n:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n\n#toasts {\n position: fixed;\n right: 20px;\n bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 20;\n max-width: 360px;\n}\n.toast {\n background: #1f2124;\n color: #fff;\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 13px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n}\n.fail {\n margin: 12px 18px;\n padding: 12px 14px;\n background: #fdf3f3;\n border: 1px solid #f3d0d0;\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 13px;\n}\n.fail p {\n margin: 0;\n flex: 1;\n}\n\n.sk-slot {\n display: none;\n padding: 18px;\n}\n.sk,\n.sk-row {\n height: 12px;\n background: #ededef;\n border-radius: 4px;\n margin-bottom: 10px;\n}\n.sk-row {\n height: 36px;\n}\n[data-async].busy > :not(.sk-slot):not([data-fail]) {\n visibility: hidden;\n}\n[data-async].busy > .sk-slot {\n display: block !important;\n visibility: visible;\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n.bars {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 56px;\n}\n.bars i {\n flex: 1 1 0;\n min-width: 2px;\n align-self: stretch;\n background-image: linear-gradient(\n to top,\n var(--bar, var(--accent)) var(--h),\n transparent var(--h)\n );\n}\n.bars i:hover {\n --bar: var(--accent-d);\n background-color: var(--border2);\n}\n.trend {\n font-size: 12px;\n margin-top: 3px;\n}\n.trend.up {\n color: var(--green);\n}\n.trend.down {\n color: var(--red);\n}\n.kfig.wrap4 {\n flex-wrap: wrap;\n gap: 28px 44px;\n}\n";
|
|
2
|
+
export const styles = ":root {\n --bg: #f6f6f7;\n --card: #fff;\n --border: #e2e2e4;\n --border2: #ededef;\n --text: #1f2124;\n --muted: #6a6f75;\n --dim: #8b9096;\n --accent: #80c580;\n --accent-d: #6cae6c;\n --link: #0b5cd5;\n --green: #1a9d5a;\n --amber: #c98a00;\n --red: #cf3c3c;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n* {\n box-sizing: border-box;\n}\nbody {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font:\n 14px/1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n}\na {\n color: var(--link);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\ncode,\n.mono {\n font-family: var(--mono);\n font-size: 0.9em;\n}\n\n/* top bar */\n.top {\n background: #fff;\n border-bottom: 1px solid var(--border);\n}\n.top .in {\n max-width: 1100px;\n margin: 0 auto;\n padding: 0 24px;\n height: 56px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.logo {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n font-size: 15px;\n color: var(--text);\n}\n.logo:hover {\n text-decoration: none;\n}\n.logo img {\n height: 32px;\n width: 32px;\n display: block;\n border-radius: 5px;\n image-rendering: pixelated;\n object-fit: cover;\n}\n.brand {\n width: 180px;\n height: auto;\n display: block;\n margin: 0 0 14px;\n border-radius: 8px;\n image-rendering: pixelated;\n}\n.top .right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n font-size: 13px;\n color: var(--muted);\n}\n\n/* layout */\n.wrap {\n max-width: 1100px;\n margin: 0 auto;\n padding: 28px 24px 72px;\n}\n.wrap.side {\n display: grid;\n grid-template-columns: 196px 1fr;\n gap: 32px;\n}\naside nav {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: sticky;\n top: 24px;\n}\naside nav a {\n padding: 7px 12px;\n border-radius: 4px;\n color: var(--text);\n font-size: 14px;\n}\naside nav a:hover {\n background: #ededef;\n text-decoration: none;\n}\naside nav a.on {\n background: #fff;\n box-shadow: inset 2px 0 0 var(--accent);\n border: 1px solid var(--border);\n font-weight: 500;\n}\naside .lbl {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--dim);\n padding: 0 12px;\n margin: 0 0 8px;\n}\n\nh1 {\n font-size: 22px;\n font-weight: 600;\n margin: 0;\n}\nh2 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n}\np.lead {\n color: var(--muted);\n margin: 6px 0 0;\n font-size: 14px;\n}\n.pagehead {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 22px;\n}\n.pagehead .actions {\n margin-left: auto;\n display: flex;\n gap: 8px;\n}\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: 6px;\n margin-bottom: 18px;\n}\n.card .hd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.card .bd {\n padding: 18px;\n}\n.card .bd.flush {\n padding: 0;\n}\n.card .ft {\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n background: #fbfbfc;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n color: var(--muted);\n border-radius: 0 0 6px 6px;\n}\n\n/* table */\ntable {\n width: 100%;\n border-collapse: collapse;\n}\nth {\n text-align: left;\n font-size: 12px;\n font-weight: 600;\n color: var(--muted);\n padding: 10px 18px;\n border-bottom: 1px solid var(--border);\n background: #fbfbfc;\n}\ntd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--border2);\n font-size: 14px;\n}\ntr:last-child td {\n border-bottom: none;\n}\ntbody tr:hover {\n background: #fafafb;\n}\n.num {\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* status text, not badges */\n.st {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n.st::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dim);\n}\n.st.ok::before {\n background: var(--green);\n}\n.st.warn::before {\n background: var(--amber);\n}\n.st.err::before {\n background: var(--red);\n}\n.st.run::before {\n background: var(--link);\n}\n\n/* buttons */\nbutton,\n.btn {\n font: inherit;\n font-size: 14px;\n padding: 6px 14px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: #fff;\n color: var(--text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n}\nbutton:hover,\n.btn:hover {\n background: #f4f4f5;\n text-decoration: none;\n}\n.btn.primary,\nbutton.primary {\n background: var(--accent);\n border-color: var(--accent);\n color: #fff;\n font-weight: 500;\n}\n.btn.primary:hover,\nbutton.primary:hover {\n background: var(--accent-d);\n border-color: var(--accent-d);\n}\n.btn.sm {\n padding: 4px 10px;\n font-size: 13px;\n}\n.btn.link {\n border: none;\n background: none;\n color: var(--link);\n padding: 0;\n}\n.btn.link:hover {\n background: none;\n text-decoration: underline;\n}\nbutton[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* toggle */\n.tg {\n position: relative;\n width: 38px;\n height: 22px;\n border-radius: 22px;\n background: #cfd2d6;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n.tg::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n transition: left 0.12s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n.tg.on {\n background: var(--accent);\n}\n.tg.on::after {\n left: 18px;\n}\n.tg:hover {\n filter: brightness(0.97);\n}\n\n/* forms */\nlabel {\n display: block;\n font-size: 13px;\n font-weight: 500;\n margin-bottom: 6px;\n}\ninput,\nselect,\ntextarea {\n width: 100%;\n background: #fff;\n border: 1px solid #cfd2d6;\n border-radius: 4px;\n color: var(--text);\n padding: 8px 10px;\n font: inherit;\n font-size: 14px;\n}\ninput:focus,\nselect:focus,\ntextarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 2px rgba(128, 197, 128, 0.35);\n}\ntextarea {\n resize: vertical;\n line-height: 1.35;\n}\n.pem {\n min-height: 150px;\n}\n.field {\n margin-bottom: 18px;\n max-width: 420px;\n}\n.field.wide {\n max-width: none;\n}\n.hint {\n font-size: 13px;\n color: var(--muted);\n margin-top: 6px;\n font-weight: 400;\n}\n.two {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0 24px;\n}\n\n/* bits */\n.muted {\n color: var(--muted);\n}\n.dim {\n color: var(--dim);\n}\n.big {\n font-size: 26px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n.kfig {\n display: flex;\n gap: 44px;\n}\n.kfig .k {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 2px;\n}\n.notice {\n background: #f3faf3;\n border: 1px solid #cfe6cf;\n border-radius: 6px;\n padding: 14px 18px;\n display: flex;\n align-items: center;\n gap: 14px;\n margin-bottom: 18px;\n}\n.notice.info {\n background: #f2f7ff;\n border-color: #cfe0fb;\n}\n.notice.bad {\n background: #fdf3f3;\n border-color: #f3d0d0;\n}\n.notice .txt {\n flex: 1;\n}\n.notice b {\n font-weight: 600;\n}\n.notice .dismiss-worth {\n margin-left: auto;\n border: 0;\n background: transparent;\n color: var(--muted);\n padding: 0 4px;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n}\n.notice .dismiss-worth:hover {\n background: transparent;\n color: var(--text);\n}\n.markdown > :first-child {\n margin-top: 0;\n}\n.markdown > :last-child {\n margin-bottom: 0;\n}\n.markdown p,\n.markdown h1,\n.markdown h2,\n.markdown h3,\n.markdown ul,\n.markdown ol,\n.markdown pre {\n margin: 0 0 12px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3 {\n font-size: 15px;\n}\n.markdown ul,\n.markdown ol {\n padding-left: 22px;\n}\n.markdown pre {\n overflow-x: auto;\n padding: 10px 12px;\n border-radius: 4px;\n background: #f1f2f3;\n font-family: var(--mono);\n font-size: 13px;\n white-space: pre;\n}\n.markdown code {\n padding: 1px 4px;\n border-radius: 3px;\n background: #f1f2f3;\n}\n.markdown pre code {\n padding: 0;\n background: transparent;\n}\n.log {\n background: #1f2124;\n color: #e6e6e6;\n border-radius: 6px;\n padding: 14px 16px;\n font-family: var(--mono);\n font-size: 12.5px;\n line-height: 1.7;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n}\n.log .t {\n color: #8b9096;\n}\n.crumbs {\n font-size: 13px;\n color: var(--muted);\n margin-bottom: 14px;\n}\n.pager {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 18px;\n border-top: 1px solid var(--border);\n font-size: 13px;\n color: var(--muted);\n}\n.pager .sp {\n margin-left: auto;\n}\n.empty {\n padding: 56px 24px;\n text-align: center;\n color: var(--muted);\n}\n.empty h2 {\n margin-bottom: 6px;\n}\n.steps {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.steps li {\n display: flex;\n gap: 14px;\n padding: 16px 0;\n border-bottom: 1px solid var(--border2);\n}\n.steps li:last-child {\n border-bottom: none;\n}\n.steps .n {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #ededef;\n color: var(--muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n flex: none;\n}\n.steps li.done .n {\n background: var(--green);\n color: #fff;\n}\n.steps li.now .n {\n background: var(--accent);\n color: #fff;\n}\n.steps .body {\n flex: 1;\n}\n.steps .body b {\n display: block;\n font-weight: 600;\n margin-bottom: 2px;\n}\n\nbutton,\n.btn,\n.tg,\n.tg::after,\ntbody tr {\n transition:\n background 0.12s ease,\n border-color 0.12s ease,\n filter 0.12s ease,\n left 0.12s ease,\n color 0.12s ease;\n}\n:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n\n#toasts {\n position: fixed;\n right: 20px;\n bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 20;\n max-width: 360px;\n}\n.toast {\n background: #1f2124;\n color: #fff;\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 13px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n}\n.fail {\n margin: 12px 18px;\n padding: 12px 14px;\n background: #fdf3f3;\n border: 1px solid #f3d0d0;\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 13px;\n}\n.fail p {\n margin: 0;\n flex: 1;\n}\n\n.secret {\n margin: 0 0 16px;\n padding: 12px 14px;\n background: #fffbea;\n border: 1px solid #ecd9a0;\n border-radius: 6px;\n font-size: 13px;\n}\n.secret b {\n display: block;\n margin-bottom: 8px;\n}\n.secret-row {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n.secret-row code {\n flex: 1;\n min-width: 220px;\n word-break: break-all;\n}\n\n.sk-slot {\n display: none;\n padding: 18px;\n}\n.sk,\n.sk-row {\n height: 12px;\n background: #ededef;\n border-radius: 4px;\n margin-bottom: 10px;\n}\n.sk-row {\n height: 36px;\n}\n[data-async].busy > :not(.sk-slot):not([data-fail]) {\n visibility: hidden;\n}\n[data-async].busy > .sk-slot {\n display: block !important;\n visibility: visible;\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n.bars {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 56px;\n}\n.bars i {\n flex: 1 1 0;\n min-width: 2px;\n align-self: stretch;\n background-image: linear-gradient(\n to top,\n var(--bar, var(--accent)) var(--h),\n transparent var(--h)\n );\n}\n.bars i:hover {\n --bar: var(--accent-d);\n background-color: var(--border2);\n}\n.trend {\n font-size: 12px;\n margin-top: 3px;\n}\n.trend.up {\n color: var(--green);\n}\n.trend.down {\n color: var(--red);\n}\n.kfig.wrap4 {\n flex-wrap: wrap;\n gap: 28px 44px;\n}\n";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProbeResult } from "../knowledge/probe.ts";
|
|
2
|
+
import type { GitHubClient, Json } from "../types.ts";
|
|
3
|
+
export type ProbePlan = {
|
|
4
|
+
analysis: ProbeResult;
|
|
5
|
+
/** The `init` flags the analysis implies, in command order. */
|
|
6
|
+
recommendation: string[];
|
|
7
|
+
/** The whole `co-maintainer init owner/repo …` line. */
|
|
8
|
+
command: string;
|
|
9
|
+
/** The analysis report plus the recommendation and reasons, for storage. */
|
|
10
|
+
report: Json;
|
|
11
|
+
};
|
|
12
|
+
export declare function probePlan(client: GitHubClient, repo: string, opts: {
|
|
13
|
+
ghConcurrent: number;
|
|
14
|
+
logTime?: boolean;
|
|
15
|
+
log?: (message: string) => void;
|
|
16
|
+
}): Promise<ProbePlan>;
|
|
17
|
+
/** The `RepoConfig` patch that matches a recommendation, so a confirmed
|
|
18
|
+
* preview stores the same limits the printed command carries. */
|
|
19
|
+
export declare function recommendationPatch(recommendation: string[]): {
|
|
20
|
+
includeCodebase: boolean;
|
|
21
|
+
includePullRequests: boolean;
|
|
22
|
+
includePullRequestChanges: boolean;
|
|
23
|
+
includeCommitHistory: boolean;
|
|
24
|
+
includeHowRepoWorks: boolean;
|
|
25
|
+
maxPrMonths?: number;
|
|
26
|
+
maxCommits?: number;
|
|
27
|
+
maxPullRequestChangeLines?: number;
|
|
28
|
+
};
|