@zackbart/connecta 0.28.1 → 0.30.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 (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -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/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -1,86 +0,0 @@
1
- // The one document a page becomes: its source (or rendered Markdown) with the
2
- // read-only `window.artifact` global injected, and the CSP it runs under.
3
- //
4
- // Built server-side, so a render check and the viewer see byte-for-byte the
5
- // same page. The global goes in right after the doctype token — found by the
6
- // tokenizer, never by a regex — so injecting it can never knock a page into
7
- // quirks mode, and it runs before any script the author wrote.
8
- import { scanHtml } from "./html-scan.js";
9
- import { scriptSafeJson } from "./json.js";
10
- import { markdownPage, MarkdownNestingError } from "./markdown.js";
11
- /**
12
- * Reserve for the injected global and Markdown theme beyond source and data.
13
- * Sixteen 64-character document names appear twice, alongside timestamps and
14
- * versions, and a 160-character title can expand sixfold when script-escaped.
15
- */
16
- export const FRAME_OVERHEAD_BYTES = 8192;
17
- /**
18
- * The frame's Content-Security-Policy. `sandbox allow-scripts` without
19
- * `allow-same-origin` gives the page an opaque origin — no cookies, no
20
- * storage, nothing of the deployment's — and `connect-src 'none'` with every
21
- * other fetch directive shut leaves it no network beyond the allowlisted
22
- * script, style, and font origins. No `'unsafe-eval'`.
23
- */
24
- export function frameCsp(allowlist) {
25
- const sources = (list) => (list.length ? ` ${list.join(" ")}` : "");
26
- return [
27
- "sandbox allow-scripts",
28
- "default-src 'none'",
29
- `script-src 'unsafe-inline'${sources(allowlist.scripts)}`,
30
- `style-src 'unsafe-inline'${sources(allowlist.styles)}`,
31
- `font-src data:${sources(allowlist.fonts)}`,
32
- "img-src data: blob:",
33
- "media-src data: blob:",
34
- "connect-src 'none'",
35
- "frame-src 'none'",
36
- "worker-src 'none'",
37
- "manifest-src 'none'",
38
- "object-src 'none'",
39
- "base-uri 'none'",
40
- "form-action 'none'",
41
- "frame-ancestors 'self'",
42
- ].join("; ");
43
- }
44
- // Deep-frozen and non-configurable: the page reads its data and cannot
45
- // replace it for a script that runs later.
46
- const DEFINE_GLOBAL = '(()=>{const f=(o)=>{if(o!==null&&typeof o==="object"){for(const k of Object.keys(o))f(o[k]);Object.freeze(o)}return o};' +
47
- 'Object.defineProperty(window,"artifact",{value:f(%VALUE%),writable:false,configurable:false,enumerable:false})})()';
48
- /**
49
- * The complete document a viewer or render check loads. `data` maps document
50
- * names to their stored, script-safe JSON text, which is spliced in as-is.
51
- *
52
- * `theme` reaches a Markdown page only, whose styling is connecta's own. An
53
- * HTML page is the author's, down to its colors, and is never restyled; the
54
- * frame around it is what follows the deployment's scheme.
55
- */
56
- export function buildFrameDocument(input) {
57
- let page = input.source;
58
- if (input.kind === "markdown") {
59
- try {
60
- page = markdownPage(input.source, input.global.title, input.theme);
61
- }
62
- catch (error) {
63
- if (!(error instanceof MarkdownNestingError))
64
- throw error;
65
- // Old or externally populated stores may hold a page that today's
66
- // validator would reject. Render fixed text instead of throwing here.
67
- page = markdownPage("This page has too many nested block quotes or lists to display.", input.global.title, input.theme);
68
- }
69
- }
70
- const meta = scriptSafeJson(input.global);
71
- const data = Object.entries(input.data)
72
- .map(([name, text]) => `${JSON.stringify(name)}:${text}`)
73
- .join(",");
74
- const value = `${meta.slice(0, -1)},"data":{${data}}}`;
75
- const script = `<script>${DEFINE_GLOBAL.replace("%VALUE%", () => value)}</script>`;
76
- let at = 0;
77
- for (const token of scanHtml(page)) {
78
- if (token.type === "doctype") {
79
- at = token.end;
80
- break;
81
- }
82
- if (token.type === "start")
83
- break;
84
- }
85
- return `${page.slice(0, at)}${script}${page.slice(at)}`;
86
- }
@@ -1,140 +0,0 @@
1
- // The artifacts connector's usage guide: what an agent must know before its
2
- // first write. Built once at construction with this deployment's allowlist
3
- // and limits, so the numbers and origins it states are the ones enforced.
4
- export const GUIDE_SUMMARY = "Publish team pages: validate first, keep data in documents, patch small edits, pass baseVersion, share the url.";
5
- const size = (bytes) => bytes % (1024 * 1024) === 0
6
- ? `${bytes / (1024 * 1024)} MiB`
7
- : bytes % 1024 === 0
8
- ? `${bytes / 1024} KiB`
9
- : `${bytes.toLocaleString("en-US")} bytes`;
10
- export function buildGuide(options) {
11
- const { allowlist, limits } = options;
12
- const origins = (list) => list.length ? list.map((origin) => `\`${origin}\``).join(", ") : "none";
13
- return `# Artifacts
14
-
15
- Artifacts are team pages that outlive a chat: dashboards, reports, plans. A page
16
- is self-contained HTML or Markdown; the numbers it shows live in named JSON
17
- **documents** that the page reads from a read-only global. Every write makes a
18
- new immutable version, attributed to you, and any version can be rolled back.
19
- Only people who can read this connector can open a page.
20
-
21
- ## Writing a page
22
-
23
- - **HTML** (\`kind: "html"\`): a complete document starting \`<!doctype html>\`,
24
- with exactly one element \`id="artifact-root"\` (usually
25
- \`<main id="artifact-root">\`) holding the content.
26
- - **Markdown** (\`kind: "markdown"\`): headings, lists, tables, code, links, and
27
- \`data:\` images. Raw HTML shows as text. Markdown pages cannot read data.
28
- - **Data**: a script reads \`window.artifact.data.<name>\` — each document by
29
- name, already parsed and frozen. \`window.artifact\` also has \`id\`, \`title\`,
30
- \`view.version\`, \`documents.<name>.{version, updatedAt}\`, and \`snapshot\`.
31
- Document names are identifiers: letters, digits, \`_\`.
32
- - **Keep data out of the HTML.** Put every figure in a document and render it
33
- from script; then fixing a number is \`set_documents\`, not a page rewrite,
34
- and a refresh can replace the data without touching the view.
35
-
36
- Pages run sandboxed in their own origin, so some things cannot work there:
37
-
38
- - **No network.** \`fetch\`, XHR, WebSocket, and beacons fail. Read
39
- \`window.artifact.data\` instead.
40
- - **External scripts** load only from ${origins(allowlist.scripts)}. None are
41
- allowed by default; an operator must opt in to each trusted origin. A script
42
- can read this page's data. When allowed, use https and pin an exact version
43
- (\`https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\`).
44
- Libraries that need \`eval\` or \`new Function\` do not run. Inline
45
- \`<script>\` is fine.
46
- - **External stylesheets** load only from ${origins(allowlist.styles)}; fonts
47
- from ${origins(allowlist.fonts)} or \`data:\`. Both origin lists default to
48
- empty. Trusted script, style, and font origins may receive page data in
49
- request URLs. Inline \`<style>\` is fine.
50
- - **Images** are \`data:\` URLs or inline SVG. Nothing relative: a page has no
51
- files beside it.
52
- - No \`<iframe>\`, \`<form>\`, \`<object>\`, \`<embed>\`, \`<base>\`, or
53
- \`<meta http-equiv>\`. A link navigates the page's own frame and cannot open a
54
- new window, so show URLs as text when readers need them elsewhere.
55
-
56
- ## Tools
57
-
58
- Reads run anywhere, including inside \`execute_code\`:
59
-
60
- - \`list_artifacts { query?, includeArchived?, limit?, cursor? }\` — check for an
61
- existing page before creating one.
62
- - \`get_artifact { id, version?, includeSource? }\` — the view, its history, its
63
- documents' versions, \`revision\`, \`url\`, and \`snapshotUrl\`.
64
- - \`get_document { id, name, version? }\` — one document's value and history.
65
- - \`get_refresh { id }\` — the configured program, schedule, freshness, and the
66
- latest 20 run records. Logs and failure messages are for agents, not readers.
67
- - \`validate_artifact { kind, source, documents? }\` — every check a save runs${options.renderCheck ? ", including this deployment's render check," : ""} without saving. **Validate before you save**; each error names a line and
68
- the fix.
69
-
70
- Writes make new versions and never ask for approval inside \`execute_code\`
71
- (unless this deployment turned that off); each one spends the program's write
72
- budget. At the top level, \`call_tool\` refuses them — use
73
- \`call_destructive_tool\`.
74
-
75
- - \`create_artifact { id, title, kind, source, documents? }\` — \`id\` is the
76
- page's URL slug (lowercase letters, digits, hyphens). \`documents\` maps each
77
- name straight to its value, \`{ "data": { "rows": [...] } }\`; supply
78
- everything the first render needs in the same call.
79
- - \`patch_artifact { id, baseVersion, edits: [{ find, replace }] }\` — **prefer
80
- this for small edits** such as a title or a label. Each \`find\` must occur
81
- exactly once in the current source (include surrounding text if it would
82
- match twice); every edit applies at once, and if any fails nothing changes.
83
- - \`update_artifact { id, baseVersion, source, title? }\` — replace the whole
84
- source.
85
- - \`set_documents { id, documents: { <name>: { baseVersion, value } } }\` — set
86
- several documents atomically; \`baseVersion: 0\` creates one, \`value: null\`
87
- removes one.
88
- - \`set_refresh { id, document, program, schedule, baseVersion }\` — version a
89
- complete \`execute_code\` async-arrow program returning the document's JSON
90
- value. \`schedule\` is \`manual\`, \`daily\`, or \`weekly\`; use \`baseVersion: 0\`
91
- for the first program, then the version from \`get_refresh\`. It can call
92
- only explicitly read-only tools on shared connectors. Personal connectors
93
- and writes fail the run even if the program catches the refusal. A failed
94
- run keeps the last good document and marks the page stale. Each run rechecks
95
- your current connector grants and pool access; removing permission to
96
- configure refresh also stops your scheduled program.
97
- - \`run_refresh { id }\` — trigger one run now through
98
- \`call_destructive_tool\`. It cannot run inside \`execute_code\` because that
99
- would hold an executor permit while asking for another.
100
- - \`rollback_artifact { id, target: "view" | "document", name?, version, baseVersion }\`
101
- — a new version with an old body; history is never rewritten.
102
- - \`archive_artifact\` / \`restore_artifact { id, baseRevision }\` — hide a page
103
- from the library, or bring it back. Archived pages refuse writes.
104
-
105
- ## Versions and conflicts
106
-
107
- Every write names what it expects to replace: \`baseVersion\` is the current
108
- view version (or the document's version), \`baseRevision\` the artifact's
109
- revision, all from \`get_artifact\`/\`get_document\`. If someone changed it
110
- first, the write fails with code \`conflict\` and nothing is saved; the error's
111
- \`current\` (\`err.details.current\` in a program) says where things stand.
112
- Re-read, reapply your change, and retry with the new base. Never retry the
113
- same call unchanged.
114
-
115
- ## Sharing
116
-
117
- \`url\` is the page as it is now; share that. \`snapshotUrl\` pins the view and
118
- every document to their current versions, for "as of" links. Both are for the
119
- team only.
120
-
121
- ## Limits
122
-
123
- | What | Limit |
124
- | --- | --- |
125
- | Page source | ${size(limits.sourceBytes)} |
126
- | One document, serialized | ${size(limits.documentBytes)} |
127
- | Documents per artifact | ${limits.documents} |
128
- | Distinct document names over its lifetime | 64 (removed names keep their versions) |
129
- | All documents together | ${size(limits.totalDocumentBytes)} |
130
- | Page plus documents | ${size(limits.renderedBytes)} |
131
- | Title | ${limits.titleChars} characters |
132
- | Edits per patch | ${limits.patchEdits}, each \`find\` up to ${size(limits.findBytes)} |
133
- | Document nesting | ${limits.jsonDepth} levels; numbers finite; no \`__proto__\` keys |
134
-
135
- A program's host call returns at most 256 KiB on some executors: read a large
136
- source with \`includeSource: false\` inside a program, or with \`call_tool\`.
137
- Versions are kept forever, and a body written by a write that lost a conflict
138
- stays stored unreferenced.
139
- `;
140
- }
@@ -1,329 +0,0 @@
1
- // A small HTML tokenizer: exactly the HTML5 tokenizer states validation and
2
- // the viewer need, and none of tree construction.
3
- //
4
- // It is a lint's tokenizer, not a sanitizer's. The viewer's CSP and sandbox are
5
- // the security boundary; this finds the tags, attributes, and raw text a page
6
- // author most plausibly got wrong, with a line and column for each, so an
7
- // agent can fix a page from the message alone. Where it approximates the
8
- // standard — foreign content, the rarer script escapes — the failure mode is a
9
- // finding that is missing or extra, never a page that escapes its frame.
10
- const RAW_TEXT = new Set([
11
- "script",
12
- "style",
13
- "xmp",
14
- "iframe",
15
- "noembed",
16
- "noframes",
17
- "noscript",
18
- "title",
19
- "textarea",
20
- ]);
21
- const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\f" || c === "\r";
22
- const isAlpha = (c) => c !== undefined && ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z"));
23
- const NAMED = {
24
- amp: "&",
25
- lt: "<",
26
- gt: ">",
27
- quot: '"',
28
- apos: "'",
29
- nbsp: " ",
30
- Tab: "\t",
31
- NewLine: "\n",
32
- colon: ":",
33
- sol: "/",
34
- lpar: "(",
35
- rpar: ")",
36
- period: ".",
37
- comma: ",",
38
- };
39
- /**
40
- * Decode the character references a URL check has to see through: numeric
41
- * ones, and the named ones that spell a scheme (`&colon;`, `&Tab;`, …).
42
- */
43
- export function decodeEntities(value) {
44
- if (!value.includes("&"))
45
- return value;
46
- return value.replace(/&(?:#(\d{1,8})|#[xX]([0-9a-fA-F]{1,7})|([A-Za-z]+));?/g, (match, dec, hex, name) => {
47
- if (name !== undefined)
48
- return NAMED[name] ?? match;
49
- const code = dec !== undefined ? Number(dec) : Number.parseInt(hex ?? "", 16);
50
- if (!Number.isFinite(code) || code === 0 || code > 0x10ffff)
51
- return "�";
52
- if (code >= 0xd800 && code <= 0xdfff)
53
- return "�";
54
- return String.fromCodePoint(code);
55
- });
56
- }
57
- /** Tokenize `source`. Never throws; malformed markup tokenizes the way a browser would, approximately. */
58
- export function scanHtml(source) {
59
- const tokens = [];
60
- // ASCII-only lowercasing keeps every offset valid; `toLowerCase` can change
61
- // a string's length.
62
- const lower = source.replace(/[A-Z]+/g, (run) => run.toLowerCase());
63
- const n = source.length;
64
- let i = 0;
65
- let textStart = 0;
66
- const flush = (end) => {
67
- if (end > textStart)
68
- tokens.push({ type: "text", start: textStart, end });
69
- };
70
- const until = (from, needle) => {
71
- const at = source.indexOf(needle, from);
72
- return at < 0 ? n : at + needle.length;
73
- };
74
- while (i < n) {
75
- if (source[i] !== "<") {
76
- i++;
77
- continue;
78
- }
79
- const next = source[i + 1];
80
- if (next === "!") {
81
- flush(i);
82
- const start = i;
83
- if (source.startsWith("<!--", i)) {
84
- if (source.startsWith("<!-->", i))
85
- i += 5;
86
- else if (source.startsWith("<!--->", i))
87
- i += 6;
88
- else
89
- i = until(i + 4, "-->");
90
- tokens.push({ type: "comment", start, end: i });
91
- }
92
- else if (source.slice(i + 2, i + 9).toLowerCase() === "doctype") {
93
- i = until(i, ">");
94
- tokens.push({ type: "doctype", start, end: i });
95
- }
96
- else if (source.startsWith("<![CDATA[", i)) {
97
- i = until(i + 9, "]]>");
98
- tokens.push({ type: "comment", start, end: i });
99
- }
100
- else {
101
- i = until(i, ">");
102
- tokens.push({ type: "comment", start, end: i });
103
- }
104
- textStart = i;
105
- continue;
106
- }
107
- if (next === "?") {
108
- flush(i);
109
- const start = i;
110
- i = until(i, ">");
111
- tokens.push({ type: "comment", start, end: i });
112
- textStart = i;
113
- continue;
114
- }
115
- if (next === "/") {
116
- const after = source[i + 2];
117
- if (after === ">") {
118
- // `</>` is dropped entirely.
119
- flush(i);
120
- i += 3;
121
- textStart = i;
122
- continue;
123
- }
124
- if (!isAlpha(after)) {
125
- flush(i);
126
- const start = i;
127
- i = until(i, ">");
128
- tokens.push({ type: "comment", start, end: i });
129
- textStart = i;
130
- continue;
131
- }
132
- const tag = readTag(source, i + 2);
133
- if (!tag) {
134
- // End of input inside a tag: the tag is never emitted.
135
- flush(i);
136
- i = n;
137
- textStart = n;
138
- break;
139
- }
140
- flush(i);
141
- tokens.push({ type: "end", name: tag.name, start: i, end: tag.end });
142
- i = tag.end;
143
- textStart = i;
144
- continue;
145
- }
146
- if (!isAlpha(next)) {
147
- i++;
148
- continue;
149
- }
150
- const tag = readTag(source, i + 1);
151
- if (!tag) {
152
- flush(i);
153
- i = n;
154
- textStart = n;
155
- break;
156
- }
157
- flush(i);
158
- tokens.push({
159
- type: "start",
160
- name: tag.name,
161
- attrs: tag.attrs,
162
- selfClosing: tag.selfClosing,
163
- start: i,
164
- end: tag.end,
165
- });
166
- i = tag.end;
167
- textStart = i;
168
- if (tag.name === "plaintext") {
169
- if (n > i)
170
- tokens.push({ type: "text", start: i, end: n, rawOf: "plaintext" });
171
- i = n;
172
- textStart = n;
173
- break;
174
- }
175
- if (RAW_TEXT.has(tag.name)) {
176
- const close = tag.name === "script"
177
- ? scriptEnd(source, lower, i)
178
- : rawTextEnd(source, lower, i, tag.name);
179
- if (close > i) {
180
- tokens.push({ type: "text", start: i, end: close, rawOf: tag.name });
181
- }
182
- i = close;
183
- textStart = i;
184
- }
185
- }
186
- flush(n);
187
- return tokens;
188
- }
189
- /** Where `</name` followed by whitespace, `/`, or `>` starts, from `from`; the end of input when absent. */
190
- function rawTextEnd(source, lower, from, name) {
191
- let at = from;
192
- for (;;) {
193
- at = lower.indexOf(`</${name}`, at);
194
- if (at < 0)
195
- return source.length;
196
- const c = source[at + 2 + name.length];
197
- if (c === undefined || c === "/" || c === ">" || isSpace(c))
198
- return at;
199
- at += 2;
200
- }
201
- }
202
- /**
203
- * Script data, with its escaped and double-escaped states: inside `<!--`, a
204
- * `<script` means the next `</script>` does not end the element.
205
- */
206
- function scriptEnd(source, lower, from) {
207
- const tagAt = (at, prefix) => {
208
- if (!lower.startsWith(prefix, at))
209
- return false;
210
- const c = source[at + prefix.length];
211
- return c === undefined || c === "/" || c === ">" || isSpace(c);
212
- };
213
- let state = "data";
214
- for (let i = from; i < source.length; i++) {
215
- if (state === "data") {
216
- if (tagAt(i, "</script"))
217
- return i;
218
- if (lower.startsWith("<!--", i)) {
219
- state = "escaped";
220
- i += 3;
221
- }
222
- }
223
- else if (state === "escaped") {
224
- if (tagAt(i, "</script"))
225
- return i;
226
- if (lower.startsWith("-->", i)) {
227
- state = "data";
228
- i += 2;
229
- }
230
- else if (tagAt(i, "<script")) {
231
- state = "double";
232
- i += 6;
233
- }
234
- }
235
- else {
236
- if (tagAt(i, "</script")) {
237
- state = "escaped";
238
- i += 7;
239
- }
240
- else if (lower.startsWith("-->", i)) {
241
- state = "data";
242
- i += 2;
243
- }
244
- }
245
- }
246
- return source.length;
247
- }
248
- /** Read a tag name and its attributes, from just after `<` or `</`. Null at end of input. */
249
- function readTag(source, from) {
250
- const n = source.length;
251
- let i = from;
252
- while (i < n && !isSpace(source[i]) && source[i] !== "/" && source[i] !== ">")
253
- i++;
254
- const name = source.slice(from, i).toLowerCase();
255
- const attrs = [];
256
- const seen = new Set();
257
- let selfClosing = false;
258
- for (;;) {
259
- while (i < n && (isSpace(source[i]) || (source[i] === "/" && source[i + 1] !== ">")))
260
- i++;
261
- if (i >= n)
262
- return null;
263
- if (source[i] === ">")
264
- return { name, attrs, selfClosing, end: i + 1 };
265
- if (source[i] === "/") {
266
- // "/>"
267
- selfClosing = true;
268
- return { name, attrs, selfClosing, end: i + 2 };
269
- }
270
- const nameStart = i;
271
- // A leading "=" belongs to the name.
272
- i++;
273
- while (i < n &&
274
- !isSpace(source[i]) &&
275
- source[i] !== "/" &&
276
- source[i] !== ">" &&
277
- source[i] !== "=")
278
- i++;
279
- const attrName = source.slice(nameStart, i).toLowerCase();
280
- let value = "";
281
- let j = i;
282
- while (j < n && isSpace(source[j]))
283
- j++;
284
- if (source[j] === "=") {
285
- i = j + 1;
286
- while (i < n && isSpace(source[i]))
287
- i++;
288
- const quote = source[i];
289
- if (quote === '"' || quote === "'") {
290
- const close = source.indexOf(quote, i + 1);
291
- if (close < 0)
292
- return null;
293
- value = source.slice(i + 1, close);
294
- i = close + 1;
295
- }
296
- else {
297
- const valueStart = i;
298
- while (i < n && !isSpace(source[i]) && source[i] !== ">")
299
- i++;
300
- value = source.slice(valueStart, i);
301
- }
302
- }
303
- // A repeated attribute is dropped; the first one wins.
304
- if (!seen.has(attrName)) {
305
- seen.add(attrName);
306
- attrs.push({ name: attrName, value: decodeEntities(value), offset: nameStart });
307
- }
308
- }
309
- }
310
- /** Map a source offset to a 1-based line and column. */
311
- export function lineLocator(source) {
312
- const starts = [0];
313
- for (let i = 0; i < source.length; i++) {
314
- if (source[i] === "\n")
315
- starts.push(i + 1);
316
- }
317
- return (offset) => {
318
- let lo = 0;
319
- let hi = starts.length - 1;
320
- while (lo < hi) {
321
- const mid = (lo + hi + 1) >> 1;
322
- if ((starts[mid] ?? 0) <= offset)
323
- lo = mid;
324
- else
325
- hi = mid - 1;
326
- }
327
- return { line: lo + 1, column: offset - (starts[lo] ?? 0) + 1 };
328
- };
329
- }
@@ -1,78 +0,0 @@
1
- // JSON as the artifacts module stores and injects it.
2
- /**
3
- * `JSON.stringify`, with `<`, U+2028, and U+2029 escaped. The result is the
4
- * same JSON value, and it can sit inside a `<script>` element as a literal:
5
- * no `</script>` or `<!--` can end or bend the element it lives in. Documents
6
- * are stored in this form, so the bytes a limit counts are the bytes a page
7
- * receives.
8
- */
9
- export function scriptSafeJson(value) {
10
- return (JSON.stringify(value) ?? "null")
11
- .replace(/</g, "\\u003c")
12
- .replace(/[\u2028\u2029]/g, (c) => (c === "\u2028" ? "\\u2028" : "\\u2029"));
13
- }
14
- /**
15
- * Why `value` cannot be a document, or undefined when it can: plain JSON with
16
- * finite numbers, nested at most `maxDepth`, and no `__proto__` key — a page
17
- * reads documents as JavaScript objects, where that key sets a prototype
18
- * instead of holding data.
19
- */
20
- export function jsonProblem(value, maxDepth) {
21
- const pointer = (path) => path.length
22
- ? `/${path.map((part) => String(part).replace(/~/g, "~0").replace(/\//g, "~1")).join("/")}`
23
- : "/";
24
- const stack = [
25
- { value, path: [], depth: 0 },
26
- ];
27
- while (stack.length) {
28
- const item = stack.pop();
29
- if (!item)
30
- break;
31
- const current = item.value;
32
- if (current === null || typeof current === "string" || typeof current === "boolean")
33
- continue;
34
- if (typeof current === "number") {
35
- if (!Number.isFinite(current)) {
36
- return `${pointer(item.path)} is ${String(current)}, which has no JSON form; use null or a string`;
37
- }
38
- continue;
39
- }
40
- if (typeof current !== "object") {
41
- return `${pointer(item.path)} is ${current === undefined ? "undefined" : `a ${typeof current}`}, which has no JSON form`;
42
- }
43
- if (item.depth >= maxDepth) {
44
- return `${pointer(item.path)} nests deeper than ${maxDepth} levels`;
45
- }
46
- if (Array.isArray(current)) {
47
- for (let index = current.length - 1; index >= 0; index--) {
48
- stack.push({ value: current[index], path: [...item.path, index], depth: item.depth + 1 });
49
- }
50
- continue;
51
- }
52
- const proto = Object.getPrototypeOf(current);
53
- if (proto !== Object.prototype && proto !== null) {
54
- return `${pointer(item.path)} is not a plain object`;
55
- }
56
- for (const key of Object.keys(current)) {
57
- if (key === "__proto__") {
58
- return `${pointer([...item.path, key])} is a "__proto__" key, which a page would read as a prototype; rename it`;
59
- }
60
- stack.push({
61
- value: current[key],
62
- path: [...item.path, key],
63
- depth: item.depth + 1,
64
- });
65
- }
66
- }
67
- return undefined;
68
- }
69
- const encoder = new TextEncoder();
70
- /** A string's size in UTF-8 bytes: what every artifact limit counts. */
71
- export function utf8Bytes(text) {
72
- return encoder.encode(text).byteLength;
73
- }
74
- /** SHA-256 as lowercase hex: the content address of a stored body. */
75
- export async function sha256Hex(text) {
76
- const digest = await crypto.subtle.digest("SHA-256", encoder.encode(text));
77
- return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
78
- }
@@ -1,20 +0,0 @@
1
- import type { KVStorage } from "../types.js";
2
- import { type ArtifactBlobStore, type ArtifactStore } from "./types.js";
3
- export interface KvArtifactStoreOptions {
4
- /**
5
- * Keep bodies here instead of in the key-value store — an R2 bucket on
6
- * Workers. Heads, versions, and runs stay in the key-value store, which
7
- * owns the compare-and-set.
8
- */
9
- blobs?: ArtifactBlobStore;
10
- /** Key prefix for every record. Default `artifact:`. */
11
- prefix?: string;
12
- }
13
- /**
14
- * An artifact store over `KVStorage`. The storage must provide `compareAndSet`
15
- * — a write commits by swapping one head record, and a read followed by a
16
- * write cannot stand in for that — and `list`, which history and the library
17
- * page through. Cloudflare Workers KV has neither guarantee and is refused
18
- * here, at construction, rather than losing a write later.
19
- */
20
- export declare function kvArtifactStore(kv: KVStorage, options?: KvArtifactStoreOptions): ArtifactStore;