@zackbart/connecta 0.24.4 → 0.25.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 (107) hide show
  1. package/AGENTS.md +40 -10
  2. package/CHANGELOG.md +253 -0
  3. package/dist/advertised-schema.js +53 -0
  4. package/dist/auth/downstream-oauth.js +490 -106
  5. package/dist/call-admission.js +139 -113
  6. package/dist/catalog-service.js +141 -105
  7. package/dist/catalog.js +258 -33
  8. package/dist/concurrency.js +24 -23
  9. package/dist/connector-scope.js +6 -37
  10. package/dist/connectors/api.d.ts +2 -2
  11. package/dist/connectors/remote-mcp.d.ts +6 -5
  12. package/dist/connectors/remote-mcp.js +317 -241
  13. package/dist/credential-contract.d.ts +9 -0
  14. package/dist/credential-rules.js +2 -2
  15. package/dist/credentials.d.ts +6 -1
  16. package/dist/credentials.js +45 -3
  17. package/dist/execute.js +363 -257
  18. package/dist/executor-admission.js +67 -50
  19. package/dist/executors/quickjs-runtime.js +4 -2
  20. package/dist/executors/quickjs.js +251 -210
  21. package/dist/fix-prompt.js +37 -0
  22. package/dist/index.d.ts +10 -1
  23. package/dist/index.js +51 -20
  24. package/dist/invocation.js +324 -285
  25. package/dist/meta-tools.js +206 -92
  26. package/dist/oauth-callback-outcome.js +94 -0
  27. package/dist/oauth-sealing.js +29 -0
  28. package/dist/operator-ui/fix-prompts.js +87 -0
  29. package/dist/operator-ui/generated.js +2 -2
  30. package/dist/operator-ui/model.d.ts +46 -1
  31. package/dist/operator-ui/setup-commands.js +53 -0
  32. package/dist/operator-ui/view.js +38 -2
  33. package/dist/program-source.js +205 -0
  34. package/dist/providers/notion.js +2 -2
  35. package/dist/providers/revenuecat.js +32 -5
  36. package/dist/providers/stripe.d.ts +3 -2
  37. package/dist/providers/vercel.js +1 -1
  38. package/dist/registry.d.ts +1 -1
  39. package/dist/registry.js +305 -260
  40. package/dist/routes/activity.js +120 -137
  41. package/dist/routes/credentials.js +174 -163
  42. package/dist/routes/mcp.js +129 -70
  43. package/dist/routes/oauth-management.js +60 -71
  44. package/dist/routes/oauth.js +44 -16
  45. package/dist/routes/operator.js +82 -0
  46. package/dist/routes/shared.d.ts +2 -0
  47. package/dist/routes/ui-data.js +210 -0
  48. package/dist/routes/ui.js +64 -32
  49. package/dist/runtime/admission.js +43 -0
  50. package/dist/runtime/call-admission.js +67 -0
  51. package/dist/runtime/connector-scope.js +72 -0
  52. package/dist/runtime/run.js +174 -0
  53. package/dist/runtime/services.js +130 -0
  54. package/dist/runtime/storage.js +66 -0
  55. package/dist/server.js +111 -89
  56. package/dist/skills.js +1 -1
  57. package/dist/storage/file.js +39 -2
  58. package/dist/storage/memory.js +28 -16
  59. package/dist/timeout.js +17 -47
  60. package/dist/types.d.ts +27 -7
  61. package/dist/ui.d.ts +16 -2
  62. package/dist/ui.js +50 -166
  63. package/dist/url-safety.js +157 -0
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/documentation/architecture.md +184 -10
  67. package/documentation/auth.md +80 -0
  68. package/documentation/code-mode.md +69 -24
  69. package/documentation/meta-tools.md +164 -27
  70. package/ethos.md +44 -47
  71. package/examples/worker/README.md +51 -5
  72. package/examples/worker/src/d1-storage.ts +116 -0
  73. package/examples/worker/src/index.ts +6 -6
  74. package/examples/worker/wrangler.jsonc +16 -4
  75. package/package.json +11 -3
  76. package/templates/node/package.json +1 -1
  77. package/dist/auth/downstream-oauth.d.ts +0 -200
  78. package/dist/catalog-fingerprint.d.ts +0 -19
  79. package/dist/catalog-limits.d.ts +0 -9
  80. package/dist/catalog-service.d.ts +0 -263
  81. package/dist/catalog.d.ts +0 -87
  82. package/dist/concurrency.d.ts +0 -3
  83. package/dist/connectors/guarded-fetch.d.ts +0 -96
  84. package/dist/credential-rules.d.ts +0 -85
  85. package/dist/execute.d.ts +0 -154
  86. package/dist/executor-result.d.ts +0 -10
  87. package/dist/executors/quickjs-child.d.ts +0 -1
  88. package/dist/executors/quickjs-protocol.d.ts +0 -67
  89. package/dist/favicon.d.ts +0 -1
  90. package/dist/identity.d.ts +0 -4
  91. package/dist/invocation.d.ts +0 -74
  92. package/dist/mcp-result.d.ts +0 -8
  93. package/dist/meta-tools.d.ts +0 -129
  94. package/dist/operator-ui/generated.d.ts +0 -2
  95. package/dist/operator-ui/view.d.ts +0 -188
  96. package/dist/result-shapes.d.ts +0 -13
  97. package/dist/retired-toolkits.d.ts +0 -2
  98. package/dist/routes/activity.d.ts +0 -2
  99. package/dist/routes/credentials.d.ts +0 -2
  100. package/dist/routes/mcp.d.ts +0 -10
  101. package/dist/routes/oauth-management.d.ts +0 -2
  102. package/dist/routes/oauth.d.ts +0 -2
  103. package/dist/routes/ui.d.ts +0 -2
  104. package/dist/server.d.ts +0 -9
  105. package/dist/skills.d.ts +0 -44
  106. package/dist/timeout.d.ts +0 -21
  107. package/dist/tool-safety.d.ts +0 -9
