@zackbart/connecta 0.24.3 → 0.24.4
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 +18 -20
- package/CHANGELOG.md +64 -1
- package/README.md +5 -6
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.d.ts +5 -1
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.js +8 -4
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -1
- package/dist/meta-tools.js +105 -29
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +12 -4
- package/dist/registry.js +22 -8
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -181
- package/documentation/auth.md +197 -176
- package/documentation/code-mode.md +426 -321
- package/documentation/meta-tools.md +356 -416
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
|
@@ -3,7 +3,12 @@ import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
|
|
|
3
3
|
import { defined } from "../connectors/api.js";
|
|
4
4
|
/** RevenueCat publishes one hosted MCP endpoint, streamable HTTP. */
|
|
5
5
|
export const REVENUECAT_MCP_ENDPOINT = "https://mcp.revenuecat.ai/mcp";
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Reviewed reads, every `Read` row of RevenueCat's tool reference as read on
|
|
8
|
+
* 2026-08-30 (P5). The list is a superset: plan, platform, and beta enrollment
|
|
9
|
+
* gate parts of the catalog, so a name this account never returns costs
|
|
10
|
+
* nothing while an unlisted new one fails closed.
|
|
11
|
+
*/
|
|
7
12
|
const READ_ONLY_TOOLS = new Set([
|
|
8
13
|
// Projects and apps
|
|
9
14
|
"get-account-billing",
|
|
@@ -70,26 +75,48 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
70
75
|
// *start* one are writes and sit below.
|
|
71
76
|
"get-paywall-ai-task",
|
|
72
77
|
]);
|
|
73
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Reviewed writes with their destructive verdict. Most follow the verb, and
|
|
80
|
+
* additive writes stay additive because `readOnlyHint: false` already routes
|
|
81
|
+
* them through the approval path — asserting a destruction that does not happen
|
|
82
|
+
* only inflates the copy a human reads. The verdicts the verb does not decide
|
|
83
|
+
* carry their reason on the row.
|
|
84
|
+
*
|
|
85
|
+
* `render-paywall-screenshot` is deliberately in neither map: RevenueCat's
|
|
86
|
+
* reference gives it no access column, so the live annotation stands and a
|
|
87
|
+
* catalog that omits one fails closed. Guessing from a harmless-sounding name
|
|
88
|
+
* is what P5 exists to prevent.
|
|
89
|
+
*/
|
|
74
90
|
const WRITE_TOOLS = new Map([
|
|
75
91
|
// Projects and apps
|
|
76
92
|
["create-app", "additive"],
|
|
77
93
|
["create-project", "additive"],
|
|
78
94
|
["update-app", "destructive"],
|
|
79
95
|
["update-project-ui-config", "destructive"],
|
|
96
|
+
// Filed `Write` by RevenueCat, so it cannot take the read path, but it leaves
|
|
97
|
+
// the saved credentials alone and only records a check's outcome.
|
|
80
98
|
["validate-app-credentials", "additive"],
|
|
81
99
|
// Products and prices
|
|
82
100
|
["archive-product", "destructive"],
|
|
83
101
|
["create-product", "additive"],
|
|
102
|
+
// Named `create-`, but "Configure prices for a product": the price set
|
|
103
|
+
// already exists, so this overwrites it, and it is money-facing.
|
|
84
104
|
["create-product-prices", "destructive"],
|
|
105
|
+
// "Fills *missing* App Store subscription territory prices" — by
|
|
106
|
+
// RevenueCat's own word it writes only where nothing is set.
|
|
85
107
|
["equalize-subscription-prices", "additive"],
|
|
108
|
+
// An upsert.
|
|
86
109
|
["set-product-store-state", "destructive"],
|
|
110
|
+
// Sends products to Apple for review.
|
|
87
111
|
["submit-products-to-store", "destructive"],
|
|
88
112
|
["unarchive-product", "destructive"],
|
|
89
113
|
["update-product", "destructive"],
|
|
114
|
+
// Reserves a new App Store Connect review screenshot slot; replaces nothing.
|
|
90
115
|
["upload-product-store-state-screenshot", "additive"],
|
|
91
116
|
// Entitlements
|
|
92
117
|
["archive-entitlement", "destructive"],
|
|
118
|
+
// Attach adds membership and removes nothing; detach is the destructive half.
|
|
119
|
+
// Filing both destructive would make the pair read identically to a human.
|
|
93
120
|
["attach-products-to-entitlement", "additive"],
|
|
94
121
|
["create-entitlement", "additive"],
|
|
95
122
|
["detach-products-from-entitlement", "destructive"],
|
|
@@ -97,6 +124,7 @@ const WRITE_TOOLS = new Map([
|
|
|
97
124
|
["update-entitlement", "destructive"],
|
|
98
125
|
// Offerings and packages
|
|
99
126
|
["archive-offering", "destructive"],
|
|
127
|
+
// The attach/detach argument again, one level down.
|
|
100
128
|
["attach-products-to-package", "additive"],
|
|
101
129
|
["create-offering", "additive"],
|
|
102
130
|
["create-packages", "additive"],
|
|
@@ -110,6 +138,7 @@ const WRITE_TOOLS = new Map([
|
|
|
110
138
|
// Paywalls
|
|
111
139
|
["attach-offering-to-paywall", "destructive"],
|
|
112
140
|
["detach-offering-from-paywall", "destructive"],
|
|
141
|
+
// Copies an existing paywall's current draft; the original is untouched.
|
|
113
142
|
["duplicate-paywall", "additive"],
|
|
114
143
|
["publish-paywall", "destructive"],
|
|
115
144
|
["unpublish-paywall", "destructive"],
|
|
@@ -130,14 +159,23 @@ const WRITE_TOOLS = new Map([
|
|
|
130
159
|
["unarchive-virtual-currency", "destructive"],
|
|
131
160
|
["update-virtual-currency", "destructive"],
|
|
132
161
|
// Integrations and webhooks
|
|
162
|
+
// Destructive on consequence, not on the verb: no existing integration
|
|
163
|
+
// changes, but with filters omitted the new one starts delivering every
|
|
164
|
+
// customer event in the project to a URL the caller typed.
|
|
133
165
|
["create-webhook-integration", "destructive"],
|
|
134
166
|
["delete-webhook-integration", "destructive"],
|
|
135
167
|
["update-webhook-integration", "destructive"],
|
|
136
|
-
// Paywall editing
|
|
168
|
+
// Paywall editing. Both start an async task; the difference is what the task
|
|
169
|
+
// touches — a new paywall, or a draft that already exists.
|
|
137
170
|
["create-paywall-ai", "additive"],
|
|
138
171
|
["edit-paywall-ai", "destructive"],
|
|
139
172
|
]);
|
|
140
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* One release-reviewed manifest, used both to classify a live tool and as the
|
|
175
|
+
* baseline the drift check compares against, so the annotation a caller gets
|
|
176
|
+
* and the verdict a check reads can never disagree. Names and verdicts only —
|
|
177
|
+
* no schemas are vendored; the live `tools/list` response stays authoritative.
|
|
178
|
+
*/
|
|
141
179
|
export const REVENUECAT_VETTED_CATALOG = vettedCatalog({
|
|
142
180
|
reads: READ_ONLY_TOOLS,
|
|
143
181
|
writes: WRITE_TOOLS,
|
|
@@ -40,7 +40,13 @@ export interface StripeHeaderOptions extends StripeCommonOptions {
|
|
|
40
40
|
connectedAccount?: string;
|
|
41
41
|
}
|
|
42
42
|
export type StripeOptions = StripeOAuthOptions | StripeHeaderOptions;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* One release-reviewed manifest, used both to classify a live tool and as the
|
|
45
|
+
* baseline the drift check compares against, so the annotation a caller gets and
|
|
46
|
+
* the verdict a check reads can never disagree. Stripe publishes no stability or
|
|
47
|
+
* deprecation policy for this tool set, so treat it as unversioned: an
|
|
48
|
+
* unclassified, unannotated name fails closed onto the approval path (P5).
|
|
49
|
+
*/
|
|
44
50
|
export declare const STRIPE_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
|
|
45
51
|
/** A maintained Stripe hosted-MCP connection. */
|
|
46
52
|
export declare function stripe(id: string, options: StripeOptions): Connector;
|
package/dist/providers/stripe.js
CHANGED
|
@@ -3,7 +3,14 @@ import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
|
|
|
3
3
|
import { defined } from "../connectors/api.js";
|
|
4
4
|
/** Stripe publishes one hosted MCP endpoint for every account and mode. */
|
|
5
5
|
export const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Stripe documents no MCP-specific limit, so these transcribe the account limit
|
|
8
|
+
* MCP traffic spends: 100 requests per second live, 25 in a sandbox. OAuth takes
|
|
9
|
+
* the sandbox rule (`mode ?? "sandbox"` below) because one session can reach
|
|
10
|
+
* either mode and the policy is fixed before the account-scoped call begins.
|
|
11
|
+
* The `maxConcurrency` figures are Connecta's own choice — Stripe says
|
|
12
|
+
* per-account and per-endpoint concurrency limits exist but publishes no number.
|
|
13
|
+
*/
|
|
7
14
|
const STRIPE_ADMISSION = {
|
|
8
15
|
production: {
|
|
9
16
|
rules: [
|
|
@@ -47,16 +54,30 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
47
54
|
"manage_stripe_accounts",
|
|
48
55
|
"search_stripe_documentation",
|
|
49
56
|
]);
|
|
50
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Reviewed writes with their destructive verdict. `stripe_api_write` is the
|
|
59
|
+
* sibling of `stripe_api_read` and carries every `POST`, `PATCH`, `PUT`, and
|
|
60
|
+
* `DELETE`. Additive writes leave `destructiveHint` unset: `readOnlyHint: false`
|
|
61
|
+
* already routes them through the approval path, and asserting destruction they
|
|
62
|
+
* do not perform would misstate their effect.
|
|
63
|
+
*/
|
|
51
64
|
const WRITE_TOOLS = new Map([
|
|
52
65
|
["stripe_api_write", "destructive"],
|
|
53
66
|
["create_refund", "destructive"],
|
|
67
|
+
// Creates and continues provider-side guide state; destroys nothing.
|
|
54
68
|
["stripe_implementation_planner", "additive"],
|
|
69
|
+
// Retrieval mixed with query-run creation behind one tool.
|
|
55
70
|
["stripe_analytics", "additive"],
|
|
56
71
|
["stripe_report", "additive"],
|
|
57
72
|
["send_stripe_mcp_feedback", "additive"],
|
|
58
73
|
]);
|
|
59
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* One release-reviewed manifest, used both to classify a live tool and as the
|
|
76
|
+
* baseline the drift check compares against, so the annotation a caller gets and
|
|
77
|
+
* the verdict a check reads can never disagree. Stripe publishes no stability or
|
|
78
|
+
* deprecation policy for this tool set, so treat it as unversioned: an
|
|
79
|
+
* unclassified, unannotated name fails closed onto the approval path (P5).
|
|
80
|
+
*/
|
|
60
81
|
export const STRIPE_VETTED_CATALOG = vettedCatalog({
|
|
61
82
|
reads: READ_ONLY_TOOLS,
|
|
62
83
|
writes: WRITE_TOOLS,
|
|
@@ -64,7 +85,12 @@ export const STRIPE_VETTED_CATALOG = vettedCatalog({
|
|
|
64
85
|
/** Stripe key prefixes carry their own mode; only a clear reading counts. */
|
|
65
86
|
const LIVE_KEY = /\b(?:sk|rk|pk)_live_/;
|
|
66
87
|
const TEST_KEY = /\b(?:sk|rk|pk)_test_/;
|
|
67
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Refuse a recognizable key/mode mismatch. This is the only mode guard Connecta
|
|
90
|
+
* can offer, and it needs the key as a literal header to read the prefix: an
|
|
91
|
+
* operator-managed key is not in the deployment file, so its declared mode
|
|
92
|
+
* stands alone and a key pointed at the other environment fails at Stripe.
|
|
93
|
+
*/
|
|
68
94
|
function assertModeMatchesKey(id, mode, auth) {
|
|
69
95
|
if (auth.type !== "headers")
|
|
70
96
|
return;
|
package/dist/providers/vercel.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* No `@vercel/sdk` on purpose — not a dependency and not an optional peer.
|
|
3
|
+
* Direct fetch keeps the root Workers-safe, avoids shipping the SDK's generated
|
|
4
|
+
* model graph, and lets the reviewed named operations and the REST hatches share
|
|
5
|
+
* one guarded transport. The trade is API drift, handled explicitly rather than
|
|
6
|
+
* by the SDK's version bumps: `scripts/drift/vercel-endpoints.json` records the
|
|
7
|
+
* method, versioned path, spec revision, and request/response digest for every
|
|
8
|
+
* fixed endpoint, and `npm run providers:check -- --provider vercel` compares
|
|
9
|
+
* those rows with Vercel's published OpenAPI document at
|
|
10
|
+
* https://openapi.vercel.sh/ without needing a credential.
|
|
11
|
+
*/
|
|
2
12
|
import { api, defined } from "../connectors/api.js";
|
|
3
13
|
import { remoteMcp } from "../connectors/remote-mcp.js";
|
|
4
14
|
import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
|
package/dist/registry.d.ts
CHANGED
|
@@ -107,8 +107,8 @@ export interface RegistryView {
|
|
|
107
107
|
signal?: AbortSignal;
|
|
108
108
|
}): Promise<CallAdmissionPermit>;
|
|
109
109
|
resultsStorage(): KVStorage;
|
|
110
|
-
/** Reserve runtime-wide capacity before writing a paging envelope. */
|
|
111
|
-
stashResult(key: string,
|
|
110
|
+
/** Reserve runtime-wide capacity before writing a paging envelope's chunks. */
|
|
111
|
+
stashResult(key: string, chunks: readonly string[], ttlSeconds: number): Promise<boolean>;
|
|
112
112
|
/** Local declared-vs-stored credential mismatch, with no downstream I/O. */
|
|
113
113
|
credentialDriftFor(id: string): Promise<string | undefined>;
|
|
114
114
|
/** Value-free shape learned from successful calls, never a provider declaration. */
|
|
@@ -240,8 +240,16 @@ export declare class Registry implements RegistryView {
|
|
|
240
240
|
private observeCatalogDrift;
|
|
241
241
|
/** Reject queued/future downstream admission; active permits release safely. */
|
|
242
242
|
closeCallAdmission(): void;
|
|
243
|
-
/**
|
|
244
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Reserve capacity and write one ASCII paging envelope in this runtime.
|
|
245
|
+
*
|
|
246
|
+
* `chunks[0]` lands on `<prefix><key>` and `chunks[n]` on `<prefix><key>#<n>`
|
|
247
|
+
* — the layout get_result reads back, so a page fetches only the chunks it
|
|
248
|
+
* covers instead of the whole stored result (issue #540). Chunking is a
|
|
249
|
+
* read-cost decision, not a capacity one: however many keys an envelope
|
|
250
|
+
* occupies, it is one stash entry charged its total ASCII length.
|
|
251
|
+
*/
|
|
252
|
+
stashResult(key: string, chunks: readonly string[], ttlSeconds: number, prefix?: string): Promise<boolean>;
|
|
245
253
|
/**
|
|
246
254
|
* Storage namespaced to the meta-tool result store (`results:` prefix), kept
|
|
247
255
|
* separate from any connector's `conn:<id>:` namespace. Backs get_result.
|
package/dist/registry.js
CHANGED
|
@@ -446,12 +446,20 @@ export class Registry {
|
|
|
446
446
|
for (const registry of this.personalRegistries.values())
|
|
447
447
|
registry.closeCallAdmission();
|
|
448
448
|
}
|
|
449
|
-
/**
|
|
450
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Reserve capacity and write one ASCII paging envelope in this runtime.
|
|
451
|
+
*
|
|
452
|
+
* `chunks[0]` lands on `<prefix><key>` and `chunks[n]` on `<prefix><key>#<n>`
|
|
453
|
+
* — the layout get_result reads back, so a page fetches only the chunks it
|
|
454
|
+
* covers instead of the whole stored result (issue #540). Chunking is a
|
|
455
|
+
* read-cost decision, not a capacity one: however many keys an envelope
|
|
456
|
+
* occupies, it is one stash entry charged its total ASCII length.
|
|
457
|
+
*/
|
|
458
|
+
async stashResult(key, chunks, ttlSeconds, prefix = "results:") {
|
|
451
459
|
const maxBytes = this.opts.results?.maxStashBytes ?? 8 * 1024 * 1024;
|
|
452
460
|
const maxEntries = this.opts.results?.maxStashEntries ?? 64;
|
|
453
461
|
// The paging envelope is ASCII, so its string length is its stored byte count.
|
|
454
|
-
const bytes =
|
|
462
|
+
const bytes = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
455
463
|
if (bytes > maxBytes || maxEntries === 0)
|
|
456
464
|
return false;
|
|
457
465
|
const now = Date.now();
|
|
@@ -462,7 +470,8 @@ export class Registry {
|
|
|
462
470
|
try {
|
|
463
471
|
// TTL alone cannot reclaim a lazy backend. Keep the charge until deletion
|
|
464
472
|
// succeeds, including writes which persisted before throwing.
|
|
465
|
-
|
|
473
|
+
for (const staleKey of entry.keys)
|
|
474
|
+
await this.opts.storage.delete(staleKey);
|
|
466
475
|
this.resultStash.delete(oldKey);
|
|
467
476
|
this.resultStashBytes -= entry.bytes;
|
|
468
477
|
}
|
|
@@ -473,11 +482,16 @@ export class Registry {
|
|
|
473
482
|
if (this.resultStash.size >= maxEntries || this.resultStashBytes + bytes > maxBytes)
|
|
474
483
|
return false;
|
|
475
484
|
const fullKey = prefix + key;
|
|
476
|
-
const
|
|
485
|
+
const keys = chunks.map((_, index) => index === 0 ? fullKey : `${fullKey}#${index}`);
|
|
486
|
+
const entry = { keys, bytes, expiresAt: Infinity, busy: true };
|
|
477
487
|
this.resultStash.set(fullKey, entry);
|
|
478
488
|
this.resultStashBytes += bytes;
|
|
479
489
|
try {
|
|
480
|
-
|
|
490
|
+
// Trailing chunks first: the header chunk is what makes an id readable, so
|
|
491
|
+
// a write that fails midway leaves no envelope pointing at absent chunks.
|
|
492
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
493
|
+
await this.opts.storage.set(keys[index], chunks[index], { ttlSeconds });
|
|
494
|
+
}
|
|
481
495
|
entry.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
482
496
|
return true;
|
|
483
497
|
}
|
|
@@ -1201,8 +1215,8 @@ class ScopedRegistryView {
|
|
|
1201
1215
|
}
|
|
1202
1216
|
return registry.admitCall(...args);
|
|
1203
1217
|
}
|
|
1204
|
-
stashResult(key,
|
|
1205
|
-
return this.root.stashResult(key,
|
|
1218
|
+
stashResult(key, chunks, ttlSeconds) {
|
|
1219
|
+
return this.root.stashResult(key, chunks, ttlSeconds, this.scope.subjectKey ? `subject:${this.scope.subjectKey}:` : "results:");
|
|
1206
1220
|
}
|
|
1207
1221
|
resultsStorage() {
|
|
1208
1222
|
return this.scope.subjectKey
|
package/dist/types.d.ts
CHANGED
|
@@ -519,6 +519,43 @@ export interface ConnectaBranding {
|
|
|
519
519
|
};
|
|
520
520
|
/** `theme-color` meta value. Defaults to "#ffffff". */
|
|
521
521
|
themeColor?: string;
|
|
522
|
+
/**
|
|
523
|
+
* Operator-page appearance. Every field is optional, and a rejected value
|
|
524
|
+
* takes its default rather than failing the page, so `createConnecta` warns
|
|
525
|
+
* at startup about anything it dropped.
|
|
526
|
+
*
|
|
527
|
+
* Five knobs, not a palette. Surfaces, borders, muted text, and the status
|
|
528
|
+
* colors all derive from these, so a deployment sets an accent and gets a
|
|
529
|
+
* readable page instead of thirty chances to break one.
|
|
530
|
+
*/
|
|
531
|
+
theme?: ConnectaTheme;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* The operator UI's themeable tokens. These land in a `:root` block on the
|
|
535
|
+
* page, so each is gated by a narrow syntactic check: an unvalidated value
|
|
536
|
+
* here would let deployment config write arbitrary CSS.
|
|
537
|
+
*/
|
|
538
|
+
export interface ConnectaTheme {
|
|
539
|
+
/**
|
|
540
|
+
* The brand color: links, focus rings, primary actions, the active nav item.
|
|
541
|
+
* Hex only (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Defaults to `#2f5fe0`.
|
|
542
|
+
*/
|
|
543
|
+
accent?: string;
|
|
544
|
+
/**
|
|
545
|
+
* Corner rounding for cards, inputs, and buttons. A CSS length (`10px`,
|
|
546
|
+
* `0.5rem`) or a bare number read as pixels. `0` restores square corners.
|
|
547
|
+
* Defaults to `10px`.
|
|
548
|
+
*/
|
|
549
|
+
radius?: string | number;
|
|
550
|
+
/** Body font stack. A plain CSS font-family list. Defaults to a system stack. */
|
|
551
|
+
fontFamily?: string;
|
|
552
|
+
/** Monospace font stack for addresses, ids, and the endpoint URL. */
|
|
553
|
+
monoFamily?: string;
|
|
554
|
+
/**
|
|
555
|
+
* `"system"` (the default) follows the operator's OS setting; `"light"` and
|
|
556
|
+
* `"dark"` pin the page to one palette.
|
|
557
|
+
*/
|
|
558
|
+
colorScheme?: "system" | "light" | "dark";
|
|
522
559
|
}
|
|
523
560
|
/** An inbound authentication provider (bearer token, interactive identity, ...). */
|
|
524
561
|
export interface InboundAuth {
|
package/dist/ui.js
CHANGED
|
@@ -8,7 +8,7 @@ import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concu
|
|
|
8
8
|
import { OPERATOR_UI_CSS, OPERATOR_UI_SCRIPT, } from "./operator-ui/generated.js";
|
|
9
9
|
import { CONNECTA_VERSION } from "./version.js";
|
|
10
10
|
export { filterUiConnectors, } from "./operator-ui/model.js";
|
|
11
|
-
import { resolveBranding, isSafeHttpsUrl } from "./branding.js";
|
|
11
|
+
import { resolveBranding, isSafeHttpsUrl, themeCss } from "./branding.js";
|
|
12
12
|
export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
|
|
13
13
|
/**
|
|
14
14
|
* A JS string literal safe to inline in a script element. Escaping `/` keeps
|
|
@@ -281,8 +281,14 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
|
|
|
281
281
|
const clerkScript = clerk && clerkScriptOrigin
|
|
282
282
|
? `<script${nonceAttr} crossorigin="anonymous" data-clerk-publishable-key="${escapeHtmlAttr(clerk.publishableKey)}" src="${escapeHtmlAttr(clerkScriptOrigin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
|
|
283
283
|
: "";
|
|
284
|
+
// A pinned scheme is an attribute, not a stylesheet edit: the dark palette
|
|
285
|
+
// keys off `html[data-scheme]`, so "system" leaves the attribute off and the
|
|
286
|
+
// media query decides.
|
|
287
|
+
const schemeAttr = brand.theme.colorScheme === "system"
|
|
288
|
+
? ""
|
|
289
|
+
: ` data-scheme="${brand.theme.colorScheme}"`;
|
|
284
290
|
return `<!doctype html>
|
|
285
|
-
<html lang="en">
|
|
291
|
+
<html lang="en"${schemeAttr}>
|
|
286
292
|
<head>
|
|
287
293
|
<meta charset="utf-8">
|
|
288
294
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@@ -292,22 +298,24 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
|
|
|
292
298
|
<link rel="shortcut icon" href="/favicon.ico">
|
|
293
299
|
<title>${escapeHtmlAttr(title)}</title>
|
|
294
300
|
${clerkScript}
|
|
295
|
-
<style>${OPERATOR_UI_CSS}</style>
|
|
301
|
+
<style>${OPERATOR_UI_CSS}${themeCss(brand.theme)}</style>
|
|
296
302
|
</head>
|
|
297
303
|
<body>
|
|
298
304
|
<a class="skip-link" href="#operatorContent">Skip to operator page</a>
|
|
299
|
-
<header class="masthead shell
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
305
|
+
<header class="masthead shell">
|
|
306
|
+
<div class="masthead-inner">
|
|
307
|
+
<div class="mast-nav">
|
|
308
|
+
${owner}
|
|
309
|
+
${product}
|
|
310
|
+
</div>
|
|
303
311
|
<div id="operatorNav"></div>
|
|
304
312
|
</div>
|
|
305
313
|
</header>
|
|
306
314
|
|
|
307
315
|
<main id="operatorContent" class="page shell" tabindex="-1">
|
|
308
|
-
<div class="lead
|
|
309
|
-
<h1
|
|
310
|
-
<div class="
|
|
316
|
+
<div class="lead">
|
|
317
|
+
<h1>${OPERATOR_PAGE_LABELS[page]}</h1>
|
|
318
|
+
<div class="lead-copy">
|
|
311
319
|
<p>${escapeHtmlAttr(brand.description)}</p>
|
|
312
320
|
<noscript><p class="msg">The operator pages need JavaScript. Nothing else here
|
|
313
321
|
does — agents reach this deployment through <span class="mono">/mcp</span>.</p></noscript>
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED