@zackbart/connecta 0.15.1 → 0.16.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/AGENTS.md +40 -20
- package/CHANGELOG.md +653 -0
- package/README.md +25 -5
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +41 -5
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +20 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +30 -3
- package/dist/errors.js +1 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +0 -1
- package/dist/providers/cloudflare.js +261 -299
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -60
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +92 -10
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +14 -14
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +23 -2
- package/documentation/operations.md +325 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +667 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/documentation/upgrading.md +454 -0
- package/ethos.md +14 -9
- package/examples/worker/README.md +102 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +10 -6
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +16 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
package/src/ui.ts
DELETED
|
@@ -1,744 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
credentialTestRule,
|
|
3
|
-
describeUndeclaredCredentialFields,
|
|
4
|
-
storedCredentialShape,
|
|
5
|
-
} from "./credentials.js";
|
|
6
|
-
import type { CredentialVault } from "./credentials.js";
|
|
7
|
-
import {
|
|
8
|
-
closeConnectorScope,
|
|
9
|
-
type DeferredWork,
|
|
10
|
-
} from "./connector-scope.js";
|
|
11
|
-
import {
|
|
12
|
-
mapSettledWithConcurrency,
|
|
13
|
-
resolveDiscoveryConcurrency,
|
|
14
|
-
} from "./concurrency.js";
|
|
15
|
-
import {
|
|
16
|
-
type CredentialManagementCapability,
|
|
17
|
-
type AccessTokenManagementCapability,
|
|
18
|
-
type UiConnector,
|
|
19
|
-
type UiData,
|
|
20
|
-
type UiTool,
|
|
21
|
-
} from "./operator-ui/model.js";
|
|
22
|
-
import {
|
|
23
|
-
OPERATOR_UI_CSS,
|
|
24
|
-
OPERATOR_UI_SCRIPT,
|
|
25
|
-
} from "./operator-ui/generated.js";
|
|
26
|
-
import type { Registry } from "./registry.js";
|
|
27
|
-
import type {
|
|
28
|
-
ConnectaBranding,
|
|
29
|
-
ConnectorStatus,
|
|
30
|
-
UiAuthConfig,
|
|
31
|
-
} from "./types.js";
|
|
32
|
-
import { CONNECTA_VERSION } from "./version.js";
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
filterUiConnectors,
|
|
36
|
-
type AccessTokenManagementCapability,
|
|
37
|
-
type CredentialManagementCapability,
|
|
38
|
-
type UiConnector,
|
|
39
|
-
type UiData,
|
|
40
|
-
} from "./operator-ui/model.js";
|
|
41
|
-
|
|
42
|
-
/** Connecta's default monochrome "C" mark. */
|
|
43
|
-
export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
44
|
-
<style>
|
|
45
|
-
.fg { fill: #000 }
|
|
46
|
-
@media (prefers-color-scheme: dark) { .fg { fill: #fff } }
|
|
47
|
-
</style>
|
|
48
|
-
<path class="fg" d="M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z"/>
|
|
49
|
-
</svg>`;
|
|
50
|
-
|
|
51
|
-
interface ResolvedBranding {
|
|
52
|
-
productName: string;
|
|
53
|
-
productUrl?: string;
|
|
54
|
-
ownerName?: string;
|
|
55
|
-
ownerUrl?: string;
|
|
56
|
-
description: string;
|
|
57
|
-
/** Browser tab title and page meta name. */
|
|
58
|
-
pageTitle: string;
|
|
59
|
-
/** href for the page's icon link. */
|
|
60
|
-
faviconHref: string;
|
|
61
|
-
themeColor: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const DEFAULT_FAVICON_HREF = "/favicon.svg";
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Branding arrives from operator config, which is untyped at a JS call site, so
|
|
68
|
-
* every field is treated as `unknown`: a non-string is read as unset rather than
|
|
69
|
-
* throwing on `.trim()`. Rendering must degrade to defaults for a malformed
|
|
70
|
-
* value, never fail — `createConnecta` calls this during construction.
|
|
71
|
-
*/
|
|
72
|
-
function trimmedString(value: unknown): string | undefined {
|
|
73
|
-
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function resolveBranding(
|
|
77
|
-
branding?: ConnectaBranding,
|
|
78
|
-
): ResolvedBranding {
|
|
79
|
-
const productName = trimmedString(branding?.productName) ?? "Connecta";
|
|
80
|
-
const ownerName = trimmedString(branding?.ownerName);
|
|
81
|
-
// Operator branding URLs become masthead/callback hrefs, so a non-http(s)
|
|
82
|
-
// scheme (javascript:, data:) is dropped the same as an unset URL — the
|
|
83
|
-
// callers already render a <span> instead of an <a> when it is absent.
|
|
84
|
-
const productUrl = trimmedString(branding?.productUrl);
|
|
85
|
-
const ownerUrl = trimmedString(branding?.ownerUrl);
|
|
86
|
-
const faviconHref = trimmedString(branding?.favicon?.href);
|
|
87
|
-
return {
|
|
88
|
-
productName,
|
|
89
|
-
...(productUrl && isSafeHttpUrl(productUrl) ? { productUrl } : {}),
|
|
90
|
-
...(ownerName ? { ownerName } : {}),
|
|
91
|
-
...(ownerUrl && isSafeHttpUrl(ownerUrl) ? { ownerUrl } : {}),
|
|
92
|
-
description:
|
|
93
|
-
trimmedString(branding?.description) ??
|
|
94
|
-
`Manage the services this ${productName} instance makes available to agents.`,
|
|
95
|
-
pageTitle:
|
|
96
|
-
trimmedString(branding?.pageTitle) ??
|
|
97
|
-
(ownerName ? `${productName} — ${ownerName}` : productName),
|
|
98
|
-
faviconHref:
|
|
99
|
-
faviconHref && isSafeIconHref(faviconHref)
|
|
100
|
-
? faviconHref
|
|
101
|
-
: DEFAULT_FAVICON_HREF,
|
|
102
|
-
themeColor: trimmedString(branding?.themeColor) ?? "#ffffff",
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Whether the operator meant to supply a value here — the question every
|
|
108
|
-
* dropped-URL warning asks before naming a field, and one definition so the
|
|
109
|
-
* branding and `uiAuth` warnings cannot answer it differently. A non-string
|
|
110
|
-
* counts as set: the intent was there and is exactly what the warning reports
|
|
111
|
-
* on. A blank or whitespace-only string does not; that is indistinguishable
|
|
112
|
-
* from leaving the field alone, and both take the default silently.
|
|
113
|
-
*/
|
|
114
|
-
function isSetUrlValue(value: unknown): boolean {
|
|
115
|
-
return typeof value === "string"
|
|
116
|
-
? trimmedString(value) !== undefined
|
|
117
|
-
: value !== undefined && value !== null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* A JS string literal safe to inline in a script element. Escaping `/` keeps
|
|
122
|
-
* an operator-supplied `</script>` from terminating the element early.
|
|
123
|
-
*/
|
|
124
|
-
function stringForInlineScript(value: string): string {
|
|
125
|
-
return JSON.stringify(value).replace(/\//g, "\\/");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Names of the branding URLs the operator set that failed their gate and were
|
|
130
|
-
* replaced by a default. Lives beside the gates so the startup warning cannot
|
|
131
|
-
* drift from them, and takes `unknown` fields for the same reason
|
|
132
|
-
* `resolveBranding` does — a warning helper must never throw.
|
|
133
|
-
*/
|
|
134
|
-
export function droppedBrandingUrls(branding?: ConnectaBranding): string[] {
|
|
135
|
-
if (!branding) return [];
|
|
136
|
-
const resolved = resolveBranding(branding);
|
|
137
|
-
const faviconHref = branding.favicon?.href;
|
|
138
|
-
return [
|
|
139
|
-
...(isSetUrlValue(branding.productUrl) && !resolved.productUrl
|
|
140
|
-
? ["productUrl"]
|
|
141
|
-
: []),
|
|
142
|
-
...(isSetUrlValue(branding.ownerUrl) && !resolved.ownerUrl
|
|
143
|
-
? ["ownerUrl"]
|
|
144
|
-
: []),
|
|
145
|
-
...(isSetUrlValue(faviconHref) &&
|
|
146
|
-
trimmedString(faviconHref) !== resolved.faviconHref
|
|
147
|
-
? ["favicon.href"]
|
|
148
|
-
: []),
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* True only for absolute `http:`/`https:` URLs. Downstream connectors control
|
|
154
|
-
* their `authorizationUrl`, so a hostile/misconfigured one could hand back a
|
|
155
|
-
* `javascript:` (or other) scheme; gate it before it can become an href.
|
|
156
|
-
*/
|
|
157
|
-
export function isSafeHttpUrl(url: unknown): boolean {
|
|
158
|
-
if (typeof url !== "string") return false;
|
|
159
|
-
try {
|
|
160
|
-
const scheme = new URL(url).protocol;
|
|
161
|
-
return scheme === "http:" || scheme === "https:";
|
|
162
|
-
} catch {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Only the second check's base; any origin works because the check is whether
|
|
169
|
-
* the href stays on whatever origin it is resolved against. It is deliberately
|
|
170
|
-
* never the sole gate: a value whose own authority equals this host (say
|
|
171
|
-
* `//connecta.invalid/x`) would resolve to this exact origin and pass, so the
|
|
172
|
-
* structural check below runs first and is what actually rejects `//host`.
|
|
173
|
-
*/
|
|
174
|
-
const SAME_ORIGIN_PROBE = "https://connecta.invalid";
|
|
175
|
-
|
|
176
|
-
/** Removed anywhere in a URL by the parser, so a gate must ignore them too. */
|
|
177
|
-
const URL_STRIPPED_CHARS = /[\t\n\r]/g;
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* True for values allowed in the page's `<link rel="icon" href>`: an absolute
|
|
181
|
-
* `http(s)` URL (an icon the operator hosts elsewhere) or a path rooted at this
|
|
182
|
-
* origin. The relative carve-out is deliberate rather than accidental — the
|
|
183
|
-
* default href is the relative `/favicon.svg`, which `isSafeHttpUrl` alone would
|
|
184
|
-
* reject — and it is kept narrow on both ends.
|
|
185
|
-
*
|
|
186
|
-
* Root-relative only, because operator and OAuth callback pages sit at
|
|
187
|
-
* different depths and a document-relative path would resolve differently.
|
|
188
|
-
*
|
|
189
|
-
* "Root-relative" is enforced structurally: exactly one leading `/` followed by
|
|
190
|
-
* a character that is neither `/` nor `\`. Both of those would make the value an
|
|
191
|
-
* authority (`//host`, and `/\host` because the URL parser folds `\` to `/` in
|
|
192
|
-
* special schemes), pointing at an origin this server does not control. The test
|
|
193
|
-
* runs on a copy with tab/newline/CR removed, since the parser strips those
|
|
194
|
-
* anywhere and `/\t/host` would otherwise slip through as single-slash. The
|
|
195
|
-
* origin comparison that follows is defense in depth, not the authority check —
|
|
196
|
-
* on its own it would accept an authority that happened to equal the probe host.
|
|
197
|
-
*/
|
|
198
|
-
export function isSafeIconHref(href: unknown): boolean {
|
|
199
|
-
if (typeof href !== "string") return false;
|
|
200
|
-
if (isSafeHttpUrl(href)) return true;
|
|
201
|
-
if (!/^\/(?![/\\])/.test(href.replace(URL_STRIPPED_CHARS, ""))) return false;
|
|
202
|
-
try {
|
|
203
|
-
return new URL(href, SAME_ORIGIN_PROBE).origin === SAME_ORIGIN_PROBE;
|
|
204
|
-
} catch {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* True only for an absolute `https:` URL — the gate every `uiAuth` URL passes:
|
|
211
|
-
* `frontendApiUrl`, which becomes the operator shell's sign-in loader source,
|
|
212
|
-
* and `signInUrl`/`signUpUrl`, which ClerkJS uses as *navigation targets* when
|
|
213
|
-
* the operator signs in. With those three gated, no operator-config value
|
|
214
|
-
* reaches the browser in a URL position — attribute or navigation — without
|
|
215
|
-
* validation, and there is no exception left to remember.
|
|
216
|
-
*
|
|
217
|
-
* Stricter than `isSafeHttpUrl` on purpose: no `http:` carve-out, no loopback
|
|
218
|
-
* carve-out, and no relative form. Nobody types `frontendApiUrl` — the shipped
|
|
219
|
-
* Clerk adapter derives it from the publishable key, and Clerk's Frontend API is
|
|
220
|
-
* always https — and a cleartext script source on an operator page would be a
|
|
221
|
-
* downgrade even where a browser's mixed-content rules had not already blocked
|
|
222
|
-
* it. `signInUrl`/`signUpUrl` *are* typed by the operator, but what belongs
|
|
223
|
-
* there is a hosted Account Portal address (`https://accounts.<domain>` or
|
|
224
|
-
* `https://<slug>.accounts.dev`), which is https as well; `http:` would carry a
|
|
225
|
-
* sign-in over cleartext, and a path relative to this origin is meaningless
|
|
226
|
-
* because this server hosts no sign-in page of its own. So the looser gate would
|
|
227
|
-
* buy nothing real, and the same strictness holds for all three.
|
|
228
|
-
*/
|
|
229
|
-
export function isSafeHttpsUrl(url: unknown): boolean {
|
|
230
|
-
if (typeof url !== "string") return false;
|
|
231
|
-
try {
|
|
232
|
-
return new URL(url).protocol === "https:";
|
|
233
|
-
} catch {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
|
|
240
|
-
* gate. Lives beside the gate for the same reason `droppedBrandingUrls` does: the
|
|
241
|
-
* startup warning cannot then drift from what rendering actually drops. Every
|
|
242
|
-
* field is read defensively rather than trusted, because a custom `InboundAuth`
|
|
243
|
-
* is untyped at a JS call site — `isSafeHttpsUrl` takes `unknown`, and a
|
|
244
|
-
* `uiAuth` that is not the clerk shape is reported as nothing to warn about.
|
|
245
|
-
*
|
|
246
|
-
* `frontendApiUrl` is required, so anything that fails its gate is a drop.
|
|
247
|
-
* `signInUrl` and `signUpUrl` are optional, so only a value the operator
|
|
248
|
-
* *supplied* and the gate then rejected is worth a warning — an unset field
|
|
249
|
-
* took no default away from anyone. `isSetUrlValue` decides that, the same way
|
|
250
|
-
* and for the same reasons it decides it for the branding URLs: a warning that
|
|
251
|
-
* fires for one and not the other would be reporting on the field rather than
|
|
252
|
-
* on the operator's intent. Rendering is not consulted for this: it drops on
|
|
253
|
-
* the gate alone, and a blank string fails that gate too — it is simply not
|
|
254
|
-
* *reported*, because a blank is indistinguishable from leaving the field
|
|
255
|
-
* alone.
|
|
256
|
-
*/
|
|
257
|
-
export function droppedUiAuthUrls(uiAuth?: UiAuthConfig): string[] {
|
|
258
|
-
if (!uiAuth || uiAuth.kind !== "clerk") return [];
|
|
259
|
-
return [
|
|
260
|
-
...(isSafeHttpsUrl(uiAuth.frontendApiUrl) ? [] : ["uiAuth.frontendApiUrl"]),
|
|
261
|
-
...(isSetUrlValue(uiAuth.signInUrl) && !isSafeHttpsUrl(uiAuth.signInUrl)
|
|
262
|
-
? ["uiAuth.signInUrl"]
|
|
263
|
-
: []),
|
|
264
|
-
...(isSetUrlValue(uiAuth.signUpUrl) && !isSafeHttpsUrl(uiAuth.signUpUrl)
|
|
265
|
-
? ["uiAuth.signUpUrl"]
|
|
266
|
-
: []),
|
|
267
|
-
];
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export type OperatorPage =
|
|
271
|
-
| "connections"
|
|
272
|
-
| "credentials"
|
|
273
|
-
| "tokens"
|
|
274
|
-
| "activity";
|
|
275
|
-
|
|
276
|
-
const OPERATOR_PAGE_LABELS: Readonly<Record<OperatorPage, string>> = {
|
|
277
|
-
connections: "Connections",
|
|
278
|
-
credentials: "Credentials",
|
|
279
|
-
tokens: "Access tokens",
|
|
280
|
-
activity: "Activity",
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
export function operatorPageForPath(path: string): OperatorPage | undefined {
|
|
284
|
-
if (path === "/") return "connections";
|
|
285
|
-
if (path === "/credentials") return "credentials";
|
|
286
|
-
if (path === "/tokens") return "tokens";
|
|
287
|
-
if (path === "/activity") return "activity";
|
|
288
|
-
return undefined;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function operatorPageTitle(
|
|
292
|
-
page: OperatorPage,
|
|
293
|
-
configuredTitle: string,
|
|
294
|
-
): string {
|
|
295
|
-
return `${OPERATOR_PAGE_LABELS[page]} — ${configuredTitle}`;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function credentialManagementCapability(input: {
|
|
299
|
-
eligibleClerkOperator: boolean;
|
|
300
|
-
hasCredentialSlots: boolean;
|
|
301
|
-
hasCredentialVault: boolean;
|
|
302
|
-
}): CredentialManagementCapability {
|
|
303
|
-
if (!input.eligibleClerkOperator) return "requires_clerk";
|
|
304
|
-
if (!input.hasCredentialSlots) return "no_slots";
|
|
305
|
-
if (!input.hasCredentialVault) return "vault_not_configured";
|
|
306
|
-
return "available";
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Build the read-only status payload served at `/ui/data`. Broken connectors are
|
|
311
|
-
* isolated: they surface status "error" with an empty tool list rather than
|
|
312
|
-
* failing the whole payload.
|
|
313
|
-
*/
|
|
314
|
-
export async function buildUiData(
|
|
315
|
-
registry: Registry,
|
|
316
|
-
baseUrl: string,
|
|
317
|
-
serverInfo: { name: string; version: string },
|
|
318
|
-
credentialVault?: CredentialVault,
|
|
319
|
-
activityEnabled = false,
|
|
320
|
-
credentialManagement: CredentialManagementCapability = credentialVault
|
|
321
|
-
? "available"
|
|
322
|
-
: "requires_clerk",
|
|
323
|
-
defer?: DeferredWork,
|
|
324
|
-
oauthManagement = false,
|
|
325
|
-
discoveryConcurrency?: number,
|
|
326
|
-
accessTokenManagement: AccessTokenManagementCapability = "not_configured",
|
|
327
|
-
): Promise<UiData> {
|
|
328
|
-
const requestScope = {};
|
|
329
|
-
const connectorSet = registry.listConnectors();
|
|
330
|
-
const concurrency = resolveDiscoveryConcurrency(discoveryConcurrency);
|
|
331
|
-
const settled = await mapSettledWithConcurrency(
|
|
332
|
-
connectorSet,
|
|
333
|
-
concurrency,
|
|
334
|
-
async (c): Promise<UiConnector> => {
|
|
335
|
-
const drift = await registry.credentialDriftFor(c.id);
|
|
336
|
-
const status: ConnectorStatus = drift
|
|
337
|
-
? { state: "auth_required", message: drift }
|
|
338
|
-
: await registry.statusFor(c.id, baseUrl, requestScope);
|
|
339
|
-
let tools: UiTool[] = [];
|
|
340
|
-
// `status()` on an unauthenticated remote connector starts OAuth and
|
|
341
|
-
// stores its state + PKCE verifier. Probing listTools immediately
|
|
342
|
-
// afterward would start a second flow, overwrite that state, and return
|
|
343
|
-
// the now-stale first URL to the operator. Only inspect tools after
|
|
344
|
-
// status proves the connector is healthy.
|
|
345
|
-
if (status.state === "ok") {
|
|
346
|
-
try {
|
|
347
|
-
tools = (
|
|
348
|
-
await registry.getTools(c.id, baseUrl, requestScope)
|
|
349
|
-
).map((t) => ({
|
|
350
|
-
name: t.name,
|
|
351
|
-
address: `${c.id}.${t.name}`,
|
|
352
|
-
...(t.description !== undefined
|
|
353
|
-
? { description: t.description }
|
|
354
|
-
: {}),
|
|
355
|
-
}));
|
|
356
|
-
} catch {
|
|
357
|
-
// broken connector: reported via status "error", tools stay empty
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
let credential: UiConnector["credential"];
|
|
361
|
-
if (c.credential && credentialVault) {
|
|
362
|
-
// One rule, shared with the test route: only the hook matching the
|
|
363
|
-
// declared credential shape can run, so the button is offered only
|
|
364
|
-
// where a click can succeed (src/credentials.ts).
|
|
365
|
-
const testRule = credentialTestRule(c);
|
|
366
|
-
const credentialFields = (
|
|
367
|
-
metadata?: Awaited<ReturnType<CredentialVault["metadata"]>>,
|
|
368
|
-
) =>
|
|
369
|
-
c.credential?.fields?.map((field) => {
|
|
370
|
-
const fieldMetadata = metadata?.fields?.[field.name];
|
|
371
|
-
return {
|
|
372
|
-
name: field.name,
|
|
373
|
-
label: field.label,
|
|
374
|
-
...(field.description
|
|
375
|
-
? { description: field.description }
|
|
376
|
-
: {}),
|
|
377
|
-
...(field.placeholder
|
|
378
|
-
? { placeholder: field.placeholder }
|
|
379
|
-
: {}),
|
|
380
|
-
inputType: field.inputType ?? "password",
|
|
381
|
-
configured: Boolean(fieldMetadata),
|
|
382
|
-
...(fieldMetadata
|
|
383
|
-
? {
|
|
384
|
-
lastFour: fieldMetadata.lastFour,
|
|
385
|
-
updatedAt: fieldMetadata.updatedAt,
|
|
386
|
-
}
|
|
387
|
-
: {}),
|
|
388
|
-
};
|
|
389
|
-
});
|
|
390
|
-
try {
|
|
391
|
-
const metadata = await credentialVault.metadata(c.id);
|
|
392
|
-
const fields = credentialFields(metadata);
|
|
393
|
-
const shape = storedCredentialShape(
|
|
394
|
-
c.credential,
|
|
395
|
-
metadata?.fields ?? null,
|
|
396
|
-
);
|
|
397
|
-
credential = {
|
|
398
|
-
label: c.credential.label,
|
|
399
|
-
...(c.credential.description
|
|
400
|
-
? { description: c.credential.description }
|
|
401
|
-
: {}),
|
|
402
|
-
...(c.credential.placeholder
|
|
403
|
-
? { placeholder: c.credential.placeholder }
|
|
404
|
-
: {}),
|
|
405
|
-
...(fields?.length ? { fields } : {}),
|
|
406
|
-
configured: shape.state === "valid",
|
|
407
|
-
removable: Boolean(metadata),
|
|
408
|
-
...(metadata
|
|
409
|
-
? {
|
|
410
|
-
lastFour: metadata.lastFour,
|
|
411
|
-
updatedAt: metadata.updatedAt,
|
|
412
|
-
}
|
|
413
|
-
: {}),
|
|
414
|
-
testable:
|
|
415
|
-
testRule.mode !== null && shape.state !== "mismatch",
|
|
416
|
-
...(shape.state === "mismatch"
|
|
417
|
-
? { error: shape.message }
|
|
418
|
-
: {}),
|
|
419
|
-
// A dropped field leaves its secret in the vault, and the field
|
|
420
|
-
// list below only renders fields the connector still declares —
|
|
421
|
-
// so without this line there is nowhere an operator could see it.
|
|
422
|
-
...(shape.state === "valid" && shape.undeclared.length
|
|
423
|
-
? {
|
|
424
|
-
notice: describeUndeclaredCredentialFields(
|
|
425
|
-
shape.undeclared,
|
|
426
|
-
),
|
|
427
|
-
}
|
|
428
|
-
: {}),
|
|
429
|
-
};
|
|
430
|
-
} catch {
|
|
431
|
-
const fields = credentialFields();
|
|
432
|
-
credential = {
|
|
433
|
-
label: c.credential.label,
|
|
434
|
-
...(c.credential.description
|
|
435
|
-
? { description: c.credential.description }
|
|
436
|
-
: {}),
|
|
437
|
-
...(c.credential.placeholder
|
|
438
|
-
? { placeholder: c.credential.placeholder }
|
|
439
|
-
: {}),
|
|
440
|
-
...(fields?.length ? { fields } : {}),
|
|
441
|
-
configured: false,
|
|
442
|
-
removable: true,
|
|
443
|
-
testable: testRule.mode !== null,
|
|
444
|
-
error: "Stored credential could not be read.",
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
id: c.id,
|
|
450
|
-
...(c.title ? { title: c.title } : {}),
|
|
451
|
-
...(c.description !== undefined
|
|
452
|
-
? { description: c.description }
|
|
453
|
-
: {}),
|
|
454
|
-
status: status.state,
|
|
455
|
-
...(status.message ? { message: status.message } : {}),
|
|
456
|
-
...(isSafeHttpUrl(status.authorizationUrl)
|
|
457
|
-
? { authorizationUrl: status.authorizationUrl }
|
|
458
|
-
: {}),
|
|
459
|
-
toolCount: tools.length,
|
|
460
|
-
tools,
|
|
461
|
-
...(c.disconnectAuth && c.startAuth ? { oauth: true } : {}),
|
|
462
|
-
...(credential ? { credential } : {}),
|
|
463
|
-
};
|
|
464
|
-
},
|
|
465
|
-
);
|
|
466
|
-
await mapSettledWithConcurrency(
|
|
467
|
-
connectorSet,
|
|
468
|
-
concurrency,
|
|
469
|
-
(connector) =>
|
|
470
|
-
closeConnectorScope(
|
|
471
|
-
connector,
|
|
472
|
-
registry.contextFor(connector.id, baseUrl, requestScope),
|
|
473
|
-
defer,
|
|
474
|
-
),
|
|
475
|
-
);
|
|
476
|
-
const connectors = settled.map((result) => {
|
|
477
|
-
if (result.status === "rejected") throw result.reason;
|
|
478
|
-
return result.value;
|
|
479
|
-
});
|
|
480
|
-
return {
|
|
481
|
-
serverInfo,
|
|
482
|
-
connectaVersion: CONNECTA_VERSION,
|
|
483
|
-
connectors,
|
|
484
|
-
activityEnabled,
|
|
485
|
-
credentialManagement,
|
|
486
|
-
accessTokenManagement,
|
|
487
|
-
oauthManagement,
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function escapeHtmlAttr(value: string): string {
|
|
492
|
-
return value
|
|
493
|
-
.replaceAll("&", "&")
|
|
494
|
-
.replaceAll('"', """)
|
|
495
|
-
.replaceAll("<", "<")
|
|
496
|
-
.replaceAll(">", ">");
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function jsonForInlineScript(value: unknown): string {
|
|
500
|
-
return JSON.stringify(value)
|
|
501
|
-
.replaceAll("<", "\\u003c")
|
|
502
|
-
.replaceAll(">", "\\u003e")
|
|
503
|
-
.replaceAll("&", "\\u0026");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Every operator page serves this same data-free shell. Connector, credential,
|
|
508
|
-
* and activity data arrives only through the authenticated `/ui/*` APIs.
|
|
509
|
-
*/
|
|
510
|
-
export function renderUiHtml(
|
|
511
|
-
uiAuth?: UiAuthConfig,
|
|
512
|
-
mcpUrl = "/mcp",
|
|
513
|
-
branding?: ConnectaBranding,
|
|
514
|
-
nonce?: string,
|
|
515
|
-
page: OperatorPage = "connections",
|
|
516
|
-
): string {
|
|
517
|
-
const clerk = uiAuth?.kind === "clerk" ? uiAuth : undefined;
|
|
518
|
-
// The Clerk loader's origin. A value that fails the gate is dropped rather
|
|
519
|
-
// than escaped into the page: the loader tag is simply not emitted, the gate
|
|
520
|
-
// reports that Clerk could not load, and the rest of the shell still renders —
|
|
521
|
-
// the same fallback-and-warn posture the branding URLs take, with the drop
|
|
522
|
-
// named in a startup warning (see `droppedUiAuthUrls`).
|
|
523
|
-
const clerkScriptOrigin =
|
|
524
|
-
clerk && isSafeHttpsUrl(clerk.frontendApiUrl)
|
|
525
|
-
? clerk.frontendApiUrl
|
|
526
|
-
: undefined;
|
|
527
|
-
// Enumerated field by field, because this object is serialized into the page's
|
|
528
|
-
// inline script: a rejected frontendApiUrl must not reach the document through
|
|
529
|
-
// `AUTH` after being kept out of the `<script src>`, and a rejected
|
|
530
|
-
// signInUrl/signUpUrl — which `AUTH` is the only path into the page for — must
|
|
531
|
-
// not reach it at all. Dropping one leaves the key absent, so `Clerk.load`
|
|
532
|
-
// falls back to its own default the same way it does for an unset value.
|
|
533
|
-
const auth = clerk
|
|
534
|
-
? {
|
|
535
|
-
kind: clerk.kind,
|
|
536
|
-
publishableKey: clerk.publishableKey,
|
|
537
|
-
...(clerkScriptOrigin ? { frontendApiUrl: clerkScriptOrigin } : {}),
|
|
538
|
-
...(isSafeHttpsUrl(clerk.signInUrl)
|
|
539
|
-
? { signInUrl: clerk.signInUrl }
|
|
540
|
-
: {}),
|
|
541
|
-
...(isSafeHttpsUrl(clerk.signUpUrl)
|
|
542
|
-
? { signUpUrl: clerk.signUpUrl }
|
|
543
|
-
: {}),
|
|
544
|
-
}
|
|
545
|
-
: (uiAuth ?? { kind: "bearer" as const });
|
|
546
|
-
const brand = resolveBranding(branding);
|
|
547
|
-
const title = operatorPageTitle(page, brand.pageTitle);
|
|
548
|
-
// When an operator shell ships a nonce-based CSP, every script it emits must
|
|
549
|
-
// carry that nonce to run; without a nonce the markup is unchanged.
|
|
550
|
-
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
551
|
-
// Top-left corner. With an owner set it reads "<owner> <product>"; without
|
|
552
|
-
// one the product label stands alone. Either half links out when the
|
|
553
|
-
// matching URL is configured.
|
|
554
|
-
const owner = brand.ownerName
|
|
555
|
-
? brand.ownerUrl
|
|
556
|
-
? `<a class="brand navlink" href="${escapeHtmlAttr(brand.ownerUrl)}">${escapeHtmlAttr(brand.ownerName)}</a>`
|
|
557
|
-
: `<span class="brand">${escapeHtmlAttr(brand.ownerName)}</span>`
|
|
558
|
-
: brand.productUrl
|
|
559
|
-
? `<a class="brand navlink" href="${escapeHtmlAttr(brand.productUrl)}">${escapeHtmlAttr(brand.productName)}</a>`
|
|
560
|
-
: `<span class="brand">${escapeHtmlAttr(brand.productName)}</span>`;
|
|
561
|
-
const product = brand.ownerName
|
|
562
|
-
? brand.productUrl
|
|
563
|
-
? `<a class="product navlink" href="${escapeHtmlAttr(brand.productUrl)}">${escapeHtmlAttr(brand.productName)}</a>`
|
|
564
|
-
: `<span class="product">${escapeHtmlAttr(brand.productName)}</span>`
|
|
565
|
-
: "";
|
|
566
|
-
const clerkScript =
|
|
567
|
-
clerk && clerkScriptOrigin
|
|
568
|
-
? `<script${nonceAttr} defer crossorigin="anonymous" data-clerk-publishable-key="${escapeHtmlAttr(clerk.publishableKey)}" src="${escapeHtmlAttr(clerkScriptOrigin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
|
|
569
|
-
: "";
|
|
570
|
-
|
|
571
|
-
return `<!doctype html>
|
|
572
|
-
<html lang="en">
|
|
573
|
-
<head>
|
|
574
|
-
<meta charset="utf-8">
|
|
575
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
576
|
-
<meta name="theme-color" content="${escapeHtmlAttr(brand.themeColor)}">
|
|
577
|
-
<meta name="description" content="${escapeHtmlAttr(brand.description)}">
|
|
578
|
-
<link rel="icon" href="${escapeHtmlAttr(brand.faviconHref)}" type="image/svg+xml">
|
|
579
|
-
<link rel="shortcut icon" href="/favicon.ico">
|
|
580
|
-
<title>${escapeHtmlAttr(title)}</title>
|
|
581
|
-
${clerkScript}
|
|
582
|
-
<style>${OPERATOR_UI_CSS}</style>
|
|
583
|
-
</head>
|
|
584
|
-
<body>
|
|
585
|
-
<a class="skip-link" href="#operatorContent">Skip to operator page</a>
|
|
586
|
-
<header class="masthead shell pgrid">
|
|
587
|
-
${owner}
|
|
588
|
-
<div class="mast-nav">
|
|
589
|
-
${product}
|
|
590
|
-
<div id="appNav" class="mast-actions hidden">
|
|
591
|
-
<nav class="page-nav" aria-label="Operator pages">
|
|
592
|
-
<a id="connectionsNav" class="navlink" href="/"
|
|
593
|
-
data-operator-page="connections"${page === "connections" ? ' aria-current="page"' : ""}>Connections</a>
|
|
594
|
-
<a id="credentialsNav" class="navlink hidden" href="/credentials"
|
|
595
|
-
data-operator-page="credentials"${page === "credentials" ? ' aria-current="page"' : ""}>Credentials</a>
|
|
596
|
-
<a id="tokensNav" class="navlink hidden" href="/tokens"
|
|
597
|
-
data-operator-page="tokens"${page === "tokens" ? ' aria-current="page"' : ""}>Access tokens</a>
|
|
598
|
-
<a id="activityNav" class="navlink hidden" href="/activity"
|
|
599
|
-
data-operator-page="activity"${page === "activity" ? ' aria-current="page"' : ""}>Activity</a>
|
|
600
|
-
</nav>
|
|
601
|
-
<div class="session-actions" aria-label="Session actions">
|
|
602
|
-
<button id="change" class="navlink hidden" type="button">Change token</button>
|
|
603
|
-
<button id="signout" class="navlink hidden" type="button">Sign out</button>
|
|
604
|
-
</div>
|
|
605
|
-
</div>
|
|
606
|
-
</div>
|
|
607
|
-
</header>
|
|
608
|
-
|
|
609
|
-
<main id="operatorContent" class="page shell" tabindex="-1">
|
|
610
|
-
<section id="gate" class="hidden">
|
|
611
|
-
<div class="lead pgrid">
|
|
612
|
-
<h1 id="gateHeading" class="pcap" tabindex="-1">${OPERATOR_PAGE_LABELS[page]}</h1>
|
|
613
|
-
<div class="pbody lead-copy">
|
|
614
|
-
<p>${escapeHtmlAttr(brand.description)}</p>
|
|
615
|
-
<p id="gateCopy" class="meta"></p>
|
|
616
|
-
<div id="tokenGate" class="row gate-actions hidden">
|
|
617
|
-
<input id="token" type="password" placeholder="Bearer token" autocomplete="off"
|
|
618
|
-
aria-label="Bearer token">
|
|
619
|
-
<button id="save" class="linklike" type="button">Open operator pages</button>
|
|
620
|
-
</div>
|
|
621
|
-
<div id="clerkGate" class="actions gate-actions hidden">
|
|
622
|
-
<button id="signin" class="linklike" type="button">Team sign in</button>
|
|
623
|
-
<button id="gateSignout" class="linklike hidden" type="button">Sign out</button>
|
|
624
|
-
</div>
|
|
625
|
-
<p id="err" role="alert"></p>
|
|
626
|
-
</div>
|
|
627
|
-
</div>
|
|
628
|
-
</section>
|
|
629
|
-
|
|
630
|
-
<div id="app" class="hidden">
|
|
631
|
-
<section id="connectionsView"${page === "connections" ? "" : ' class="hidden"'}>
|
|
632
|
-
<div class="lead pgrid">
|
|
633
|
-
<h1 id="connectionsHeading" class="pcap" tabindex="-1">Connections</h1>
|
|
634
|
-
<div class="pbody lead-copy">
|
|
635
|
-
<p>Use this endpoint to give an MCP client access to the tools below.</p>
|
|
636
|
-
<div class="endpoint">
|
|
637
|
-
<div class="endpoint-row">
|
|
638
|
-
<code id="mcpUrl" class="mono"></code>
|
|
639
|
-
<button id="copyMcpUrl" class="linklike" type="button">Copy URL</button>
|
|
640
|
-
</div>
|
|
641
|
-
</div>
|
|
642
|
-
<p class="cap" id="serverInfo">${escapeHtmlAttr(brand.productName)} operator</p>
|
|
643
|
-
<p id="oauthNotice" class="meta" role="status" aria-live="polite" tabindex="-1"></p>
|
|
644
|
-
</div>
|
|
645
|
-
</div>
|
|
646
|
-
<section class="section pgrid" aria-labelledby="connectorLedgerHeading">
|
|
647
|
-
<h2 class="pcap" id="connectorLedgerHeading">Connectors</h2>
|
|
648
|
-
<div class="pbody">
|
|
649
|
-
<div class="row toolbar">
|
|
650
|
-
<input id="filter" type="search" placeholder="Filter connectors or tools…"
|
|
651
|
-
aria-label="Filter connectors or tools">
|
|
652
|
-
</div>
|
|
653
|
-
<div id="list" class="connector-tools" aria-busy="false"></div>
|
|
654
|
-
</div>
|
|
655
|
-
</section>
|
|
656
|
-
</section>
|
|
657
|
-
|
|
658
|
-
<section id="credentialsView"${page === "credentials" ? "" : ' class="hidden"'}>
|
|
659
|
-
<div class="lead pgrid">
|
|
660
|
-
<h1 id="credentialsHeading" class="pcap" tabindex="-1">Credentials</h1>
|
|
661
|
-
<div class="pbody">
|
|
662
|
-
<p class="activity-copy">Rotate operator-managed connector credentials. Stored values are never returned or displayed.</p>
|
|
663
|
-
<p id="credentialNotice" class="meta" role="status" aria-live="polite"
|
|
664
|
-
tabindex="-1"></p>
|
|
665
|
-
<div id="credentialUnavailable" class="unavailable hidden"></div>
|
|
666
|
-
<div id="credentialList" class="credential-ledger" aria-busy="false"></div>
|
|
667
|
-
</div>
|
|
668
|
-
</div>
|
|
669
|
-
</section>
|
|
670
|
-
|
|
671
|
-
<section id="tokensView"${page === "tokens" ? "" : ' class="hidden"'}>
|
|
672
|
-
<div class="lead pgrid">
|
|
673
|
-
<h1 id="tokensHeading" class="pcap" tabindex="-1">Access tokens</h1>
|
|
674
|
-
<div class="pbody">
|
|
675
|
-
<p class="activity-copy">Create named Bearer tokens for MCP clients. Each secret is shown once; revoke it when that client should lose access.</p>
|
|
676
|
-
<p id="tokenNotice" class="meta" role="status" aria-live="polite"
|
|
677
|
-
tabindex="-1"></p>
|
|
678
|
-
<div id="tokenUnavailable" class="unavailable hidden"></div>
|
|
679
|
-
<div id="tokenAvailable" class="hidden">
|
|
680
|
-
<form id="tokenCreateForm" class="token-create">
|
|
681
|
-
<label for="tokenName">Client name</label>
|
|
682
|
-
<div class="row">
|
|
683
|
-
<input id="tokenName" type="text" maxlength="80"
|
|
684
|
-
placeholder="Claude desktop, ChatGPT production…"
|
|
685
|
-
autocomplete="off">
|
|
686
|
-
<button id="createToken" class="linklike" type="submit">Create token</button>
|
|
687
|
-
</div>
|
|
688
|
-
</form>
|
|
689
|
-
<section id="tokenReveal" class="token-reveal hidden"
|
|
690
|
-
aria-labelledby="tokenRevealHeading">
|
|
691
|
-
<div class="token-reveal-head">
|
|
692
|
-
<h2 id="tokenRevealHeading" tabindex="-1">Copy this token now</h2>
|
|
693
|
-
<span class="cap">Shown once</span>
|
|
694
|
-
</div>
|
|
695
|
-
<p class="meta">Store it in the MCP client before leaving this page. It cannot be displayed again.</p>
|
|
696
|
-
<div class="endpoint-row token-secret">
|
|
697
|
-
<code id="createdToken" class="mono"></code>
|
|
698
|
-
<button id="copyCreatedToken" class="linklike" type="button">Copy token</button>
|
|
699
|
-
</div>
|
|
700
|
-
<button id="dismissCreatedToken" class="linklike" type="button">I stored it</button>
|
|
701
|
-
</section>
|
|
702
|
-
<div id="tokenList" class="token-ledger" aria-busy="false"></div>
|
|
703
|
-
</div>
|
|
704
|
-
</div>
|
|
705
|
-
</div>
|
|
706
|
-
</section>
|
|
707
|
-
|
|
708
|
-
<section id="activityView"${page === "activity" ? "" : ' class="hidden"'}>
|
|
709
|
-
<div class="lead pgrid">
|
|
710
|
-
<h1 id="activityHeading" class="pcap" tabindex="-1">Activity</h1>
|
|
711
|
-
<div class="pbody">
|
|
712
|
-
<p class="activity-copy" id="activitySummary">Arguments and results are never stored.</p>
|
|
713
|
-
<div id="activityUnavailable" class="unavailable hidden">
|
|
714
|
-
Activity history is not configured. Add an <span class="mono">activity.store</span>
|
|
715
|
-
with a list reader to enable this page.
|
|
716
|
-
</div>
|
|
717
|
-
<div id="activityAvailable">
|
|
718
|
-
<div class="row activity-controls">
|
|
719
|
-
<input id="activitySearch" type="search"
|
|
720
|
-
placeholder="Search user, tool, or outcome…"
|
|
721
|
-
aria-label="Search loaded activity">
|
|
722
|
-
<button id="refreshActivity" class="linklike" type="button">Refresh</button>
|
|
723
|
-
</div>
|
|
724
|
-
<p id="activityNotice" class="meta" role="status" aria-live="polite"></p>
|
|
725
|
-
<div id="activityList" class="activity-ledger" aria-busy="false"></div>
|
|
726
|
-
<button id="moreActivity" class="linklike activity-more hidden" type="button">Load older</button>
|
|
727
|
-
</div>
|
|
728
|
-
</div>
|
|
729
|
-
</div>
|
|
730
|
-
</section>
|
|
731
|
-
</div>
|
|
732
|
-
</main>
|
|
733
|
-
|
|
734
|
-
<script${nonceAttr}>
|
|
735
|
-
const AUTH = ${jsonForInlineScript(auth)};
|
|
736
|
-
const MCP_URL = ${jsonForInlineScript(mcpUrl)};
|
|
737
|
-
const INITIAL_PAGE = ${jsonForInlineScript(page)};
|
|
738
|
-
const TITLE_SUFFIX = ${jsonForInlineScript(brand.pageTitle)};
|
|
739
|
-
const PRODUCT_NAME = ${stringForInlineScript(brand.productName)};
|
|
740
|
-
const PRODUCT_OPERATOR_LABEL = ${stringForInlineScript(brand.productName + " operator")};
|
|
741
|
-
${OPERATOR_UI_SCRIPT}</script>
|
|
742
|
-
</body>
|
|
743
|
-
</html>`;
|
|
744
|
-
}
|