@@ -20,5 +20,14 @@ export interface CredentialVault {
20
20
  set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
21
21
  setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
22
22
  delete(connectorId: string, owner?: string): Promise<void>;
23
+ /**
24
+ * Encrypt downstream OAuth state (tokens, a registered client, a PKCE
25
+ * verifier) before it enters storage. `purpose` is the storage key the value
26
+ * is written under, so the result is bound to one connector, owner, and key.
27
+ * Without both `seal` and `open`, that state is stored as plaintext.
28
+ */
29
+ seal?(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
30
+ /** Reverse `seal` for the same connector, purpose, and owner; rejects otherwise. */
31
+ open?(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
23
32
  }
24
33
  export {};
@@ -3,7 +3,7 @@ export const STORED_CREDENTIAL_SHAPE_MISMATCH_ERROR = "Stored credential fields
3
3
  /**
4
4
  * Compare a connector's current declaration with the keys in its stored
5
5
  * credential. Values are deliberately ignored: callers may pass decrypted
6
- * values or `/credentials`' masked field metadata and get the same answer.
6
+ * values or the operator UI's masked credential field metadata and get the same answer.
7
7
  *
8
8
  * The test is CONTAINMENT, not equality: the stored key set is compatible when
9
9
  * it holds every field currently declared — the reserved `value` key for a
@@ -65,7 +65,7 @@ export function describeUndeclaredCredentialFields(fields) {
65
65
  }
66
66
  /**
67
67
  * The one rule deciding whether a connector's credential can be tested — read
68
- * by /credentials' `testable` flag, by `POST /ui/credentials/<id>/test` when
68
+ * by the operator UI's credential `testable` flag, by `POST /ui/credentials/<id>/test` when
69
69
  * it picks a hook, and by the construction-time mismatch warning, so those
70
70
  * three cannot drift apart.
71
71
  *
@@ -3,7 +3,8 @@ import type { ConnectorCredentialValues, KVStorage } from "./types.js";
3
3
  /**
4
4
  * Encrypted, connector-scoped credential vault over the deployment's existing
5
5
  * KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
6
- * secret outside the store.
6
+ * secret outside the store. The same key seals downstream OAuth state, which
7
+ * the OAuth provider stores itself.
7
8
  */
8
9
  export declare class CredentialVault implements Vault {
9
10
  private readonly storage;
@@ -17,5 +18,9 @@ export declare class CredentialVault implements Vault {
17
18
  set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
18
19
  setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
19
20
  delete(connectorId: string, owner?: string): Promise<void>;
21
+ /** Encrypt downstream OAuth state under the vault key; nothing is stored. */
22
+ seal(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
23
+ /** Decrypt what `seal` produced for this connector, purpose, and owner. */
24
+ open(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
20
25
  }
21
26
  export declare function encryptedCredentialVault(storage: KVStorage, encryptionKey: string): Vault;
@@ -3,6 +3,16 @@ const IV_BYTES = 12;
3
3
  const MAX_CREDENTIAL_BYTES = 16_384;
4
4
  const encoder = new TextEncoder();
5
5
  const decoder = new TextDecoder();
6
+ /** Version tag of the compact `seal` output: `v1.<iv>.<ciphertext>`. */
7
+ const SEALED_VERSION = "v1";
8
+ /**
9
+ * Binds sealed OAuth state to its connector, owner, and physical storage key
10
+ * (which carries the authorization epoch), so ciphertext copied anywhere else
11
+ * does not open. Connector ids cannot contain `|`, and the owner is a hash.
12
+ */
13
+ function oauthStateAdditionalData(connectorId, purpose, owner) {
14
+ return encoder.encode(`connecta:oauth-state:v1|${connectorId}|${owner ?? ""}|${purpose}`);
15
+ }
6
16
  function storageKey(connectorId, owner) {
7
17
  return owner
8
18
  ? `principal:${owner}:conn:${connectorId}:credential:v1`
@@ -20,7 +30,7 @@ function base64ToBytes(value) {
20
30
  binary = atob(value);
21
31
  }
22
32
  catch {
23
- throw new Error("credentials.encryptionKey must be a base64-encoded 32-byte key");
33
+ throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
24
34
  }
25
35
  return Uint8Array.from(binary, (char) => char.charCodeAt(0));
26
36
  }
@@ -87,7 +97,8 @@ function validateValues(values) {
87
97
  /**
88
98
  * Encrypted, connector-scoped credential vault over the deployment's existing
89
99
  * KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
90
- * secret outside the store.
100
+ * secret outside the store. The same key seals downstream OAuth state, which
101
+ * the OAuth provider stores itself.
91
102
  */
92
103
  export class CredentialVault {
93
104
  storage;
@@ -96,7 +107,7 @@ export class CredentialVault {
96
107
  this.storage = storage;
97
108
  const raw = base64ToBytes(encryptionKey.trim());
98
109
  if (raw.byteLength !== KEY_BYTES) {
99
- throw new Error("credentials.encryptionKey must be a base64-encoded 32-byte key");
110
+ throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
100
111
  }
101
112
  this.key = crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
102
113
  }
@@ -182,6 +193,37 @@ export class CredentialVault {
182
193
  async delete(connectorId, owner) {
183
194
  await this.storage.delete(storageKey(connectorId, owner));
184
195
  }
196
+ /** Encrypt downstream OAuth state under the vault key; nothing is stored. */
197
+ async seal(connectorId, purpose, plaintext, owner) {
198
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
199
+ const ciphertext = await crypto.subtle.encrypt({
200
+ name: "AES-GCM",
201
+ iv,
202
+ additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
203
+ }, await this.key, encoder.encode(plaintext));
204
+ return `${SEALED_VERSION}.${bytesToBase64(iv)}.${bytesToBase64(new Uint8Array(ciphertext))}`;
205
+ }
206
+ /** Decrypt what `seal` produced for this connector, purpose, and owner. */
207
+ async open(connectorId, purpose, sealed, owner) {
208
+ const [version, iv, ciphertext, ...rest] = sealed.split(".");
209
+ if (version !== SEALED_VERSION ||
210
+ iv === undefined ||
211
+ ciphertext === undefined ||
212
+ rest.length > 0) {
213
+ throw new Error("Sealed OAuth state is invalid or corrupted");
214
+ }
215
+ try {
216
+ const plaintext = await crypto.subtle.decrypt({
217
+ name: "AES-GCM",
218
+ iv: base64ToBytes(iv),
219
+ additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
220
+ }, await this.key, base64ToBytes(ciphertext));
221
+ return decoder.decode(plaintext);
222
+ }
223
+ catch {
224
+ throw new Error("Sealed OAuth state could not be decrypted");
225
+ }
226
+ }
185
227
  }
186
228
  export function encryptedCredentialVault(storage, encryptionKey) {
187
229
  return new CredentialVault(storage, encryptionKey);