@zackbart/connecta 0.33.0 → 0.34.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/CHANGELOG.md +65 -0
- package/README.md +100 -128
- package/SECURITY.md +1 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +3 -0
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- package/dist/catalog-drift.d.ts +30 -26
- package/dist/catalog-drift.js +169 -84
- package/dist/catalog-service.js +16 -2
- package/dist/config-value-sources.js +6 -1
- package/dist/config.d.ts +13 -2
- package/dist/config.js +36 -2
- package/dist/connector-caller.js +2 -1
- package/dist/connectors/api-connector.d.ts +3 -0
- package/dist/connectors/api-connector.js +5 -1
- package/dist/connectors/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +2 -0
- package/dist/connectors/remote-mcp.js +5 -1
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +2 -0
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.js +1 -0
- package/dist/invocation.js +3 -2
- package/dist/membership.d.ts +2 -0
- package/dist/meta-tools.js +3 -2
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-ui/agents.d.ts +1 -1
- package/dist/operator-ui/agents.js +16 -16
- package/dist/operator-ui/config-summary.js +565 -0
- package/dist/operator-ui/connection-map.js +376 -0
- package/dist/operator-ui/endpoints.js +45 -16
- package/dist/operator-ui/fix-prompts.js +1 -1
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/health.js +52 -0
- package/dist/operator-ui/member-page.js +13 -28
- package/dist/operator-ui/overview.js +123 -0
- package/dist/operator-ui/view.d.ts +26 -22
- package/dist/operator-ui/view.js +130 -96
- package/dist/page-styles.js +5 -3
- package/dist/provider.d.ts +6 -1
- package/dist/provider.js +18 -1
- package/dist/providers/_shared/google/workspace.d.ts +9 -4
- package/dist/providers/_shared/google/workspace.js +7 -3
- package/dist/providers/basecamp/index.d.ts +2 -0
- package/dist/providers/breeze/index.d.ts +2 -0
- package/dist/providers/ccb/index.d.ts +2 -0
- package/dist/providers/ccb/index.js +2 -2
- package/dist/providers/cloudflare/index.d.ts +2 -0
- package/dist/providers/cloudflare/index.js +5 -1
- package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
- package/dist/providers/cloudflare/rest.js +19 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -2
- package/dist/providers/cloudflare/value-safety.js +51 -18
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +1 -1
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +1 -1
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/intercom/index.d.ts +24 -0
- package/dist/providers/intercom/index.js +121 -0
- package/dist/providers/intercom/skill.generated.js +8 -0
- package/dist/providers/linear/index.js +204 -19
- package/dist/providers/linear/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.d.ts +2 -0
- package/dist/providers/mixpanel/index.js +21 -17
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/index.d.ts +2 -0
- package/dist/providers/notion/index.js +1 -1
- package/dist/providers/notion/skill.generated.js +1 -1
- package/dist/providers/overflow/index.d.ts +2 -0
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.d.ts +2 -0
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/index.d.ts +5 -3
- package/dist/providers/revenuecat/index.js +147 -7
- package/dist/providers/revenuecat/skill.generated.js +3 -3
- package/dist/providers/sheets/index.js +1 -1
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +1 -1
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/index.d.ts +2 -0
- package/dist/providers/stripe/index.js +50 -30
- package/dist/providers/stripe/skill.generated.js +2 -1
- package/dist/providers/tithely/index.d.ts +2 -0
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.d.ts +2 -0
- package/dist/providers/vercel/index.js +2 -2
- package/dist/providers/vercel/openapi.generated.js +391 -374
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/vercel/value-safety.js +14 -4
- package/dist/registry.d.ts +10 -14
- package/dist/registry.js +77 -62
- package/dist/routes/mcp.js +20 -13
- package/dist/routes/oauth.js +2 -2
- package/dist/routes/ui-data.js +8 -2
- package/dist/skills.js +18 -2
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +36 -11
- package/dist/ui.js +9 -4
- package/dist/usage-guide.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/README.md +2 -2
- package/documentation/architecture.md +66 -18
- package/documentation/auth.md +183 -67
- package/documentation/code-mode.md +30 -10
- package/documentation/deploying.md +5 -1
- package/documentation/integrating.md +64 -7
- package/documentation/meta-tools.md +38 -24
- package/documentation/migration-0.33.md +21 -13
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operating.md +1 -1
- package/documentation/operator-tests.md +52 -20
- package/documentation/operator-ui.md +341 -104
- package/documentation/provider-auth-migration.md +22 -0
- package/examples/worker/AGENTS.md +15 -9
- package/examples/worker/README.md +41 -14
- package/examples/worker/src/connecta.config.ts +5 -4
- package/examples/worker/wrangler.jsonc +1 -1
- package/package.json +23 -19
- package/templates/node/AGENTS.md +13 -7
- package/templates/node/README.md +14 -11
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.34.1 — 2026-10-19
|
|
6
|
+
|
|
7
|
+
Connecta 0.34.1 is a patch release with a security fix. The Cloudflare provider passed stored AI Search crawler headers through unredacted, including on the routes 0.34.0 already served, so any caller allowed to read those resources could see header values that may carry credentials. 0.34.1 withholds them, along with conditional firewall match credentials, Vercel daemon probe headers, setup-form defaults and payment client secrets, and refuses Cloudforce One match responses that have no published schema before any request is sent. Deployments that use the Cloudflare or Vercel providers should upgrade.
|
|
8
|
+
|
|
9
|
+
Connectors can now set `readOnly: true` (#895). A read-only connector serves only reviewed reads on every endpoint, maintainers' plain `/mcp` included; write and unclassified tools are hidden from discovery and refused on every call path. It combines with endpoint and pool `readOnly`, and the default is unchanged. The Stripe guide now shows both setups side by side: OAuth to Stripe's hosted MCP, or a secret or restricted key through the REST connector, with or without `readOnly`. A new maintained Intercom provider supports personal OAuth or a shared bearer token in the US or EU region; its schemas are not pinned yet because Intercom's catalog requires authentication to list.
|
|
10
|
+
|
|
11
|
+
Catalog drift no longer counts as connector health (#867): a drifted connector reads Connected and stays out of Needs attention, while drift details remain on the connector page and in Activity. Connector cards, counts and the attention list keep their last verdict and position while details reload, and the top bar is more compact. Drift detection itself is corrected (#884): reordered schema arrays no longer report false changes for schemas pinned in the new format (existing pins keep their original hashing until re-reviewed), plan-gated tools can be marked optional, schema coverage is reported honestly, failed fingerprints mark verification unavailable instead of keeping a clean report, and per-person connectors keep separate observations. The Linear, Mixpanel, RevenueCat and Stripe reviews are reconciled with their live hosted catalogs and pin schemas; Mixpanel's Get-Events, Get-Dashboard, List-Cohorts and Get-Metric serve as reads again without deployment overrides, so a `classification: { mixpanel: { "Get-Events": "read" } }` override can be removed. Upgrading from 0.34.0 needs no configuration, storage migration or token rotation.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Add a maintained Intercom hosted MCP provider with explicit US/EU region selection, personal OAuth or shared bearer-token auth, reviewed tool classifications, and guidance for sensitive support data and optional Fin Operator actions. Permission-gated Fin tools are optional for missing-tool drift while present tools retain classification checks. Schema coverage reports present reviewed tools as unpinned. Schema digests remain unpinned because anonymous catalog discovery requires authentication.
|
|
16
|
+
- Add optional connector-level `readOnly` to every connector and maintained provider, enforcing read-only discovery and calls on every endpoint. Show the policy in the operator UI and clarify Stripe OAuth and API-key setup.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Correct catalog drift reporting with schema coverage and verification states,
|
|
21
|
+
identity-partitioned observations, optional gated tools, and versioned schema
|
|
22
|
+
set canonicalization that preserves existing pins. Detect documented tool
|
|
23
|
+
access changes and stale unclassified acknowledgements in provider checks.
|
|
24
|
+
Operator refresh responses include the catalog comparison from that refresh,
|
|
25
|
+
so hashing failures and changed schemas cannot return an earlier clean report.
|
|
26
|
+
Provider pin assertions select the stored digest format, and a generated-roster
|
|
27
|
+
guard verifies every pin against captured schemas and rejects input/output drift.
|
|
28
|
+
- Reconcile the maintained Linear catalog with hosted label lifecycle, inbox, saved view, triage, and diff tools. Preserve plan-gated customer requests, review the documented customer-needs read, and pin the observed hosted schemas so later changes fail closed.
|
|
29
|
+
- Refresh reviewed hosted Mixpanel schemas so Get-Events, Get-Dashboard, List-Cohorts, and Get-Metric remain available to read-only endpoints without deployment overrides. Review the limited-beta Get-Audit-Log as a read, retain Fill-Event-Metadata as a write, and classify Get-Experiment as destructive because its optional computations can persist results and auto-conclude experiments.
|
|
30
|
+
- Keep catalog drift informational, keep each connector's last verdict on every operator view while its details are read again so cards, counts and the attention list stop bouncing, keep attention rows in configuration order as their verdicts settle, safely hold prototype-named connector IDs, keep card and detail headers and Overview attention text on single lines with accessible ellipsis, including long connector ids, auth scopes and Read-only badges, reserve the card action footer and empty tool bar row, and compact the top bar for long deployment names and all page tabs.
|
|
31
|
+
- Refresh Vercel and Cloudflare REST contracts and value-safety reviews, withhold conditional firewall match credentials, stored AI Search crawler headers and daemon probe headers, setup-form defaults, and payment client secrets, refuse schemaless Cloudforce match responses before dispatch, admit two stateless Cloudflare POST reads, classify hosted Cloudflare documentation search as read-only, and reconcile Vercel's complete hosted tool inventory.
|
|
32
|
+
- Review RevenueCat subscription-group listing, audience preview, and paywall screenshot rendering as reads, pin four live schemas, and clarify permission-gated catalogs and conservative credential-validation routing.
|
|
33
|
+
- Refresh Stripe hosted MCP tool reviews for money movement, recipient setup, Atlas formation, and renamed feedback; remove retired reviews, pin captured schemas for drift detection, and document account, mode, and human-confirmation requirements.
|
|
34
|
+
|
|
35
|
+
## 0.34.0 — 2026-10-16
|
|
36
|
+
|
|
37
|
+
Connecta 0.34.0 rebuilds the operator UI around an Overview with connector health, endpoints, people and agents, a Needs attention list with fix prompts, and a connection map (#867). Maintainers now land on Overview at `/`; Endpoints moves to `/endpoints`, and `/access` redirects there. Update bookmarks, links and browser automation that expected Endpoints at `/`. Members still get their member page on every operator page path. The upgrade steps are in `documentation/migration-0.34.md`.
|
|
38
|
+
|
|
39
|
+
The redesign carries through Connectors, Endpoints, the access matrix, Tokens, Settings, Activity and the member page, with warm neutral surfaces, one accent, self-hosted Hanken Grotesk and JetBrains Mono, and light and dark themes. Configuration uses tiles, connector tools open in a schema drawer, Activity groups calls by request, and Account holds Sign out and Change token. Default browser titles now join the product and owner with `·`; set `branding.pageTitle` explicitly if an integration needs the old title. Custom titles stay unchanged. Activity outcome links can use `failed` or `succeeded`; older exact outcome values still filter by that value. Custom activity stores may optionally report `describe().optionSources.retentionDays` as `"config"` or `"default"` so Settings can distinguish an explicit retention setting; existing stores need no change.
|
|
40
|
+
|
|
41
|
+
Agents in a pool without an `owner` no longer discover Google Workspace connectors whose `subject` is a mapping function (#873). Those connectors previously appeared but failed calls with `auth_required`; now they are absent from discovery, search, describe, skills and code mode, and calls by address fail `unknown_address`. Saved program returns are rechecked too. To give an agent Workspace access, use a pool with the intended person's Workspace address as its `owner`, such as `alice@example.com`; do not add an owner to a shared pool just to restore discovery. People, agents in owned pools and fixed-subject connectors keep their behavior.
|
|
42
|
+
|
|
43
|
+
Pools can now take a one-line `description` of 1 to 500 characters, appended to that pool's MCP instructions after `This endpoint: ` (#872). Pools without a description and plain `/mcp` keep their instructions. The auth guide now documents group tiers: pick the most capable pool everyone present is cleared for, use the base pool when anyone is unknown, and build personal pools on that person's highest tier (#875). Shipped docs also correct operator, provider and deployment guidance (#866). Repository development and CI now use pnpm (#877); installed deployments, the Node template and npm publishing keep using npm. Upgrading from 0.33.0 needs no storage migration or token rotation. Deployments that do not use the operator UI or mapping-subject Workspace connectors in owner-less agent pools can ignore the UI and discovery migration steps; all new configuration fields are optional.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Add an Overview page as the maintainer's landing page at `/`: a status pill and stat cards for connector health, endpoints, and people and agents, a "Needs attention" list that links each problem to its existing fix with a copyable fix prompt, and a connection map of callers, endpoints and connectors that traces what a node reaches on hover or focus and opens its page on click. Endpoints moves to `/endpoints`, and `/access` now redirects there. Members still get their member page on every path.
|
|
48
|
+
- Pools take an optional `description`: one line of plain text, 1 to 500 characters, that `/mcp/<pool>` and `/agent/<pool>` append to their MCP instructions after `This endpoint: ` so the model learns that endpoint's scope. Plain `/mcp` and pools without one keep the shared instructions, and `describeConfig()` lists each pool's `description` (`null` when unset).
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- The operator UI's Activity page and the member page ("Your connections") follow the calm redesign (#867). Activity shows counts and a calls-over-time strip, filters with a search box, an All / Failed / Succeeded control and connector and source chips instead of select menus, and rows grouped by request with one status dot, the caller as a person or agent chip, a connector monogram, the tool, and duration and size; a row opens for the rest of what was recorded. A connector's Activity tab uses the same rows. The member page shows the signed-in email, endpoints as cards, accounts with distinct monograms and aligned status badges, and a compact shared list. The Activity outcome filter now takes `failed` or `succeeded`; an older exact outcome value in a link still filters by that value.
|
|
53
|
+
- An agent in a pool without an `owner` no longer sees Google Workspace connectors whose `subject` is a mapping function: they are absent from discovery, search, describe, skills and code mode, and a call by address fails `unknown_address`, as for a personal connector. Before, the agent saw them and every call failed `auth_required`. People in the same pool, agents in owned pools, and fixed-subject connectors are unchanged (#870).
|
|
54
|
+
|
|
55
|
+
Paging a saved program return rechecks every connector contributing to search counts, ranking, term analysis, or guidance, including matches outside the returned page and connector references when no tools matched. Removing a pool owner therefore also hides those saved contributions from agents.
|
|
56
|
+
- Document group tiers in the agent pool layout: a chat gets the most capable tier everyone present is cleared for, tiers nest by spreading tool lists, the base pool is the lowest tier and the fallback for anyone unknown, a tier can double as a team pool for the people cleared for it, and personal pools build on their person's highest tier. Shorten the README to the visual overview.
|
|
57
|
+
- The operator UI and the pages connecta renders for people (sign-in, no access, OAuth results, 404s) take a new look: warm neutral surfaces, one indigo accent, soft cards, status pills, and Hanken Grotesk with JetBrains Mono for values, in light and dark. The fonts stay self-hosted. The top bar puts the sections, Search, Appearance and a new Account button on one row; Sign out and Change token now live under Account. The drawn connecta mark shows only on an unbranded deployment; a configured `favicon.svg` shows instead. A custom accent derives readable button text and dark-mode link color from itself.
|
|
58
|
+
- The operator UI's Connectors page shows a card per connector with its status, Shared or Per person badge, a read/write tool bar and its last call, filtered by All, Needs attention or Healthy. A connector's page opens with its status and badges; Tools filters by Read or Write and opens a schema in a side drawer, and Connection shows authentication facts, a credential card with its masked last four, and diagnostics as tiles (#867).
|
|
59
|
+
- The operator UI's Endpoints, access matrix and Tokens pages adopt the calm design. Endpoint rows show who can use them as an avatar stack, their connectors as monograms and their tools over a read/write bar, with a read-only pill only where it applies; client setup switches between Claude Code, Codex and JSON over one snippet. Matrix cells show the share of a connector they grant as a pie, read-only columns are hatched, and the Status and Runs as columns give way to a status dot and each cell's popover, which marks every tool read or write. Tokens rows show the agent mark, pool chips, a state pill and dates, with one primary action at most, and stack into cards on a narrow screen. Matrix shares count only live granted tools, show unknown coverage when the catalog is unavailable, and name both granted and total live counts for assistive technology.
|
|
60
|
+
- Develop the repository with pnpm instead of npm: `pnpm-lock.yaml` replaces `package-lock.json`, `pnpm-workspace.yaml` allows only the esbuild and workerd build scripts, CI installs with `pnpm install --frozen-lockfile`, and `check:security` runs `pnpm audit`. The published package, its install instructions, and the Node template still use npm, and releases still publish through `npm publish` with trusted publishing.
|
|
61
|
+
- Settings and the connector Configuration card show configuration as tiles instead of a key/value tree. Deployment facts are chips (name with server version, Writes allowed or Read-only, and a warning chip for unconfigured memory storage); sign-in shows provider tiles, an agent count and one Maintainers tile; features show on or off; headline limits draw large with an accent dot when set in code, and the remaining defaults fold into one "17 defaults" disclosure of grouped cards. Sizes read in decimal units with the exact byte count as a tooltip, and a null tool timeout shows the host-call timeout it falls back to. A connector's card adds an auth row: mode, header names, OAuth scope, API origins and OAuth endpoints as compact badges. The D1 and SQLite activity stores report whether code set `retentionDays`, and Settings marks retention set only then. Snapshot keys the UI does not map still appear as labelled tiles.
|
|
62
|
+
|
|
63
|
+
Open deployments retain the Maintainers tile beside Open, with "None" when no maintainers are configured.
|
|
64
|
+
- The operator UI uses shorter copy throughout (#867): labels, empty and error states, confirmations, notices, gates and the OAuth callback and 404 pages, with no em dashes. Page titles separate with a middle dot. On the Overview, narrow stat tiles sit two up, the map draws a muted dash instead of "0 tools" for a connector whose catalog is unknown, and caller chips in the narrow list link like map nodes.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Correct inaccurate guidance across the shipped docs, provider skills, and the usage skill: token-route statuses, OAuth control labels, meta-tool paging and timing, host-call budgets, error codes, doctor output and credentials, local `.env` use, Worker secrets and copied-repo setup, the 0.33 Node template script upgrade, and provider argument names and write classifications. Rewrite the README to explain people, agents, pools, and account ownership with diagrams, and drop the hero image.
|
|
69
|
+
|
|
5
70
|
## 0.33.0 — 2026-10-16
|
|
6
71
|
|
|
7
72
|
Connecta 0.33.0 makes pools the access model, and every deployment with inbound auth must change its configuration (#837). Work through `documentation/migration-0.33.md` before upgrading; it maps every removed option to its replacement. A new top-level `maintainers` list names the people who reach plain `/mcp` and administer the deployment. Each pool now has a static `members` list and serves exactly its `tools` to those members. Agents are declared by name in `agents`, each with one rotatable `cta_` token. Each removed option fails at startup with a message naming its replacement, so a deployment that starts has nothing silently ignored: `identity` (even `{}`) and its `connectorAccess`, `credentialAdministration`, `personalConnection`, `accessTokenManagement` and `activityAccess`, plus the top-level `grant` and pool `grant`.
|
package/README.md
CHANGED
|
@@ -1,157 +1,129 @@
|
|
|
1
1
|
# connecta
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**One connection between your agent and the services you choose.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Connecta puts your MCP servers and HTTP APIs behind one endpoint that you run
|
|
6
|
+
yourself. Connect your agent once; decide in code who can reach what.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
Connect your agent once, then choose which integrations it can use. You run
|
|
9
|
-
Connecta yourself, on your own machine or infrastructure.
|
|
10
|
-
|
|
11
|
-
## The mental model
|
|
12
|
-
|
|
13
|
-
Ask your agent to find open issues, compare them with customer feedback, or
|
|
14
|
-
prepare a report. Connecta gives it a small set of tools for finding and using
|
|
15
|
-
the services you connected, without loading every service's tool definitions
|
|
16
|
-
into the conversation.
|
|
17
|
-
|
|
18
|
-
The agent writes a short program that runs in a sandbox beside those
|
|
19
|
-
integrations. It can gather results, combine them, and return the parts that
|
|
20
|
-
answer your question. Fifty issues can become a short list grouped by owner
|
|
21
|
-
before the result reaches the conversation. The agent writes the code; you
|
|
22
|
-
ask for the work.
|
|
8
|
+
## How it works
|
|
23
9
|
|
|
24
10
|
```mermaid
|
|
25
11
|
flowchart LR
|
|
26
|
-
Agent["Your agent"] <-->|"One connection"| Connecta
|
|
27
|
-
|
|
28
|
-
Connecta
|
|
29
|
-
|
|
12
|
+
Agent["Your agent"] <-->|"One connection<br/>six small tools"| Connecta
|
|
13
|
+
|
|
14
|
+
subgraph Connecta["Your Connecta deployment"]
|
|
15
|
+
Sandbox["Sandbox<br/>runs the agent's short programs"]
|
|
16
|
+
Keys["Credentials<br/>stay on the server"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Connecta <--> Work["Linear, GitHub, Notion"]
|
|
20
|
+
Connecta <--> Business["Stripe, RevenueCat, Mixpanel"]
|
|
21
|
+
Connecta <--> Yours["Any remote MCP server<br/>or HTTP API"]
|
|
30
22
|
|
|
31
23
|
style Connecta fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
|
|
32
24
|
```
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
only the
|
|
37
|
-
|
|
26
|
+
The agent doesn't load every service's tools into the conversation. It writes
|
|
27
|
+
a short program that gathers, joins, and trims results inside Connecta, so
|
|
28
|
+
only the answer comes back.
|
|
29
|
+
|
|
30
|
+
## What you can connect
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
Maintained connections: <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Infisical, Intercom, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, Vercel, and Wispr Flow<!-- providers:end -->.
|
|
33
|
+
Plus any remote MCP server or HTTP API you describe.
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
## Who can use it
|
|
36
|
+
|
|
37
|
+
| | People | Agents |
|
|
38
|
+
| ----------------- | ---------------------------------------------------- | ---------------------------------------- |
|
|
39
|
+
| What they are | You and your team | Bots, assistants, CI, scheduled jobs |
|
|
40
|
+
| How they prove it | Sign in: Clerk (Node) or Cloudflare Access (Workers) | One token Connecta issues to that agent |
|
|
41
|
+
| Their own logins | Yes, on personal connections | No. An agent uses accounts you assign it |
|
|
42
|
+
|
|
43
|
+
**Maintainers** are the people who run the deployment: they reach every tool
|
|
44
|
+
and manage credentials and agent tokens. Everyone else reaches only the pools
|
|
45
|
+
they are listed in.
|
|
46
|
+
|
|
47
|
+
### Pools
|
|
48
|
+
|
|
49
|
+
A **pool** is a named set of tools plus a list of who may use it. Each pool
|
|
50
|
+
has its own address, and every caller sees exactly that pool's tools.
|
|
44
51
|
|
|
45
52
|
```mermaid
|
|
46
|
-
flowchart
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
style Combine fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
|
|
56
|
-
style Answer fill:#dcfce7,stroke:#15803d,color:#14532d
|
|
53
|
+
flowchart LR
|
|
54
|
+
Maint["Maintainer<br/>(signs in)"] --> Main["/mcp<br/>every tool"]
|
|
55
|
+
Bob["Bob<br/>(signs in)"] --> Team["/mcp/team<br/>CRM + tickets"]
|
|
56
|
+
Bot["assistant agent<br/>(token)"] --> AgentPool["/agent/assistant<br/>CRM + tickets"]
|
|
57
|
+
|
|
58
|
+
style Main fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b
|
|
59
|
+
style Team fill:#dcfce7,stroke:#15803d,color:#14532d
|
|
60
|
+
style AgentPool fill:#fef3c7,stroke:#b45309,color:#78350f
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Connecta includes maintained connections for <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Infisical, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, Vercel, and Wispr Flow<!-- providers:end -->.
|
|
71
|
-
Each connection has its own setup guide.
|
|
72
|
-
Stripe, Notion, Cloudflare, and Vercel use their hosted MCP servers when you
|
|
73
|
-
sign in with OAuth, and Connecta's own API connections when you use an API key,
|
|
74
|
-
a Cloudflare API token or Global API Key, a Notion integration token, or a
|
|
75
|
-
Vercel access token. You can also
|
|
76
|
-
connect other remote MCP servers or define the operations you need from an
|
|
77
|
-
HTTP API.
|
|
78
|
-
|
|
79
|
-
Google Workspace connections use the account your deployment assigns to each
|
|
80
|
-
signed-in person. A Workspace administrator authorizes them for your domain.
|
|
81
|
-
Gmail reads mail and attachments and writes drafts, but does not send mail.
|
|
82
|
-
Google Drive can read, write, and share files, but does not permanently delete
|
|
83
|
-
them. Available
|
|
84
|
-
operations and account access depend on the integration and your deployment.
|
|
85
|
-
|
|
86
|
-
Large results need not fill the conversation. The agent can filter and
|
|
87
|
-
summarize them in its program, or read a large result in smaller pieces.
|
|
88
|
-
|
|
89
|
-
## Writes, credentials, and access
|
|
90
|
-
|
|
91
|
-
Connecta distinguishes reads from writes, and each endpoint is either writable
|
|
92
|
-
or read-only. By default an endpoint is writable: your agent can make a change
|
|
93
|
-
with one visible tool call or inside a program, and your agent's host decides
|
|
94
|
-
whether to ask for approval; it sees the program tool as a write. You can mark
|
|
95
|
-
the whole deployment or a single endpoint read-only, and then nothing there can
|
|
96
|
-
change anything: search leaves write tools out and every write is refused. You
|
|
97
|
-
make this choice explicitly for each deployment. Neither choice gives a caller
|
|
98
|
-
access to additional services.
|
|
63
|
+
People use a pool at `/mcp/<pool>` and agents at `/agent/<pool>`. A caller
|
|
64
|
+
outside a pool gets the same "not found" as a pool that doesn't exist, and
|
|
65
|
+
someone in two pools uses two addresses: pools never merge.
|
|
66
|
+
|
|
67
|
+
### One agent, many people
|
|
68
|
+
|
|
69
|
+
An assistant that talks to several people keeps **one token and one
|
|
70
|
+
connection**. Its harness, not the model, picks the pool each turn: a chat
|
|
71
|
+
gets the most capable tier that everyone in it is cleared for, and a
|
|
72
|
+
one-on-one gets that person's own pool.
|
|
99
73
|
|
|
100
74
|
```mermaid
|
|
101
|
-
flowchart
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
style
|
|
75
|
+
flowchart LR
|
|
76
|
+
Guests["Chat with anyone unknown"] --> Base["/agent/assistant<br/>read-only basics"]
|
|
77
|
+
Group["Chat of team members"] --> TeamTier["/agent/assistant-team<br/>shared tools"]
|
|
78
|
+
Leads["Chat of leads only"] --> LeadsTier["/agent/assistant-leads<br/>shared tools + billing"]
|
|
79
|
+
Alice["One-on-one<br/>with Alice"] --> PA["/agent/assistant-alice<br/>her tier + her accounts"]
|
|
80
|
+
|
|
81
|
+
Base -.-> TeamTier -.-> LeadsTier
|
|
82
|
+
|
|
83
|
+
style Base fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b
|
|
84
|
+
style TeamTier fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b
|
|
85
|
+
style LeadsTier fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b
|
|
86
|
+
style PA fill:#dcfce7,stroke:#15803d,color:#14532d
|
|
110
87
|
```
|
|
111
88
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
when, and whether it worked, without recording arguments, results, or program
|
|
134
|
-
code. Pool members connect their own accounts on personal connections.
|
|
135
|
-
|
|
136
|
-
Your agent or developer changes the integrations and access rules in deployment
|
|
137
|
-
code. The UI helps you inspect them and manage authentication.
|
|
89
|
+
Each tier builds on the one below it. A person's own pool builds on the
|
|
90
|
+
highest tier they are cleared for and holds only the agent, since anyone else
|
|
91
|
+
in it would act as that person. A tier can double as a team's own pool for
|
|
92
|
+
the people cleared for it. [Organizing an agent's pools](./documentation/auth.md#organizing-an-agents-pools)
|
|
93
|
+
has the full layout.
|
|
94
|
+
|
|
95
|
+
### Whose account a connection uses
|
|
96
|
+
|
|
97
|
+
- **Shared** (the default): the deployment's account, connected once by a
|
|
98
|
+
maintainer.
|
|
99
|
+
- **Personal**: each person's own account. Agents never see these.
|
|
100
|
+
- **Google Workspace**: the signed-in person's account; for an agent, the
|
|
101
|
+
owner of a person's pool, or one fixed shared account. Without an owner,
|
|
102
|
+
mapping connections are absent for agents.
|
|
103
|
+
|
|
104
|
+
## Reads and writes
|
|
105
|
+
|
|
106
|
+
Every endpoint is writable or read-only, and you choose explicitly. A
|
|
107
|
+
read-only endpoint hides write tools and refuses every write. You can also make
|
|
108
|
+
one connection read-only across all endpoints. On a writable
|
|
109
|
+
one, your agent's host decides what to approve.
|
|
138
110
|
|
|
139
111
|
## Where it runs
|
|
140
112
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
113
|
+
Node or Docker (Clerk sign-in, SQLite) or Cloudflare Workers (Cloudflare
|
|
114
|
+
Access, D1). Both serve the same agent tools. An optional operator UI shows
|
|
115
|
+
a connection map, endpoints, connectors, and activity; integrations and access
|
|
116
|
+
still change only in deployment code.
|
|
144
117
|
|
|
145
118
|
## Getting started
|
|
146
119
|
|
|
147
|
-
Give your agent the [agent documentation](./documentation/README.md) and ask
|
|
148
|
-
to set up Connecta with the integrations you want
|
|
149
|
-
|
|
150
|
-
Cloudflare Workers, including connecting your agent client.
|
|
120
|
+
Give your agent the [agent documentation](./documentation/README.md) and ask
|
|
121
|
+
it to set up Connecta with the integrations you want, where to run it, and who
|
|
122
|
+
should have access.
|
|
151
123
|
|
|
152
124
|
<a id="existing-client-tokens"></a>
|
|
153
125
|
|
|
154
|
-
|
|
126
|
+
Upgrading? Point your agent at the
|
|
155
127
|
[upgrade guidance](./documentation/deploying.md#upgrade-an-existing-deployment).
|
|
156
128
|
|
|
157
129
|
## Status
|
|
@@ -160,7 +132,7 @@ Built for its author's deployments first and published openly. Breaking
|
|
|
160
132
|
changes are expected before 1.0. See the [changelog](./CHANGELOG.md) and
|
|
161
133
|
[security policy](./SECURITY.md).
|
|
162
134
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
135
|
+
[Principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md) ·
|
|
136
|
+
[Decision records](https://github.com/zackbart/connecta/tree/main/decisions) ·
|
|
137
|
+
[Planned work](https://github.com/zackbart/connecta/issues) (a plan is not a
|
|
138
|
+
shipped feature)
|
package/SECURITY.md
CHANGED
|
@@ -20,5 +20,5 @@ The MCP TypeScript SDK 2.x packages and Effect are exact-pinned. Effect is the
|
|
|
20
20
|
runtime core, and its unstable modules can break in a minor release, so its pin
|
|
21
21
|
moves in a dedicated upgrade. The split client and server packages no longer
|
|
22
22
|
install Hono or `@hono/node-server`, so the temporary root-project override
|
|
23
|
-
used with SDK 1.x is gone. A dependency bump must pass `
|
|
23
|
+
used with SDK 1.x is gone. A dependency bump must pass `pnpm run release:check`
|
|
24
24
|
and the Node/Workers runtime suites before a pin moves.
|
package/bin/version.mjs
CHANGED
package/dist/activity.d.ts
CHANGED
|
@@ -126,10 +126,13 @@ export interface ActivityStore extends ActivitySink {
|
|
|
126
126
|
/**
|
|
127
127
|
* The adapter and its retention, for `Connecta.describeConfig()`. Kinds
|
|
128
128
|
* other than the shipped `"d1"` and `"sqlite"` are described as `"custom"`.
|
|
129
|
+
* `optionSources` says whether code set `retentionDays` (`"config"`) or the
|
|
130
|
+
* store chose it (`"default"`); without it the retention reads as a default.
|
|
129
131
|
*/
|
|
130
132
|
describe?(): {
|
|
131
133
|
kind: string;
|
|
132
134
|
retentionDays?: number;
|
|
135
|
+
optionSources?: Readonly<Record<string, "default" | "config">>;
|
|
133
136
|
};
|
|
134
137
|
}
|
|
135
138
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
package/dist/branding.d.ts
CHANGED
|
@@ -99,6 +99,13 @@ export interface PageLayout {
|
|
|
99
99
|
* `mastheadEnd`, and `tail` are the caller's markup and are not.
|
|
100
100
|
*/
|
|
101
101
|
export declare function renderPage(branding: ConnectaBranding | undefined, layout: PageLayout): string;
|
|
102
|
+
/**
|
|
103
|
+
* The mark beside the brand name. Connecta's drawn "C" belongs to an
|
|
104
|
+
* unbranded deployment only; a deployment that serves its own SVG favicon
|
|
105
|
+
* shows that; any other branding shows its name alone.
|
|
106
|
+
*/
|
|
107
|
+
export type BrandMark = "connecta" | "favicon" | "none";
|
|
108
|
+
export declare function brandMark(branding: ConnectaBranding | undefined, uiMounted: boolean): BrandMark;
|
|
102
109
|
/**
|
|
103
110
|
* Status marks for one-message pages. Decorative: the label and heading beside
|
|
104
111
|
* each carry the meaning, so the shape repeats it and color is never alone.
|
package/dist/branding.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PAGE_CSS, TOKENS_CSS } from "./page-styles.js";
|
|
1
|
+
import { PAGE_CSS, PAGE_FONTS, TOKENS_CSS } from "./page-styles.js";
|
|
2
2
|
import { htmlSecurityHeaders } from "./html-security.js";
|
|
3
3
|
/** Connecta's default monochrome "C" mark. */
|
|
4
4
|
export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
@@ -9,6 +9,7 @@ export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" vie
|
|
|
9
9
|
<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"/>
|
|
10
10
|
</svg>`;
|
|
11
11
|
const DEFAULT_FAVICON_HREF = "/favicon.svg";
|
|
12
|
+
const DEFAULT_PRODUCT_NAME = "Connecta";
|
|
12
13
|
/**
|
|
13
14
|
* Branding arrives from operator config, which is untyped at a JS call site, so
|
|
14
15
|
* every field is treated as `unknown`: a non-string is read as unset rather than
|
|
@@ -19,7 +20,7 @@ function trimmedString(value) {
|
|
|
19
20
|
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
20
21
|
}
|
|
21
22
|
export function resolveBranding(branding) {
|
|
22
|
-
const productName = trimmedString(branding?.productName) ??
|
|
23
|
+
const productName = trimmedString(branding?.productName) ?? DEFAULT_PRODUCT_NAME;
|
|
23
24
|
const ownerName = trimmedString(branding?.ownerName);
|
|
24
25
|
// Operator branding URLs become masthead/callback hrefs, so a non-http(s)
|
|
25
26
|
// scheme (javascript:, data:) is dropped the same as an unset URL — the
|
|
@@ -34,7 +35,7 @@ export function resolveBranding(branding) {
|
|
|
34
35
|
...(ownerUrl && isSafeHttpUrl(ownerUrl) ? { ownerUrl } : {}),
|
|
35
36
|
description: trimmedString(branding?.description) ??
|
|
36
37
|
`Manage the services this ${productName} instance makes available to agents.`,
|
|
37
|
-
pageTitle: trimmedString(branding?.pageTitle) ?? (ownerName ? `${productName}
|
|
38
|
+
pageTitle: trimmedString(branding?.pageTitle) ?? (ownerName ? `${productName} · ${ownerName}` : productName),
|
|
38
39
|
faviconHref: faviconHref && isSafeIconHref(faviconHref) ? faviconHref : DEFAULT_FAVICON_HREF,
|
|
39
40
|
themeColor: configuredThemeColor(branding) ?? "#ffffff",
|
|
40
41
|
theme: resolveTheme(branding?.theme),
|
|
@@ -128,6 +129,46 @@ export function droppedThemeTokens(theme) {
|
|
|
128
129
|
}
|
|
129
130
|
return dropped.map((token) => `theme.${token}`);
|
|
130
131
|
}
|
|
132
|
+
function rgbOf(hex) {
|
|
133
|
+
const digits = hex.slice(1);
|
|
134
|
+
// `#rgb` and `#rgba` double each digit; alpha never affects contrast here.
|
|
135
|
+
const short = digits.length <= 4;
|
|
136
|
+
const pairs = [0, 1, 2].map((i) => (short ? digits[i].repeat(2) : digits.slice(i * 2, i * 2 + 2)));
|
|
137
|
+
return pairs.map((pair) => parseInt(pair, 16));
|
|
138
|
+
}
|
|
139
|
+
function luminance([r, g, b]) {
|
|
140
|
+
const channel = (value) => {
|
|
141
|
+
const c = value / 255;
|
|
142
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
143
|
+
};
|
|
144
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
145
|
+
}
|
|
146
|
+
function contrast(a, b) {
|
|
147
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
|
148
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
149
|
+
}
|
|
150
|
+
const hexOf = (rgb) => `#${rgb.map((c) => Math.round(c).toString(16).padStart(2, "0")).join("")}`;
|
|
151
|
+
/** The dark palette's lightest surface that carries accent text (`--hover`). */
|
|
152
|
+
const DARK_TEXT_GROUND = [0x24, 0x22, 0x1e];
|
|
153
|
+
/**
|
|
154
|
+
* A configured accent's two derived colors. Text on its fill is white or
|
|
155
|
+
* near-black, whichever reads better; accent text on dark surfaces mixes the
|
|
156
|
+
* accent with white until it reaches 4.5:1, starting from the built-in
|
|
157
|
+
* accent's 80% so a light accent keeps its color.
|
|
158
|
+
*/
|
|
159
|
+
function accentTokens(accent) {
|
|
160
|
+
const rgb = rgbOf(accent);
|
|
161
|
+
const ink = [0x0f, 0x0e, 0x1f];
|
|
162
|
+
const onAccent = contrast(rgb, [255, 255, 255]) >= contrast(rgb, ink) ? "#ffffff" : hexOf(ink);
|
|
163
|
+
let darkText = hexOf(rgb.map((c) => c * 0.8 + 255 * 0.2));
|
|
164
|
+
for (let share = 0.8; share >= 0; share -= 0.05) {
|
|
165
|
+
const mixed = rgb.map((c) => c * share + 255 * (1 - share));
|
|
166
|
+
darkText = hexOf(mixed);
|
|
167
|
+
if (contrast(mixed, DARK_TEXT_GROUND) >= 4.5)
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
return { onAccent, darkText };
|
|
171
|
+
}
|
|
131
172
|
/**
|
|
132
173
|
* The resolved theme as a `:root` block, or "" when a deployment configured
|
|
133
174
|
* nothing. It is emitted after the stylesheet so it overrides the defaults.
|
|
@@ -135,8 +176,10 @@ export function droppedThemeTokens(theme) {
|
|
|
135
176
|
* anything that would need escaping is dropped rather than rewritten.
|
|
136
177
|
*/
|
|
137
178
|
export function themeCss(theme) {
|
|
179
|
+
const derived = theme.accent ? accentTokens(theme.accent) : undefined;
|
|
138
180
|
const declarations = [
|
|
139
181
|
theme.accent ? `--accent:${theme.accent}` : "",
|
|
182
|
+
derived ? `--custom-on-accent:${derived.onAccent};--custom-accent-text:${derived.darkText}` : "",
|
|
140
183
|
theme.radius !== undefined ? `--radius:${theme.radius}` : "",
|
|
141
184
|
theme.fontFamily ? `--sans:${theme.fontFamily}` : "",
|
|
142
185
|
theme.monoFamily ? `--mono:${theme.monoFamily}` : "",
|
|
@@ -156,11 +199,11 @@ export function escapeHtml(value) {
|
|
|
156
199
|
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
157
200
|
}
|
|
158
201
|
/**
|
|
159
|
-
* The
|
|
202
|
+
* The top bar's `--bg` in each palette, for the browser chrome a
|
|
160
203
|
* `theme-color` paints. A configured `themeColor` wins; without one a dark
|
|
161
204
|
* page should not sit under a white status bar.
|
|
162
205
|
*/
|
|
163
|
-
const SURFACE = { light: "#
|
|
206
|
+
const SURFACE = { light: "#f6f4ef", dark: "#12110f" };
|
|
164
207
|
/** The operator's own `themeColor`, read the one way both callers read it. */
|
|
165
208
|
function configuredThemeColor(branding) {
|
|
166
209
|
return trimmedString(branding?.themeColor);
|
|
@@ -202,6 +245,7 @@ export function renderPage(branding, layout) {
|
|
|
202
245
|
: label("brand", brand.productName, brand.productUrl);
|
|
203
246
|
const product = brand.ownerName ? label("product", brand.productName, brand.productUrl) : "";
|
|
204
247
|
const customIcon = brand.faviconHref !== DEFAULT_FAVICON_HREF;
|
|
248
|
+
const mark = brandMarkHtml(brandMark(branding, layout.uiMounted));
|
|
205
249
|
// `isSafeIconHref` already limited a relative href to one leading slash, so
|
|
206
250
|
// resolving it can only land on `iconOrigin` itself.
|
|
207
251
|
const iconHref = (href) => layout.iconOrigin && href.startsWith("/") ? new URL(href, layout.iconOrigin).toString() : href;
|
|
@@ -214,7 +258,13 @@ export function renderPage(branding, layout) {
|
|
|
214
258
|
const skip = layout.skipTo
|
|
215
259
|
? `<a class="skip-link" href="#${escapeHtml(layout.skipTo.id)}">${escapeHtml(layout.skipTo.label)}</a>\n`
|
|
216
260
|
: "";
|
|
217
|
-
|
|
261
|
+
// The operator bundle declares its own fonts. Other pages link the same
|
|
262
|
+
// files only where the UI module serves them, root-relative so the page's
|
|
263
|
+
// own origin serves them; elsewhere the stacks fall back to system fonts.
|
|
264
|
+
const fonts = layout.uiMounted
|
|
265
|
+
? PAGE_FONTS.map((font) => `@font-face{font-family:"${font.family}";font-style:normal;font-weight:${font.weight};font-display:swap;src:url("${font.path}") format("woff2")}`).join("")
|
|
266
|
+
: "";
|
|
267
|
+
const styles = (layout.styles ?? fonts + TOKENS_CSS + PAGE_CSS) + (layout.extraStyles ?? "");
|
|
218
268
|
return `<!doctype html>
|
|
219
269
|
<html lang="en"${schemeAttribute(brand.theme)}>
|
|
220
270
|
<head>
|
|
@@ -231,7 +281,7 @@ ${layout.head ?? ""}
|
|
|
231
281
|
${skip}<header class="masthead shell">
|
|
232
282
|
<div class="masthead-inner">
|
|
233
283
|
<div class="mast-nav">
|
|
234
|
-
${owner}${product ? `\n ${product}` : ""}
|
|
284
|
+
${mark}${owner}${product ? `\n ${product}` : ""}
|
|
235
285
|
</div>${layout.mastheadEnd ? `\n ${layout.mastheadEnd}` : ""}
|
|
236
286
|
</div>
|
|
237
287
|
</header>
|
|
@@ -239,6 +289,23 @@ ${layout.body}${layout.tail ? `\n${layout.tail}` : ""}
|
|
|
239
289
|
</body>
|
|
240
290
|
</html>`;
|
|
241
291
|
}
|
|
292
|
+
export function brandMark(branding, uiMounted) {
|
|
293
|
+
if (uiMounted && trimmedString(branding?.favicon?.svg))
|
|
294
|
+
return "favicon";
|
|
295
|
+
const brand = resolveBranding(branding);
|
|
296
|
+
const unbranded = brand.productName === DEFAULT_PRODUCT_NAME &&
|
|
297
|
+
!brand.ownerName &&
|
|
298
|
+
brand.faviconHref === DEFAULT_FAVICON_HREF &&
|
|
299
|
+
!branding?.favicon?.svg;
|
|
300
|
+
return unbranded ? "connecta" : "none";
|
|
301
|
+
}
|
|
302
|
+
function brandMarkHtml(mark) {
|
|
303
|
+
if (mark === "connecta")
|
|
304
|
+
return '<span class="brand-mark" aria-hidden="true"></span>';
|
|
305
|
+
if (mark === "favicon")
|
|
306
|
+
return `<img class="brand-mark" src="${DEFAULT_FAVICON_HREF}" alt="">`;
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
242
309
|
/**
|
|
243
310
|
* Status marks for one-message pages. Decorative: the label and heading beside
|
|
244
311
|
* each carry the meaning, so the shape repeats it and color is never alone.
|
|
@@ -281,12 +348,12 @@ export function notFoundResponse(request, opts) {
|
|
|
281
348
|
<p class="status-label">404</p>
|
|
282
349
|
</div>
|
|
283
350
|
<h1>Page not found</h1>
|
|
284
|
-
<p class="status-copy">Nothing is
|
|
351
|
+
<p class="status-copy">Nothing is here. Check the link, or ask whoever runs ${escapeHtml(brand.productName)}.</p>
|
|
285
352
|
${home}
|
|
286
353
|
</section>
|
|
287
354
|
</main>`;
|
|
288
355
|
return new Response(renderPage(opts.ui?.branding, {
|
|
289
|
-
title: `Page not found
|
|
356
|
+
title: `Page not found · ${brand.pageTitle}`,
|
|
290
357
|
uiMounted,
|
|
291
358
|
// Same bytes on either host: the icons, like the home link, come from
|
|
292
359
|
// the public origin.
|