@zackbart/connecta 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +18 -10
  4. package/dist/activity-friction.d.ts +3 -0
  5. package/dist/activity-friction.js +19 -0
  6. package/dist/activity.d.ts +11 -2
  7. package/dist/activity.js +15 -19
  8. package/dist/auth/downstream-oauth.d.ts +2 -1
  9. package/dist/auth/downstream-oauth.js +10 -1
  10. package/dist/branding.d.ts +67 -0
  11. package/dist/branding.js +176 -0
  12. package/dist/connectors/remote-mcp.js +3 -5
  13. package/dist/credential-contract.d.ts +24 -0
  14. package/dist/credential-contract.js +1 -0
  15. package/dist/credential-rules.d.ts +85 -0
  16. package/dist/credential-rules.js +107 -0
  17. package/dist/credentials.d.ts +4 -100
  18. package/dist/credentials.js +3 -107
  19. package/dist/index.d.ts +22 -55
  20. package/dist/index.js +30 -58
  21. package/dist/invocation.js +2 -3
  22. package/dist/meta-tools.d.ts +4 -0
  23. package/dist/meta-tools.js +8 -4
  24. package/dist/module-contracts.d.ts +19 -0
  25. package/dist/module-contracts.js +1 -0
  26. package/dist/operator-ui/generated.js +2 -2
  27. package/dist/operator-ui/model.d.ts +6 -3
  28. package/dist/operator-ui/view.d.ts +2 -18
  29. package/dist/operator-ui/view.js +3 -20
  30. package/dist/registry.d.ts +4 -1
  31. package/dist/registry.js +4 -6
  32. package/dist/routes/activity.js +1 -1
  33. package/dist/routes/credentials.js +5 -2
  34. package/dist/routes/mcp.js +7 -3
  35. package/dist/routes/oauth-management.d.ts +2 -0
  36. package/dist/routes/oauth-management.js +108 -0
  37. package/dist/routes/oauth.d.ts +0 -1
  38. package/dist/routes/oauth.js +21 -121
  39. package/dist/routes/shared.d.ts +19 -17
  40. package/dist/routes/shared.js +48 -44
  41. package/dist/routes/ui.js +36 -33
  42. package/dist/server.js +6 -26
  43. package/dist/types.d.ts +2 -0
  44. package/dist/ui.d.ts +15 -70
  45. package/dist/ui.js +176 -317
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/documentation/architecture.md +26 -17
  49. package/documentation/auth.md +61 -106
  50. package/documentation/cloudflare.md +1 -1
  51. package/documentation/connectors.md +1 -1
  52. package/documentation/linear.md +1 -1
  53. package/documentation/meta-tools.md +6 -4
  54. package/documentation/mixpanel.md +1 -1
  55. package/documentation/notion.md +2 -2
  56. package/documentation/operations.md +12 -14
  57. package/documentation/operator-ui.md +82 -104
  58. package/documentation/optional-modules-upgrade.md +243 -0
  59. package/documentation/provider-conventions.md +5 -3
  60. package/documentation/revenuecat.md +1 -1
  61. package/documentation/storage-and-credentials.md +59 -40
  62. package/documentation/stripe.md +1 -1
  63. package/documentation/upgrading.md +29 -4
  64. package/ethos.md +22 -30
  65. package/examples/worker/AGENTS.md +3 -1
  66. package/examples/worker/README.md +68 -84
  67. package/examples/worker/src/d1-activity.ts +1 -1
  68. package/examples/worker/src/index.ts +11 -6
  69. package/package.json +18 -2
  70. package/templates/node/AGENTS.md +8 -6
  71. package/templates/node/README.md +56 -67
  72. package/templates/node/package.json +1 -1
  73. package/templates/node/src/file-activity.ts +1 -1
  74. package/templates/node/src/index.ts +11 -12
  75. package/dist/access-tokens.d.ts +0 -31
  76. package/dist/access-tokens.js +0 -236
  77. package/dist/routes/access-tokens.d.ts +0 -6
  78. package/dist/routes/access-tokens.js +0 -83
