@lanes-sh/link 0.6.8 → 0.6.9
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/README.md +5 -0
- package/package.json +1 -1
- package/src/cli/brand.ts +10 -9
- package/src/cli/commands/operate/auth.ts +333 -0
- package/src/cli/commands/operate/desktop.ts +220 -0
- package/src/cli/commands/operate/findings.ts +9 -38
- package/src/cli/commands/operate/inspect.ts +59 -40
- package/src/cli/commands/operate/serve.ts +0 -3
- package/src/cli/commands/operate.ts +5 -3
- package/src/cli/main.ts +19 -4
- package/src/cli/selection.ts +15 -4
- package/src/cli/usage.ts +5 -1
- package/src/connectivity/auth/index.ts +1 -0
- package/src/connectivity/auth/oauth-authcode/provider.ts +17 -1
- package/src/connectivity/auth/oauth-authcode/refresh.ts +43 -11
- package/src/connectivity/auth/oauth-jwt/index.ts +12 -1
- package/src/connectivity/auth/reauth.ts +48 -0
- package/src/server/cors.ts +3 -3
- package/src/server/endpoint.ts +0 -17
- package/src/server/harness.ts +0 -7
- package/src/server/index.ts +3 -33
- package/src/server/mcp/index.ts +0 -1
- package/src/server/mcp/visibility.ts +0 -33
- package/src/cli/commands/operate/dashboard.ts +0 -107
- package/src/cli/dashboard-page.ts +0 -293
- package/src/cli/dashboard-shell.ts +0 -125
- package/src/cli/provider-marks.ts +0 -45
- package/src/server/dashboard.ts +0 -212
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A brand mark per provider, drawn rather than fetched.
|
|
3
|
-
*
|
|
4
|
-
* Vendored from Simple Icons 16.28.0, which dedicates its paths to the public
|
|
5
|
-
* domain under CC0-1.0. **The trademarks themselves remain their owners'** —
|
|
6
|
-
* these identify the service a connection reaches, which is what nominative use
|
|
7
|
-
* is for, and nothing here implies that any of them endorses this.
|
|
8
|
-
*
|
|
9
|
-
* Inlined because there is nowhere else to put them. The page is served under a
|
|
10
|
-
* strict `default-src 'none'`, so it fetches nothing; the alternative — a CDN —
|
|
11
|
-
* would tell a third party which dashboard the owner is looking at, every time
|
|
12
|
-
* they look. There is also no static-asset pipeline to serve them from.
|
|
13
|
-
*
|
|
14
|
-
* Every path is one shape on a `0 0 24 24` canvas, filled with `currentColor`,
|
|
15
|
-
* so a mark inherits the page's text colour and is correct in either theme
|
|
16
|
-
* without a second copy. That is also why these are monochrome rather than the
|
|
17
|
-
* full-colour originals: `callback-page.ts` reserves colour for status, and a
|
|
18
|
-
* row of brand hues would spend it on decoration.
|
|
19
|
-
*
|
|
20
|
-
* Ids sharing a mark do so deliberately — `gmail` and `gmail_mcp` reach the same
|
|
21
|
-
* mailbox by different transports, and the four `icloud_*` providers are one
|
|
22
|
-
* service. The mark names the vendor; the name beside it names the product.
|
|
23
|
-
*
|
|
24
|
-
* Absent here, `dashboard-page.ts` falls back to a monogram. That covers every
|
|
25
|
-
* provider an operator writes themselves, and the two shipped ones Simple Icons
|
|
26
|
-
* does not carry: Slack, whose mark was withdrawn at the brand's request, and
|
|
27
|
-
* Google Contacts, which it never had.
|
|
28
|
-
*/
|
|
29
|
-
export const PROVIDER_MARKS: Readonly<Record<string, string>> = {
|
|
30
|
-
calendar: 'M18.316 5.684H24v12.632h-5.684V5.684zM5.684 24h12.632v-5.684H5.684V24zM18.316 5.684V0H1.895A1.894 1.894 0 0 0 0 1.895v16.421h5.684V5.684h12.632zm-7.207 6.25v-.065c.272-.144.5-.349.687-.617s.279-.595.279-.982c0-.379-.099-.72-.3-1.025a2.05 2.05 0 0 0-.832-.714 2.703 2.703 0 0 0-1.197-.257c-.6 0-1.094.156-1.481.467-.386.311-.65.671-.793 1.078l1.085.452c.086-.249.224-.461.413-.633.189-.172.445-.257.767-.257.33 0 .602.088.816.264a.86.86 0 0 1 .322.703c0 .33-.12.589-.36.778-.24.19-.535.284-.886.284h-.567v1.085h.633c.407 0 .748.109 1.02.327.272.218.407.499.407.843 0 .336-.129.614-.387.832s-.565.327-.924.327c-.351 0-.651-.103-.897-.311-.248-.208-.422-.502-.521-.881l-1.096.452c.178.616.505 1.082.977 1.401.472.319.984.478 1.538.477a2.84 2.84 0 0 0 1.293-.291c.382-.193.684-.458.902-.794.218-.336.327-.72.327-1.149 0-.429-.115-.797-.344-1.105a2.067 2.067 0 0 0-.881-.689zm2.093-1.931l.602.913L15 10.045v5.744h1.187V8.446h-.827l-2.158 1.557zM22.105 0h-3.289v5.184H24V1.895A1.894 1.894 0 0 0 22.105 0zm-3.289 23.5l4.684-4.684h-4.684V23.5zM0 22.105C0 23.152.848 24 1.895 24h3.289v-5.184H0v3.289z',
|
|
31
|
-
docs: 'M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727h-6zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818v1.364zm0-3.273H7.091V9.273h9.818v1.363zM14.727 6h6l-6-6v6z',
|
|
32
|
-
drive: 'M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z',
|
|
33
|
-
drive_mcp: 'M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z',
|
|
34
|
-
github: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
|
|
35
|
-
gmail: 'M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z',
|
|
36
|
-
gmail_mcp: 'M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z',
|
|
37
|
-
icloud_calendar: 'M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z',
|
|
38
|
-
icloud_contacts: 'M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z',
|
|
39
|
-
icloud_drive: 'M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z',
|
|
40
|
-
icloud_mail: 'M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z',
|
|
41
|
-
linear: 'M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z',
|
|
42
|
-
notion: 'M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z',
|
|
43
|
-
sheets: 'M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6l-6-6zm1.363 10.636h-3.41v1.91h3.41v-1.91zm0 3.273h-3.41v1.91h3.41v-1.91zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5h6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z',
|
|
44
|
-
google_tasks: 'M11.383.617C5.097.617 0 5.714 0 12c0 6.286 5.097 11.383 11.383 11.383 6.286 0 11.38-5.097 11.38-11.383a11.34 11.34 0 0 0-.878-4.389l-3.203 3.203c.062.387.1.782.1 1.186a7.398 7.398 0 1 1-7.4-7.398c1.499 0 2.889.448 4.054 1.214l2.857-2.857a11.325 11.325 0 0 0-6.91-2.342zm9.674.756c-.292 0-.583.112-.805.334-2.97 2.965-5.934 5.934-8.9 8.902L9.596 8.854a1.139 1.139 0 0 0-1.61 0l-1.775 1.773a1.139 1.139 0 0 0 0 1.61l4.166 4.163a1.421 1.421 0 0 0 2.012 0L23.666 5.121a1.136 1.136 0 0 0 0-1.61l-1.805-1.804a1.136 1.136 0 0 0-.804-.334z',
|
|
45
|
-
};
|
package/src/server/dashboard.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import type { Authenticator } from '#auth';
|
|
2
|
-
import { dashboardPage, dashboardSignInPage, type DashboardConnection } from '#cli/dashboard-page.ts';
|
|
3
|
-
import type { Generations } from './generations.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The dashboard route.
|
|
7
|
-
*
|
|
8
|
-
* Local only, and mounted by nobody else: `lanes link start` asks for it and the
|
|
9
|
-
* container entrypoint does not, with `serve()` refusing it a second time if the
|
|
10
|
-
* bind address is not loopback. That is a decision rather than an omission, and
|
|
11
|
-
* ADR-018 is why. Cloud Run IAM admits a caller holding a Google-signed identity
|
|
12
|
-
* token; a browser doing a top-level navigation never sends one, so `access:
|
|
13
|
-
* iam` answers a person with a 403 rather than a sign-in page. The mode a remote
|
|
14
|
-
* client actually uses — `access: public` with an `auth.authorization` block —
|
|
15
|
-
* leaves the front door open and gates `/mcp` on a bearer token instead, which a
|
|
16
|
-
* navigation cannot carry either. Neither arrangement has a browser-shaped door,
|
|
17
|
-
* so this one does not pretend to be reachable through them.
|
|
18
|
-
*
|
|
19
|
-
* What it serves is a reader. Nothing here writes config, authorises anything,
|
|
20
|
-
* or reaches an upstream — see `#cli/dashboard-page.ts` for why that is the
|
|
21
|
-
* honest shape rather than a limitation to lift later.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export const DASHBOARD_PATH = '/dashboard';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Whether a bind serves the dashboard.
|
|
28
|
-
*
|
|
29
|
-
* Two conditions, and both are load-bearing. Asking is the entrypoint's
|
|
30
|
-
* decision — `lanes link start` does, the container does not. Loopback is the
|
|
31
|
-
* one that does not depend on anybody remembering: a routable bind is a
|
|
32
|
-
* deployed one, and the consequence of getting this wrong is a page listing the
|
|
33
|
-
* owner's accounts on a public address. Named rather than inlined so the rule
|
|
34
|
-
* can be stated once and asserted directly.
|
|
35
|
-
*/
|
|
36
|
-
export function servesDashboard(asked: boolean | undefined, loopback: boolean): boolean {
|
|
37
|
-
return asked === true && loopback;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** The query parameter `lanes link dashboard` puts the profile token in. */
|
|
41
|
-
const KEY_PARAM = 'k';
|
|
42
|
-
|
|
43
|
-
const COOKIE = 'lanes_link_dashboard';
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* How long a browser stays signed in.
|
|
47
|
-
*
|
|
48
|
-
* Bounded rather than tied to the process: an endpoint left running for weeks
|
|
49
|
-
* should not leave a tab that authenticated once able to read the workspace for
|
|
50
|
-
* as long as it lives.
|
|
51
|
-
*/
|
|
52
|
-
const SESSION_MS = 12 * 60 * 60 * 1000;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Sessions this handler has issued.
|
|
56
|
-
*
|
|
57
|
-
* A random id, not the profile token. The token arrives once, in a URL the CLI
|
|
58
|
-
* built, and is exchanged here for something that is worth nothing anywhere else
|
|
59
|
-
* — so the credential that reaches every profile is never sitting in a cookie
|
|
60
|
-
* jar, and revoking it is closing this process rather than clearing a browser.
|
|
61
|
-
*
|
|
62
|
-
* In memory, which is the one place stateless-by-design does not apply: this
|
|
63
|
-
* route exists only on a single local process. The deployed instance that
|
|
64
|
-
* `index.ts` keeps stateless for never serves it.
|
|
65
|
-
*/
|
|
66
|
-
export interface DashboardSessions {
|
|
67
|
-
issue(): string;
|
|
68
|
-
valid(id: string | null): boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function dashboardSessions(now: () => number = Date.now): DashboardSessions {
|
|
72
|
-
const issued = new Map<string, number>();
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
issue() {
|
|
76
|
-
const id = crypto.randomUUID();
|
|
77
|
-
const at = now();
|
|
78
|
-
// Pruned on write rather than on a timer, so an endpoint nobody is
|
|
79
|
-
// looking at holds nothing and runs nothing.
|
|
80
|
-
for (const [key, expires] of issued) if (expires <= at) issued.delete(key);
|
|
81
|
-
issued.set(id, at + SESSION_MS);
|
|
82
|
-
return id;
|
|
83
|
-
},
|
|
84
|
-
valid(id) {
|
|
85
|
-
if (!id) return false;
|
|
86
|
-
const expires = issued.get(id);
|
|
87
|
-
if (expires === undefined) return false;
|
|
88
|
-
if (expires <= now()) {
|
|
89
|
-
issued.delete(id);
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
return true;
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface DashboardSurface {
|
|
98
|
-
readonly generations: Generations;
|
|
99
|
-
readonly authenticator: Authenticator;
|
|
100
|
-
/** The profile shown when the URL names none. */
|
|
101
|
-
readonly primary: string;
|
|
102
|
-
readonly sessions: DashboardSessions;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function cookieValue(header: string | null, name: string): string | null {
|
|
106
|
-
if (!header) return null;
|
|
107
|
-
for (const part of header.split(';')) {
|
|
108
|
-
const [key, ...rest] = part.trim().split('=');
|
|
109
|
-
if (key === name) return rest.join('=');
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function handleDashboard(
|
|
115
|
-
request: Request,
|
|
116
|
-
surface: DashboardSurface,
|
|
117
|
-
): Promise<Response> {
|
|
118
|
-
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
119
|
-
return new Response('Method not allowed', { status: 405, headers: { allow: 'GET, HEAD' } });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const url = new URL(request.url);
|
|
123
|
-
const key = url.searchParams.get(KEY_PARAM);
|
|
124
|
-
|
|
125
|
-
if (key !== null) {
|
|
126
|
-
// The token is checked by the same authenticator every other route uses,
|
|
127
|
-
// rather than compared here. One credential, one comparison, one place a
|
|
128
|
-
// rotation has to land.
|
|
129
|
-
const outcome = await surface.authenticator.authenticate(`Bearer ${key}`);
|
|
130
|
-
if (!outcome.ok) return dashboardSignInPage(401);
|
|
131
|
-
|
|
132
|
-
// Straight back out to a URL without it. A token that stays in the address
|
|
133
|
-
// bar is one in the history, in the window title, and in whatever the next
|
|
134
|
-
// link sends as a Referer — and this is the only request that needs it.
|
|
135
|
-
url.searchParams.delete(KEY_PARAM);
|
|
136
|
-
return new Response(null, {
|
|
137
|
-
status: 303,
|
|
138
|
-
headers: {
|
|
139
|
-
location: `${url.pathname}${url.search}`,
|
|
140
|
-
// No `Secure`: this is served over http on loopback and nowhere else,
|
|
141
|
-
// and a Secure cookie would simply never be stored. `SameSite=Strict`
|
|
142
|
-
// is what keeps another origin from navigating into an authenticated
|
|
143
|
-
// view, and the rebinding check in front of every route (`index.ts`)
|
|
144
|
-
// is what keeps one from being the origin in the first place.
|
|
145
|
-
'set-cookie':
|
|
146
|
-
`${COOKIE}=${surface.sessions.issue()}; HttpOnly; SameSite=Strict; ` +
|
|
147
|
-
`Path=${DASHBOARD_PATH}; Max-Age=${Math.floor(SESSION_MS / 1000)}`,
|
|
148
|
-
'cache-control': 'no-store',
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (!surface.sessions.valid(cookieValue(request.headers.get('cookie'), COOKIE))) {
|
|
154
|
-
return dashboardSignInPage(401);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Pinned for the whole render, as a request on `/mcp` is: a reload landing
|
|
158
|
-
// between the connection list and the provider catalogue would otherwise
|
|
159
|
-
// produce a page describing two different configurations.
|
|
160
|
-
const generation = surface.generations.acquire();
|
|
161
|
-
try {
|
|
162
|
-
const asked = url.searchParams.get('profile');
|
|
163
|
-
const name = asked ?? surface.primary;
|
|
164
|
-
const runtime = generation.profiles.get(name);
|
|
165
|
-
|
|
166
|
-
// A name this endpoint does not serve can only have been typed: every
|
|
167
|
-
// profile link on the page comes from the list below. Refusing beats
|
|
168
|
-
// rendering a different profile than the URL says, which is the silent pick
|
|
169
|
-
// `resolveSelection` exists to prevent.
|
|
170
|
-
if (!runtime) return new Response('Not found', { status: 404 });
|
|
171
|
-
|
|
172
|
-
const records = runtime.connections ? await runtime.connections() : [];
|
|
173
|
-
const byKey = new Map(records.map((record) => [`${record.provider}.${record.id}`, record]));
|
|
174
|
-
|
|
175
|
-
// The same join `lanes link status` makes: config says what should exist,
|
|
176
|
-
// the store says what does, and the difference is the whole point of
|
|
177
|
-
// showing it.
|
|
178
|
-
const connections: DashboardConnection[] = runtime.config.connections.map((connection) => ({
|
|
179
|
-
key: `${connection.provider}.${connection.id}`,
|
|
180
|
-
provider: connection.provider,
|
|
181
|
-
account: connection.account,
|
|
182
|
-
...(connection.label ? { label: connection.label } : {}),
|
|
183
|
-
state: byKey.get(`${connection.provider}.${connection.id}`)?.status ?? 'not reconciled',
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
// Never `instance.default_target`, which this line used to fall back to.
|
|
187
|
-
// ADR-037 left that field inert precisely so a missing target surfaces here
|
|
188
|
-
// rather than one command later: the page names a credential store, and a
|
|
189
|
-
// page that guessed which one would report an account as connected against
|
|
190
|
-
// a store the endpoint asking for it does not read.
|
|
191
|
-
//
|
|
192
|
-
// Absent only if a runtime reached this route without being opened against
|
|
193
|
-
// anything, which a served endpoint never does — `endpoint.ts` builds every
|
|
194
|
-
// entry in that map from a `Runtime`, whose target is required. Refusing
|
|
195
|
-
// beats rendering, for the same reason the 404 above does.
|
|
196
|
-
if (!runtime.target) return new Response('No target', { status: 500 });
|
|
197
|
-
|
|
198
|
-
return dashboardPage({
|
|
199
|
-
profile: name,
|
|
200
|
-
profiles: generation.names(),
|
|
201
|
-
target: runtime.target,
|
|
202
|
-
// One, and it is the one being served. A revision serves exactly the
|
|
203
|
-
// target it was deployed as, and the config no longer carries a list of
|
|
204
|
-
// alternatives for it to offer (ADR-052).
|
|
205
|
-
targets: [runtime.target],
|
|
206
|
-
connections,
|
|
207
|
-
ownClients: Object.keys(runtime.config.oauth_apps),
|
|
208
|
-
});
|
|
209
|
-
} finally {
|
|
210
|
-
await surface.generations.release(generation);
|
|
211
|
-
}
|
|
212
|
-
}
|