@zackbart/connecta 0.28.0 → 0.29.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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
package/dist/ui.js CHANGED
@@ -1,59 +1,50 @@
1
+ import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
1
2
  import { routeUi } from "./routes/ui.js";
2
3
  import { routeCredentials } from "./routes/credentials.js";
3
4
  import { routeOAuthManagement } from "./routes/oauth-management.js";
4
5
  import { uiData } from "./routes/ui-data.js";
5
6
  import { runEdge } from "./runtime/run.js";
6
- import { isExplicitlyReadOnly } from "./tool-safety.js";
7
- import { OPERATOR_UI_CSS, OPERATOR_UI_SCRIPT, } from "./operator-ui/generated.js";
7
+ import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH } from "./operator-ui/generated.js";
8
8
  export { filterUiConnectors, } from "./operator-ui/model.js";
9
- import { escapeHtml, isSafeHttpsUrl, notFoundResponse, renderPage, resolveBranding, } from "./branding.js";
9
+ import { escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
10
10
  export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
11
- /**
12
- * A JS string literal safe to inline in a script element. Escaping `/` keeps
13
- * an operator-supplied `</script>` from terminating the element early.
14
- */
15
- function stringForInlineScript(value) {
16
- return JSON.stringify(value).replace(/\//g, "\\/");
17
- }
18
11
  const OPERATOR_PAGE_LABELS = {
19
- connections: "Connections",
12
+ overview: "Overview",
13
+ connections: "Connectors",
14
+ connector: "Connector",
15
+ tools: "Tools",
16
+ access: "Access",
17
+ config: "Config",
20
18
  tokens: "Access tokens",
21
19
  activity: "Activity",
22
- artifacts: "Artifacts",
23
- artifact: "Artifact",
24
20
  };
25
- /** `/artifacts/<id>` and its snapshots, `/artifacts/<id>/v/<version>`. */
26
- const ARTIFACT_PAGE = /^\/artifacts\/[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?(?:\/v\/\d{1,9})?$/;
27
21
  export function operatorPageForPath(path) {
28
- if (path === "/tokens")
29
- return "tokens";
30
22
  if (path === "/")
23
+ return "overview";
24
+ if (path === "/connectors")
31
25
  return "connections";
26
+ if (/^\/connectors\/[a-z0-9_-]+$/.test(path))
27
+ return "connector";
28
+ if (path === "/tools")
29
+ return "tools";
30
+ if (path === "/access")
31
+ return "access";
32
+ if (path === "/config")
33
+ return "config";
34
+ if (path === "/tokens")
35
+ return "tokens";
32
36
  if (path === "/activity")
33
37
  return "activity";
34
- if (path === "/artifacts")
35
- return "artifacts";
36
- if (ARTIFACT_PAGE.test(path))
37
- return "artifact";
38
38
  return undefined;
39
39
  }
40
- /** Everything artifact pages own: shells, their API, and the frame. */
41
- function isArtifactPath(path) {
42
- return path === "/artifacts" || path.startsWith("/artifacts/");
43
- }
44
40
  export function operatorPageTitle(page, configuredTitle) {
45
41
  return `${OPERATOR_PAGE_LABELS[page]} — ${configuredTitle}`;
46
42
  }
47
- /**
48
- * The badge a tool earns, from the predicates core enforces with. Computed
49
- * here so the page reports the rule rather than restating it. `exempt` is the
50
- * config exemption (#566), which `isApprovalExempt` already refuses to grant
51
- * a read-only tool.
52
- */
53
- export function uiToolSafety(definition, exempt = false) {
54
- if (isExplicitlyReadOnly(definition))
43
+ /** Report the verdict the registry stored, independent of pool trust. */
44
+ export function uiToolSafety(definition) {
45
+ if (definition.classification === "read")
55
46
  return "runs_in_programs";
56
- return exempt ? "exempt" : "needs_approval";
47
+ return "needs_approval";
57
48
  }
58
49
  /**
59
50
  * The fix-prompt key for a connector that is not usable. Chosen from the
@@ -63,6 +54,8 @@ export function uiToolSafety(definition, exempt = false) {
63
54
  export function uiProblemFor(connector, status, observed) {
64
55
  if (observed.credentialDrift)
65
56
  return "credential_mismatch";
57
+ if (status === "credential_required")
58
+ return "credential_required";
66
59
  if (status === "error")
67
60
  return "connector_unavailable";
68
61
  if (status === "auth_required") {
@@ -86,9 +79,7 @@ export function credentialManagementCapability(input) {
86
79
  * isolated: they surface status "error" with an empty tool list rather than
87
80
  * failing the whole payload.
88
81
  */
89
- export async function buildUiData(registry, baseUrl, serverInfo, credentialVault, activityEnabled = false, credentialManagement = credentialVault
90
- ? "available"
91
- : "requires_operator", defer, oauthManagement = false, discoveryConcurrency, personalCredentialOwner, detailOptions = {}) {
82
+ export async function buildUiData(registry, baseUrl, serverInfo, credentialVault, activityEnabled = false, credentialManagement = credentialVault ? "available" : "requires_operator", defer, oauthManagement = false, discoveryConcurrency, personalCredentialOwner, detailOptions = {}) {
92
83
  return runEdge(uiData(registry, baseUrl, {
93
84
  serverInfo,
94
85
  credentialVault,
@@ -102,27 +93,24 @@ export async function buildUiData(registry, baseUrl, serverInfo, credentialVault
102
93
  }));
103
94
  }
104
95
  function jsonForInlineScript(value) {
105
- return JSON.stringify(value)
106
- .replaceAll("<", "\\u003c")
107
- .replaceAll(">", "\\u003e")
108
- .replaceAll("&", "\\u0026");
96
+ return JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("&", "\\u0026");
109
97
  }
110
98
  /**
111
99
  * Every operator page serves this same data-free shell. Connector, credential,
112
100
  * and activity data arrives only through the authenticated `/ui/*` APIs.
113
101
  */
114
- export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "connections", options = {}) {
102
+ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding,
103
+ /** @deprecated Scripts load from same-origin assets; ignored. */
104
+ _nonce, page = "overview") {
115
105
  const clerk = uiAuth?.kind === "clerk" ? uiAuth : undefined;
116
106
  // The Clerk loader's origin. A value that fails the gate is dropped rather
117
107
  // than escaped into the page: the loader tag is simply not emitted, the gate
118
108
  // reports that Clerk could not load, and the rest of the shell still renders —
119
109
  // the same fallback-and-warn posture the branding URLs take, with the drop
120
110
  // named in a startup warning (see `droppedUiAuthUrls`).
121
- const clerkScriptOrigin = clerk && isSafeHttpsUrl(clerk.frontendApiUrl)
122
- ? clerk.frontendApiUrl
123
- : undefined;
111
+ const clerkScriptOrigin = clerk && isSafeHttpsUrl(clerk.frontendApiUrl) ? clerk.frontendApiUrl : undefined;
124
112
  // Enumerated field by field, because this object is serialized into the page's
125
- // inline script: a rejected frontendApiUrl must not reach the document through
113
+ // JSON config: a rejected frontendApiUrl must not reach the document through
126
114
  // `AUTH` after being kept out of the `<script src>`, and a rejected
127
115
  // signInUrl/signUpUrl — which `AUTH` is the only path into the page for — must
128
116
  // not reach it at all. Dropping one leaves the key absent, so `Clerk.load`
@@ -132,20 +120,13 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
132
120
  kind: clerk.kind,
133
121
  publishableKey: clerk.publishableKey,
134
122
  ...(clerkScriptOrigin ? { frontendApiUrl: clerkScriptOrigin } : {}),
135
- ...(isSafeHttpsUrl(clerk.signInUrl)
136
- ? { signInUrl: clerk.signInUrl }
137
- : {}),
138
- ...(isSafeHttpsUrl(clerk.signUpUrl)
139
- ? { signUpUrl: clerk.signUpUrl }
140
- : {}),
123
+ ...(isSafeHttpsUrl(clerk.signInUrl) ? { signInUrl: clerk.signInUrl } : {}),
124
+ ...(isSafeHttpsUrl(clerk.signUpUrl) ? { signUpUrl: clerk.signUpUrl } : {}),
141
125
  }
142
126
  : (uiAuth ?? { kind: "bearer" });
143
127
  const brand = resolveBranding(branding);
144
- // When an operator shell ships a nonce-based CSP, every script it emits must
145
- // carry that nonce to run; without a nonce the markup is unchanged.
146
- const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
147
128
  const clerkScript = clerk && clerkScriptOrigin
148
- ? `<script${nonceAttr} crossorigin="anonymous" data-clerk-publishable-key="${escapeHtml(clerk.publishableKey)}" src="${escapeHtml(clerkScriptOrigin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
129
+ ? `<script crossorigin="anonymous" data-clerk-publishable-key="${escapeHtml(clerk.publishableKey)}" src="${escapeHtml(clerkScriptOrigin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
149
130
  : "";
150
131
  // The shared layout owns the head, scheme, theme, and masthead; the shell
151
132
  // adds its own bundle (which already imports the shared tokens), the nav
@@ -153,9 +134,10 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
153
134
  return renderPage(branding, {
154
135
  title: operatorPageTitle(page, brand.pageTitle),
155
136
  uiMounted: true,
156
- iconOrigin: options.iconOrigin,
157
- styles: OPERATOR_UI_CSS,
158
- head: clerkScript,
137
+ operatorShell: true,
138
+ styles: "",
139
+ head: `<link rel="stylesheet" href="${OPERATOR_UI_STYLE_PATH}"><link rel="license" href="${OPERATOR_UI_NOTICES_PATH}">` +
140
+ clerkScript,
159
141
  skipTo: { id: "operatorContent", label: "Skip to operator page" },
160
142
  mastheadEnd: '<div id="operatorNav"></div>',
161
143
  body: `
@@ -170,28 +152,45 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
170
152
  </div>
171
153
  </main>
172
154
  `,
173
- tail: `<script${nonceAttr}>
174
- const AUTH = ${jsonForInlineScript(auth)};
175
- const MCP_URL = ${jsonForInlineScript(mcpUrl)};
176
- const INITIAL_PAGE = ${jsonForInlineScript(page)};
177
- const HOME_URL = ${jsonForInlineScript(options.homeUrl ?? "/")};
178
- const TITLE_SUFFIX = ${jsonForInlineScript(brand.pageTitle)};
179
- const PRODUCT_NAME = ${stringForInlineScript(brand.productName)};
180
- const PRODUCT_DESCRIPTION = ${stringForInlineScript(brand.description)};
181
- const PRODUCT_OPERATOR_LABEL = ${stringForInlineScript(brand.productName + " operator")};
182
- ${OPERATOR_UI_SCRIPT}</script>`,
155
+ tail: `<script id="operatorConfig" type="application/json">${jsonForInlineScript({
156
+ auth,
157
+ mcpUrl,
158
+ initialPage: page,
159
+ titleSuffix: brand.pageTitle,
160
+ productName: brand.productName,
161
+ productDescription: brand.description,
162
+ productOperatorLabel: brand.productName + " operator",
163
+ })}</script><script defer src="${OPERATOR_UI_SCRIPT_PATH}"></script>`,
183
164
  });
184
165
  }
185
166
  function ownsOperatorPath(reserved, path) {
186
167
  if (path === "/activity" || path === "/tokens")
187
168
  return true;
188
- return reserved.some((pattern) => pattern.endsWith("/*")
189
- ? path.startsWith(pattern.slice(0, -1))
190
- : path === pattern);
169
+ return reserved.some((pattern) => pattern.endsWith("/*") ? path.startsWith(pattern.slice(0, -1)) : path === pattern);
191
170
  }
171
+ /** The closed options operatorUi() accepts; see `assertKnownOptions`. */
172
+ const OPERATOR_UI_OPTIONS = optionsOf()({
173
+ branding: optionsOf()({
174
+ ...keys("productName", "productUrl", "ownerName", "ownerUrl", "description", "pageTitle", "themeColor"),
175
+ favicon: optionsOf()(keys("svg", "ico", "href")),
176
+ theme: optionsOf()(keys("accent", "radius", "fontFamily", "monoFamily", "colorScheme")),
177
+ }),
178
+ });
192
179
  /** Mount the connection UI without enabling any storage or activity module. */
193
180
  export function operatorUi(options = {}) {
194
- const reservedPaths = ["/", "/ui", "/ui/*", "/favicon.svg", "/favicon.ico"];
181
+ options = assertKnownOptions(options, "operatorUi()", OPERATOR_UI_OPTIONS);
182
+ const reservedPaths = [
183
+ "/",
184
+ "/connectors",
185
+ "/connectors/*",
186
+ "/tools",
187
+ "/access",
188
+ "/config",
189
+ "/ui",
190
+ "/ui/*",
191
+ "/favicon.svg",
192
+ "/favicon.ico",
193
+ ];
195
194
  return {
196
195
  ...options,
197
196
  reservedPaths,
@@ -203,25 +202,12 @@ export function operatorUi(options = {}) {
203
202
  // route here, and no activity module, runs for a path this surface does
204
203
  // not own. The Activity page is the one addition; the server reserves
205
204
  // it only when history is readable, and routeUi checks the same thing.
206
- // Artifact pages are the other: they exist only beside the module.
207
- const artifacts = context.opts.artifactsModule;
208
- const artifactPath = Boolean(artifacts) && isArtifactPath(context.path);
209
- if (!artifactPath && !ownsOperatorPath(reservedPaths, context.path))
205
+ if (!ownsOperatorPath(reservedPaths, context.path))
210
206
  return null;
211
- if (artifactPath && !operatorPageForPath(context.path)) {
212
- // The pages' JSON API and the sandboxed frame; the module owns both,
213
- // and this bundle imports none of it.
214
- return (await artifacts?.handle(context)) ??
215
- notFoundResponse(context.request, context.opts);
216
- }
217
- const tokenResponse = await context.opts.accessTokens?.handle(context);
207
+ const tokenResponse = await context.opts.config.accessTokens?.handle(context);
218
208
  if (tokenResponse)
219
209
  return tokenResponse;
220
- const routes = [
221
- ...(context.opts.credentialVault ? [routeCredentials] : []),
222
- routeOAuthManagement,
223
- routeUi,
224
- ];
210
+ const routes = [...(context.opts.config.vault ? [routeCredentials] : []), routeOAuthManagement, routeUi];
225
211
  for (const route of routes) {
226
212
  const response = await route(context);
227
213
  if (response) {
@@ -234,7 +220,7 @@ export function operatorUi(options = {}) {
234
220
  return response;
235
221
  }
236
222
  }
237
- return context.opts.activityModule?.handle(context) ?? null;
223
+ return context.opts.config.activity?.handle(context) ?? null;
238
224
  },
239
225
  };
240
226
  }
@@ -48,7 +48,7 @@ function ipv6Pieces(host) {
48
48
  const pieces = (part) => {
49
49
  if (!part)
50
50
  return [];
51
- const parsed = part.split(":").map((piece) => HEXTET.test(piece) ? Number.parseInt(piece, 16) : Number.NaN);
51
+ const parsed = part.split(":").map((piece) => (HEXTET.test(piece) ? Number.parseInt(piece, 16) : Number.NaN));
52
52
  return parsed.some(Number.isNaN) ? undefined : parsed;
53
53
  };
54
54
  const head = pieces(halves[0]);
@@ -89,9 +89,7 @@ function classifyIpv6(pieces) {
89
89
  function canonicalHost(hostname) {
90
90
  if (/[/\\?#@]/.test(hostname))
91
91
  return undefined;
92
- const bracketed = hostname.includes(":") && !hostname.startsWith("[")
93
- ? `[${hostname}]`
94
- : hostname;
92
+ const bracketed = hostname.includes(":") && !hostname.startsWith("[") ? `[${hostname}]` : hostname;
95
93
  try {
96
94
  return new URL(`http://${bracketed}/`).hostname;
97
95
  }
@@ -144,8 +142,7 @@ export function learnedUrlRefusal(configured, target) {
144
142
  if (target.origin === configured.origin)
145
143
  return undefined;
146
144
  const targetClass = classifyHost(target.hostname);
147
- if (targetClass === "loopback" &&
148
- classifyHost(configured.hostname) === "loopback") {
145
+ if (targetClass === "loopback" && classifyHost(configured.hostname) === "loopback") {
149
146
  return undefined;
150
147
  }
151
148
  if (targetClass !== "public") {
@@ -0,0 +1,137 @@
1
+ /** Self-contained content for the usage skill; independent of skill registration. */
2
+ export const USAGE_SKILL = `---
3
+ name: usage
4
+ description: How to route work between one execute_code program and Connecta's explicit call, authorization, and result tools.
5
+ ---
6
+
7
+ # Connecta usage
8
+
9
+ ## Choose the route
10
+
11
+ | Work | Tool |
12
+ | --- | --- |
13
+ | One known-address read | call_tool |
14
+ | One known-address write | call_destructive_tool |
15
+ | Discovery, multiple or dependent calls, paging, reduction | execute_code |
16
+
17
+ For example, read account acct_42 with call_tool arguments
18
+ \`{ "address": "crm.get_account", "args": { "id": "acct_42" } }\`.
19
+ Discover and call in the same program when schemas suffice. Top-level search_tools
20
+ covers catalog inspection and write discovery in read-only pools. Programs may
21
+ write only in trusted pools; the tool description states this endpoint's trust.
22
+ Unannotated tools fail closed to writes.
23
+
24
+ ## The global and the envelopes
25
+
26
+ Pass one plain JavaScript async arrow, \`async () => { ... }\`. It receives no
27
+ arguments. Use the supplied \`connecta\` global; do not shadow it with a parameter.
28
+ No imports, require, filesystem, fetch, or timers in portable programs.
29
+
30
+ \`connecta.call(address, args, { timeoutMs })\` is positional. The object overload
31
+ is \`connecta.call({ address, args, timeoutMs })\`. Both return \`{ data, format }\`.
32
+ For \`format: "json"\`, data is the JSON value. For \`format: "text"\`, data is a
33
+ string: parse only if the provider documents JSON text. Never guess collection roots.
34
+
35
+ \`connecta.read("resource://id/"+encodeURIComponent(uri))\` returns \`{ contents }\` for advertised URIs.
36
+
37
+ Search and describe return \`{ tools }\`, never an array. Search also returns
38
+ \`catalogErrors\`, pagination, and possible \`absence\`; check failures before
39
+ selecting a match. JSON schemas are the program default. Compact schemas have
40
+ \`schemaFormat: "text"\`; do not read their .properties. Select the exact tool and
41
+ account/environment, inspect its schema, and supply its required keys and types.
42
+
43
+ Discover an unfamiliar run lookup, then reduce its JSON result:
44
+
45
+ \`\`\`js
46
+ async () => {
47
+ const page = await connecta.search({ connector: "ci", query: "get_run", safety: "readOnly" });
48
+ if (page.catalogErrors.length || page.absence) return { gap: page.catalogErrors, absence: page.absence };
49
+ const tool = page.tools.find(t => t.name === "get_run");
50
+ if (!tool) return { gap: "get_run unavailable" };
51
+ const { tools } = await connecta.describe({ address: tool.address });
52
+ if (tools[0].error) return { gap: tools[0].error };
53
+ const result = await connecta.call(tool.address, { runId: 42 });
54
+ if (result.format !== "json") return { gap: "Expected JSON" };
55
+ return { status: result.data.status, jobId: result.data.failedJobId };
56
+ }
57
+ \`\`\`
58
+
59
+ ## Keep partial successes
60
+
61
+ Await Promise.allSettled for independent calls when one failure must not discard
62
+ other answers. Each rejected reason keeps \`code\`, \`message\`, \`retryable\`, and
63
+ \`details\`. Branch on fields, never error prose. Repair one argument failure
64
+ before repeating it across records. Every run reports hostCalls counts separately.
65
+
66
+ After verifying these addresses and schemas, read JSON and plain text together:
67
+
68
+ \`\`\`js
69
+ async () => {
70
+ const settled = await Promise.allSettled([
71
+ connecta.call("ci.get_run", { runId: 42 }),
72
+ connecta.call("ci.get_job_logs", { jobId: "job_7" }),
73
+ connecta.call("ci.get_run", { runId: "wrong-type" })
74
+ ]);
75
+ return settled.map(s => s.status === "rejected"
76
+ ? { error: s.reason.code, retryable: s.reason.retryable }
77
+ : s.value.format === "text"
78
+ ? { text: s.value.data.split("\\n").filter(line => line.startsWith("ERROR")) }
79
+ : { status: s.value.data.status });
80
+ }
81
+ \`\`\`
82
+
83
+ Host-call budget exhaustion ends the run with budget_exceeded. Catch and
84
+ allSettled cannot recover it or return partial output. Bound batches before
85
+ starting them; search, describe, call, result, and skill spend host calls.
86
+ Split remaining reads into another run. Never replay writes for their output.
87
+
88
+ ## Per-connector guides
89
+
90
+ Fetch a listed guide with skills by its exact name, or \`connecta.skill(name)\`
91
+ inside a program; it returns \`{ name, text, format: "text" }\`.
92
+ \`guideRequired: true\` is a hard stop. connector_required and approval_required
93
+ remain after schema expansion; schema_truncated clears after exact describe.
94
+ Otherwise fetch for relevant units, pagination, aliases, or sequences.
95
+
96
+ ## Large results and recovery
97
+
98
+ A truncated direct result carries a resultId when paging is available. Follow
99
+ nextOffset in UTF-8 bytes, reassemble and reduce inside one program. Use the
100
+ returned id in place of result-id below; this example assumes documented JSON.
101
+
102
+ \`\`\`js
103
+ async () => {
104
+ let text = "", offset = 0, page;
105
+ do {
106
+ page = await connecta.result("result-id", { offset, maxBytes: 8000 });
107
+ text += page.text;
108
+ offset = page.nextOffset;
109
+ } while (page.hasMore);
110
+ return JSON.parse(text).items.map(item => item.id);
111
+ }
112
+ \`\`\`
113
+
114
+ Program returns have no page handle. Reduce reads before returning. After a
115
+ write, use its existing result, check the target, or report the gap.
116
+ write_outcome_unknown means a write was sent without a known answer: check the
117
+ target, never resend automatically. Non-retryable errors require repair.
118
+ Follow typed auth recovery: host_auth_required needs host connection repair;
119
+ downstream_oauth_required or auth_required carries an authorize_connector
120
+ handoff; provider_permission_denied needs the resource owner to grant access.
121
+
122
+ ## Output
123
+
124
+ Return reduced JSON. console.log is captured. connecta.emit accepts text, image,
125
+ or audio blocks, works without await, and delivers only after success. Images
126
+ need canonical base64 and image/png, image/jpeg, image/gif, or image/webp.
127
+ Text shares the result cap; the description states block and byte budgets.
128
+ `;
129
+ /** Kept in sync with GuestApi by the declaration test. These are type notation, not runnable JS. */
130
+ export const GUEST_API_DECLARATION = `search(args?: CatalogSearchArgs): Promise<CatalogSearchResult>;
131
+ describe(args?: CatalogDescribeArgs): Promise<{ tools: CatalogDescription[] }>;
132
+ call(address: string, args?: unknown, options?: { timeoutMs?: number }): Promise<GuestResult>;
133
+ call(request: { address: string; args?: unknown; timeoutMs?: number }): Promise<GuestResult>;
134
+ read(uri: string): Promise<GuestResourceResult>;
135
+ result(id: string, options?: { offset?: number; maxBytes?: number }): Promise<GuestResultPage>;
136
+ skill(name: string): Promise<{ name: string; text: string; format: "text" }>;
137
+ emit(block: GuestBlock): PromiseLike<void>;`;
@@ -1,4 +1,5 @@
1
1
  import { ConnectorCallError } from "./errors.js";
2
+ import { type FailureSubject } from "./operator-record.js";
2
3
  import type { JsonSchema, Logger } from "./types.js";
3
4
  export interface ValidateToolInputOptions {
4
5
  /**
@@ -54,6 +55,12 @@ export interface CompileValidatorOptions {
54
55
  * the `Connector` interface directly.
55
56
  */
56
57
  export declare function validateToolInput(schema: JsonSchema, args: unknown, opts: ValidateToolInputOptions): ConnectorCallError | null;
58
+ /**
59
+ * `validateToolInput` for a tool connecta resolved in a catalog. `subject`
60
+ * names it in the log record when the schema is unusable; a caller's own
61
+ * address never reaches the log.
62
+ */
63
+ export declare function validateCatalogToolInput(schema: JsonSchema, args: unknown, opts: ValidateToolInputOptions, subject: FailureSubject): ConnectorCallError | null;
57
64
  /**
58
65
  * Eagerly compile and cache a tool's inputSchema, throwing when the validator
59
66
  * cannot use it. Reuses the same module-level cache `validateToolInput` reads,