@@ -1,5 +1,4 @@
1
1
  import { identityStorageKey, validIdentityReference } from "../identity.js";
2
- import { operatorPageForPath } from "../ui.js";
3
2
  export { msg } from "../errors.js";
4
3
  export function privateJson(body, init = {}) {
5
4
  const headers = new Headers(init.headers);
@@ -36,7 +35,9 @@ export async function authorize(request, baseUrl, auth, runtimeContext, identity
36
35
  const identity = { actor, interactive: false };
37
36
  let connectorIds = "all";
38
37
  try {
39
- connectorIds = await identityConfig?.connectorAccess?.(identity) ?? "all";
38
+ connectorIds = identityConfig?.connectorAccess ? await identityConfig.connectorAccess(identity) : "all";
39
+ if (connectorIds !== "all" && (!Array.isArray(connectorIds) || !connectorIds.every(id => typeof id === "string" && /^[a-z0-9_-]+$/.test(id))))
40
+ throw new Error("invalid connector permission");
40
41
  }
41
42
  catch {
42
43
  return {
@@ -44,7 +45,7 @@ export async function authorize(request, baseUrl, auth, runtimeContext, identity
44
45
  response: privateJson({ error: "identity access resolution failed" }, { status: 403 }),
45
46
  };
46
47
  }
47
- return { ok: true, actor, identity, connectorIds, operator: false };
48
+ return { ok: true, actor, identity, connectorIds, operator: false, credentialAdministration: "none", personalConnection: "none" };
48
49
  }
49
50
  let lastResponse = null;
50
51
  for (const provider of auth) {
@@ -74,14 +75,26 @@ export async function authorize(request, baseUrl, auth, runtimeContext, identity
74
75
  interactive,
75
76
  };
76
77
  let operator = interactive;
78
+ let credentialAdministration = "none";
79
+ let personalConnection = "none";
77
80
  let connectorIds = "all";
78
81
  try {
79
- if (identityConfig?.operatorAccess) {
82
+ if (identityConfig?.activityAccess) {
80
83
  operator = interactive && principal
81
- ? await identityConfig.operatorAccess(principal)
84
+ ? await identityConfig.activityAccess(principal)
82
85
  : false;
83
86
  }
84
- connectorIds = await identityConfig?.connectorAccess?.(identity) ?? "all";
87
+ connectorIds = identityConfig?.connectorAccess ? await identityConfig.connectorAccess(identity) : "all";
88
+ if (interactive) {
89
+ credentialAdministration = identityConfig?.credentialAdministration ? await identityConfig.credentialAdministration(identity) : "none";
90
+ personalConnection = principal && identityConfig?.personalConnection ? await identityConfig.personalConnection(identity) : "none";
91
+ }
92
+ if (typeof operator !== "boolean")
93
+ throw new Error("invalid activity permission");
94
+ for (const permission of [connectorIds, credentialAdministration, personalConnection]) {
95
+ if (permission !== "all" && permission !== "none" && (!Array.isArray(permission) || !permission.every(id => typeof id === "string" && /^[a-z0-9_-]+$/.test(id))))
96
+ throw new Error("invalid identity permission");
97
+ }
85
98
  }
86
99
  catch {
87
100
  return {
@@ -100,6 +113,8 @@ export async function authorize(request, baseUrl, auth, runtimeContext, identity
100
113
  ? { principalKey: await identityStorageKey(principal) }
101
114
  : {}),
102
115
  connectorIds,
116
+ credentialAdministration,
117
+ personalConnection,
103
118
  operator,
104
119
  ...(operator ? { uiAdminEligible: true } : {}),
105
120
  };
@@ -118,34 +133,6 @@ export async function authorize(request, baseUrl, auth, runtimeContext, identity
118
133
  }),
119
134
  };
120
135
  }
121
- export async function authorizeUiAdmin(request, baseUrl, auth, purpose = "credential management", runtimeContext, identityConfig) {
122
- // Operator mutation is intentionally narrower than /mcp and /ui/data: only
123
- // an interactive provider may admit it. A static bearer token is useful
124
- // for headless tool calls but must not become a deployment-admin key.
125
- //
126
- // Every interactive provider gets a turn, the way the /mcp gate does.
127
- // Stopping at the first would make admission depend on config order: a failed gate or
128
- // missing user may simply mean a later provider is the one meant to admit.
129
- // The last refusal is returned if none do.
130
- const authz = await authorizeUiIdentity(request, baseUrl, auth, purpose, runtimeContext, identityConfig);
131
- if (!authz.ok)
132
- return authz;
133
- if (!authz.operator || !authz.actor.id) {
134
- return {
135
- ok: false,
136
- response: privateJson({ error: `${purpose} requires operator access` }, { status: 403 }),
137
- };
138
- }
139
- return {
140
- ok: true,
141
- userId: authz.identity.principal?.id ?? authz.actor.id,
142
- ...(authz.identity.principal
143
- ? { principal: authz.identity.principal }
144
- : {}),
145
- ...(authz.principalKey ? { principalKey: authz.principalKey } : {}),
146
- connectorIds: authz.connectorIds,
147
- };
148
- }
149
136
  export async function authorizeUiIdentity(request, baseUrl, auth, purpose, runtimeContext, identityConfig) {
150
137
  const providers = auth.filter((candidate) => candidate.interactiveOperator);
151
138
  if (providers.length === 0) {
@@ -181,25 +168,42 @@ export function isSameOrigin(request, baseUrl) {
181
168
  return false;
182
169
  }
183
170
  }
184
- export function withSecurityHeaders(response, requestUrl, path) {
171
+ export function withSecurityHeaders(response, requestUrl, _path) {
185
172
  const headers = new Headers(response.headers);
186
173
  headers.set("X-Content-Type-Options", "nosniff");
187
174
  headers.set("Referrer-Policy", "no-referrer");
188
175
  if (requestUrl.protocol === "https:") {
189
176
  headers.set("Strict-Transport-Security", "max-age=31536000");
190
177
  }
191
- if (operatorPageForPath(path) || path === "/ui") {
192
- // Operator HTML responses ship their own nonce-based script CSP (which
193
- // already includes frame-ancestors 'none'); only fall back to the
194
- // framing-only directive when no CSP is present (for example redirects).
195
- if (!headers.has("Content-Security-Policy")) {
196
- headers.set("Content-Security-Policy", "frame-ancestors 'none'");
197
- }
198
- headers.set("X-Frame-Options", "DENY");
199
- }
200
178
  return new Response(response.body, {
201
179
  status: response.status,
202
180
  statusText: response.statusText,
203
181
  headers,
204
182
  });
205
183
  }
184
+ /** Unknown configured ids refuse the complete view, including management rights. */
185
+ export function validateAuthPermissions(authz, registry) {
186
+ for (const value of [
187
+ authz.connectorIds,
188
+ authz.credentialAdministration,
189
+ authz.personalConnection,
190
+ ]) {
191
+ if (value === "all" || value === "none")
192
+ continue;
193
+ if (!Array.isArray(value) || value.some(id => !registry.getConnector(id))) {
194
+ throw new Error("invalid identity permission connector ids");
195
+ }
196
+ }
197
+ }
198
+ export function mayManageConnector(authz, connector) {
199
+ if (!authz.identity.interactive)
200
+ return false;
201
+ if (authz.connectorIds !== "all" && !authz.connectorIds.includes(connector.id)) {
202
+ return false;
203
+ }
204
+ const permission = connector.authScope === "personal"
205
+ ? authz.personalConnection
206
+ : authz.credentialAdministration;
207
+ return permission === "all" ||
208
+ (permission !== "none" && permission.includes(connector.id));
209
+ }
package/dist/routes/ui.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { CONNECTA_VERSION } from "../version.js";
1
2
  import { CONNECTA_FAVICON_ICO } from "../favicon.js";
2
- import { buildUiData, CONNECTA_FAVICON_SVG, credentialManagementCapability, operatorPageForPath, renderUiHtml, } from "../ui.js";
3
- import { authorize, msg, privateJson, } from "./shared.js";
3
+ import { buildUiData, CONNECTA_FAVICON_SVG, operatorPageForPath, renderUiHtml, } from "../ui.js";
4
+ import { authorize, mayManageConnector, validateAuthPermissions, msg, privateJson, } from "./shared.js";
4
5
  /**
5
6
  * Headers that make an operator-supplied favicon body inert on this origin.
6
7
  * The SVG route is the sharp one: `image/svg+xml` is an *active* content type,
@@ -67,7 +68,7 @@ export async function routeUi(context) {
67
68
  });
68
69
  }
69
70
  const operatorPage = operatorPageForPath(path);
70
- if (operatorPage) {
71
+ if (operatorPage && (operatorPage !== "activity" || opts.activity?.list)) {
71
72
  if (request.method !== "GET" && request.method !== "HEAD") {
72
73
  return privateJson({ error: "method not allowed" }, { status: 405 });
73
74
  }
@@ -97,13 +98,17 @@ export async function routeUi(context) {
97
98
  },
98
99
  });
99
100
  }
100
- if (path !== "/ui/data")
101
+ const detail = /^\/ui\/connectors\/([a-z0-9_-]+)$/.exec(path);
102
+ if (path !== "/ui/data" && !detail)
101
103
  return null;
104
+ if (request.method !== "GET")
105
+ return privateJson({ error: "method not allowed" }, { status: 405 });
102
106
  const authz = await authorize(request, baseUrl, opts.auth, runtimeContext, opts.identity);
103
107
  if (!authz.ok)
104
108
  return authz.response;
105
109
  let registry;
106
110
  try {
111
+ validateAuthPermissions(authz, opts.registry);
107
112
  registry = opts.registry.scoped({
108
113
  connectorIds: authz.connectorIds,
109
114
  ...(authz.subjectKey ? { subjectKey: authz.subjectKey } : {}),
@@ -113,33 +118,31 @@ export async function routeUi(context) {
113
118
  catch (error) {
114
119
  return privateJson({ error: msg(error) }, { status: 403 });
115
120
  }
116
- const eligibleOperator = authz.uiAdminEligible === true;
117
- const interactiveManager = authz.identity.interactive;
118
- const personalManager = Boolean(interactiveManager && authz.principalKey);
119
- const visibleConnectors = registry.listConnectors();
120
- const hasManageableCredentialSlot = visibleConnectors.some((connector) => Boolean(connector.credential) &&
121
- (connector.authScope !== "personal" || personalManager));
122
- const credentialManagement = interactiveManager && hasManageableCredentialSlot
123
- ? opts.credentialVault
124
- ? "available"
125
- : "vault_not_configured"
126
- : credentialManagementCapability({
127
- eligibleOperator: interactiveManager,
128
- hasCredentialSlots: visibleConnectors.some((connector) => Boolean(connector.credential)),
129
- hasCredentialVault: Boolean(opts.credentialVault),
130
- });
131
- // As with connector credentials, a Bearer-authenticated observer learns
132
- // only that an interactive operator is required, not whether this deployment has opted into
133
- // token issuance. Configuration topology is operator data.
134
- const accessTokenManagement = !eligibleOperator
135
- ? "requires_operator"
136
- : opts.accessTokens
137
- ? "available"
138
- : "not_configured";
139
- const data = await buildUiData(registry, baseUrl, opts.serverInfo,
140
- // A static headless bearer may read connector health, but only an
141
- // interactive human receives credential metadata for visible connectors.
142
- interactiveManager ? opts.credentialVault : undefined, Boolean(opts.activity?.list) &&
143
- (!opts.identity?.operatorAccess || eligibleOperator), credentialManagement, defer, interactiveManager, opts.discoveryConcurrency, accessTokenManagement, personalManager ? authz.principalKey : undefined);
144
- return privateJson(data);
121
+ const visible = registry.listConnectors();
122
+ const mayManage = (id) => { const connector = registry.getConnector(id); return Boolean(connector && mayManageConnector(authz, connector)); };
123
+ const permissions = (connector) => ({
124
+ use: true,
125
+ manageSharedAuth: connector.authScope !== "personal" && mayManage(connector.id),
126
+ connectPersonal: connector.authScope === "personal" && mayManage(connector.id),
127
+ });
128
+ const activityEnabled = Boolean(opts.activity?.list) && authz.operator;
129
+ const credentialManagement = visible.some(c => c.credential && mayManage(c.id))
130
+ ? opts.credentialVault ? "available" : "vault_not_configured"
131
+ : authz.identity.interactive && !visible.some(c => c.credential) ? "no_slots" : "requires_operator";
132
+ if (detail) {
133
+ const connector = registry.getConnector(detail[1]);
134
+ if (!connector)
135
+ return privateJson({ error: "unknown connector" }, { status: 404 });
136
+ const one = opts.registry.scoped({ connectorIds: [connector.id], ...(authz.subjectKey ? { subjectKey: authz.subjectKey } : {}), ...(authz.principalKey ? { principalKey: authz.principalKey } : {}) });
137
+ const data = await buildUiData(one, baseUrl, opts.serverInfo, opts.credentialVault, activityEnabled, credentialManagement, defer, false, 1, authz.principalKey, { mayManage, timeoutMs: opts.probeTimeoutMs ?? 30_000, signal: request.signal });
138
+ return privateJson({ ...data.connectors[0], permissions: permissions(connector) });
139
+ }
140
+ return privateJson({
141
+ serverInfo: opts.serverInfo,
142
+ connectaVersion: CONNECTA_VERSION,
143
+ activityEnabled,
144
+ credentialManagement,
145
+ oauthManagement: visible.some(c => mayManage(c.id)),
146
+ connectors: visible.map(c => ({ id: c.id, ...(c.title ? { title: c.title } : {}), ...(c.description ? { description: c.description } : {}), authScope: c.authScope ?? "shared", status: "loading", toolCount: 0, tools: [], oauth: Boolean(c.startAuth && c.disconnectAuth), permissions: permissions(c) })),
147
+ });
145
148
  }
package/dist/server.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import { isAdmittingExecutor } from "./executor-admission.js";
2
- import { routeAccessTokens } from "./routes/access-tokens.js";
3
- import { routeActivity } from "./routes/activity.js";
4
- import { routeCredentials } from "./routes/credentials.js";
5
2
  import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
6
- import { routeOAuthCallback, routeOAuthManagement, } from "./routes/oauth.js";
3
+ import { routeOAuthCallback, } from "./routes/oauth.js";
7
4
  import { withSecurityHeaders, } from "./routes/shared.js";
8
- import { routeUi } from "./routes/ui.js";
9
5
  /**
10
6
  * Build the Web-standard fetch handler.
11
7
  *
@@ -59,15 +55,9 @@ export function createFetchHandler(opts) {
59
55
  };
60
56
  const route = async () => {
61
57
  // Private mutations own OPTIONS so they never inherit wildcard CORS.
62
- const accessTokens = await routeAccessTokens(context);
63
- if (accessTokens)
64
- return accessTokens;
65
- const credentials = await routeCredentials(context);
66
- if (credentials)
67
- return credentials;
68
- const oauthManagement = await routeOAuthManagement(context);
69
- if (oauthManagement)
70
- return oauthManagement;
58
+ const uiResponse = await opts.ui?.handle(context);
59
+ if (uiResponse)
60
+ return uiResponse;
71
61
  if (request.method === "OPTIONS") {
72
62
  for (const provider of auth) {
73
63
  if (provider.handleMetadata) {
@@ -123,12 +113,8 @@ export function createFetchHandler(opts) {
123
113
  },
124
114
  reservedRoutes: [
125
115
  "/health",
126
- "/",
127
- "/credentials",
128
- "/tokens",
129
- "/activity",
130
- "/ui",
131
- "/ui/*",
116
+ ...(opts.ui?.reservedPaths ?? []),
117
+ ...(opts.ui && opts.activity?.list ? ["/activity"] : []),
132
118
  ],
133
119
  },
134
120
  ...(opts.deploymentInfo ? { deployment: opts.deploymentInfo } : {}),
@@ -137,12 +123,6 @@ export function createFetchHandler(opts) {
137
123
  const oauthCallback = await routeOAuthCallback(context);
138
124
  if (oauthCallback)
139
125
  return oauthCallback;
140
- const ui = await routeUi(context);
141
- if (ui)
142
- return ui;
143
- const activity = await routeActivity(context);
144
- if (activity)
145
- return activity;
146
126
  const mcp = await routeMcp(context);
147
127
  if (mcp)
148
128
  return mcp;
package/dist/types.d.ts CHANGED
@@ -132,6 +132,8 @@ export interface CredentialTestResult {
132
132
  message?: string;
133
133
  }
134
134
  export interface ConnectorContext {
135
+ /** Explicit downstream consent initiation, never set by status/catalog/calls. */
136
+ allowAuthorization?: boolean;
135
137
  /** Storage namespaced to this connector. */
136
138
  storage: KVStorage;
137
139
  logger: Logger;
package/dist/ui.d.ts CHANGED
@@ -1,75 +1,12 @@
1
- import type { CredentialVault } from "./credentials.js";
1
+ import type { OperatorSurface } from "./module-contracts.js";
2
+ import type { CredentialVault } from "./credential-contract.js";
2
3
  import { type DeferredWork } from "./connector-scope.js";
3
- import { type CredentialManagementCapability, type AccessTokenManagementCapability, type UiData } from "./operator-ui/model.js";
4
+ import { type CredentialManagementCapability, type UiData } from "./operator-ui/model.js";
4
5
  import type { RegistryView } from "./registry.js";
5
6
  import type { ConnectaBranding, UiAuthConfig } from "./types.js";
6
- export { filterUiConnectors, type AccessTokenManagementCapability, type CredentialManagementCapability, type UiConnector, type UiData, } from "./operator-ui/model.js";
7
- /** Connecta's default monochrome "C" mark. */
8
- export declare const CONNECTA_FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n <style>\n .fg { fill: #000 }\n @media (prefers-color-scheme: dark) { .fg { fill: #fff } }\n </style>\n <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\"/>\n</svg>";
9
- interface ResolvedBranding {
10
- productName: string;
11
- productUrl?: string;
12
- ownerName?: string;
13
- ownerUrl?: string;
14
- description: string;
15
- /** Browser tab title and page meta name. */
16
- pageTitle: string;
17
- /** href for the page's icon link. */
18
- faviconHref: string;
19
- themeColor: string;
20
- }
21
- export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBranding;
22
- /**
23
- * Names of the branding URLs the operator set that failed their gate and were
24
- * replaced by a default. Lives beside the gates so the startup warning cannot
25
- * drift from them, and takes `unknown` fields for the same reason
26
- * `resolveBranding` does — a warning helper must never throw.
27
- */
28
- export declare function droppedBrandingUrls(branding?: ConnectaBranding): string[];
29
- export declare function isSafeHttpUrl(url: unknown): boolean;
30
- /**
31
- * True for values allowed in the page's `<link rel="icon" href>`: an absolute
32
- * `http(s)` URL (an icon the operator hosts elsewhere) or a path rooted at this
33
- * origin. The relative carve-out is deliberate rather than accidental — the
34
- * default href is the relative `/favicon.svg`, which `isSafeHttpUrl` alone would
35
- * reject — and it is kept narrow on both ends.
36
- *
37
- * Root-relative only, because operator and OAuth callback pages sit at
38
- * different depths and a document-relative path would resolve differently.
39
- *
40
- * "Root-relative" is enforced structurally: exactly one leading `/` followed by
41
- * a character that is neither `/` nor `\`. Both of those would make the value an
42
- * authority (`//host`, and `/\host` because the URL parser folds `\` to `/` in
43
- * special schemes), pointing at an origin this server does not control. The test
44
- * runs on a copy with tab/newline/CR removed, since the parser strips those
45
- * anywhere and `/\t/host` would otherwise slip through as single-slash. The
46
- * origin comparison that follows is defense in depth, not the authority check —
47
- * on its own it would accept an authority that happened to equal the probe host.
48
- */
49
- export declare function isSafeIconHref(href: unknown): boolean;
50
- /** Absolute HTTPS gate for the `UiAuthConfig` URL fields documented in types.ts. */
51
- export declare function isSafeHttpsUrl(url: unknown): boolean;
52
- /**
53
- * Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
54
- * gate. Lives beside the gate for the same reason `droppedBrandingUrls` does: the
55
- * startup warning cannot then drift from what rendering actually drops. Every
56
- * field is read defensively rather than trusted, because a custom `InboundAuth`
57
- * is untyped at a JS call site — `isSafeHttpsUrl` takes `unknown`, and a
58
- * `uiAuth` that is not the clerk shape is reported as nothing to warn about.
59
- *
60
- * `frontendApiUrl` is required, so anything that fails its gate is a drop.
61
- * `signInUrl` and `signUpUrl` are optional, so only a value the operator
62
- * *supplied* and the gate then rejected is worth a warning — an unset field
63
- * took no default away from anyone. `isSetUrlValue` decides that, the same way
64
- * and for the same reasons it decides it for the branding URLs: a warning that
65
- * fires for one and not the other would be reporting on the field rather than
66
- * on the operator's intent. Rendering is not consulted for this: it drops on
67
- * the gate alone, and a blank string fails that gate too — it is simply not
68
- * *reported*, because a blank is indistinguishable from leaving the field
69
- * alone.
70
- */
71
- export declare function droppedUiAuthUrls(uiAuth?: UiAuthConfig): string[];
72
- export type OperatorPage = "connections" | "credentials" | "tokens" | "activity";
7
+ export { filterUiConnectors, type CredentialManagementCapability, type UiConnector, type UiData, } from "./operator-ui/model.js";
8
+ export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
9
+ export type OperatorPage = "connections" | "activity";
73
10
  export declare function operatorPageForPath(path: string): OperatorPage | undefined;
74
11
  export declare function operatorPageTitle(page: OperatorPage, configuredTitle: string): string;
75
12
  export declare function credentialManagementCapability(input: {
@@ -85,9 +22,17 @@ export declare function credentialManagementCapability(input: {
85
22
  export declare function buildUiData(registry: RegistryView, baseUrl: string, serverInfo: {
86
23
  name: string;
87
24
  version: string;
88
- }, credentialVault?: CredentialVault, activityEnabled?: boolean, credentialManagement?: CredentialManagementCapability, defer?: DeferredWork, oauthManagement?: boolean, discoveryConcurrency?: number, accessTokenManagement?: AccessTokenManagementCapability, personalCredentialOwner?: string): Promise<UiData>;
25
+ }, credentialVault?: CredentialVault, activityEnabled?: boolean, credentialManagement?: CredentialManagementCapability, defer?: DeferredWork, oauthManagement?: boolean, discoveryConcurrency?: number, personalCredentialOwner?: string, detailOptions?: {
26
+ mayManage?: (id: string) => boolean;
27
+ timeoutMs?: number;
28
+ signal?: AbortSignal;
29
+ }): Promise<UiData>;
89
30
  /**
90
31
  * Every operator page serves this same data-free shell. Connector, credential,
91
32
  * and activity data arrives only through the authenticated `/ui/*` APIs.
92
33
  */
93
34
  export declare function renderUiHtml(uiAuth?: UiAuthConfig, mcpUrl?: string, branding?: ConnectaBranding, nonce?: string, page?: OperatorPage): string;
35
+ /** Mount the connection UI without enabling any storage or activity module. */
36
+ export declare function operatorUi(options?: {
37
+ branding?: ConnectaBranding;
38
+ }): OperatorSurface;