@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Server-side Markdown renderer using marked.js
3
+ *
4
+ * This module provides markdown rendering that produces HTML matching
5
+ * the visual appearance of the CodeMirror editor extensions.
6
+ */
7
+
8
+ import { Marked } from "marked";
9
+ import sanitizeHtml from "sanitize-html";
10
+ import { markdownClient } from "./client";
11
+ import { codeExtension } from "./extensions/code";
12
+ import { guidedHelpExtension } from "./extensions/guided-help";
13
+ import { imagesExtension } from "./extensions/images";
14
+ import { infoBlocksExtension } from "./extensions/info-blocks";
15
+ import { katexExtension } from "./extensions/katex";
16
+ import { linksExtension } from "./extensions/links";
17
+ import { markExtension } from "./extensions/mark";
18
+ import { subSupExtension } from "./extensions/sub-sup";
19
+ import { tablesExtension } from "./extensions/tables";
20
+ import { taskListExtension } from "./extensions/task-list";
21
+
22
+ // Create a configured marked instance
23
+ type MarkdownProfile = "content" | "help";
24
+
25
+ const createMarked = (profile: MarkdownProfile = "content") => {
26
+ const marked = new Marked();
27
+
28
+ marked.use({
29
+ breaks: true,
30
+ gfm: true,
31
+ });
32
+
33
+ // Apply extensions in order
34
+ // Note: katexExtension must come before codeExtension to handle ```math blocks
35
+ marked.use(infoBlocksExtension());
36
+ marked.use(taskListExtension());
37
+ marked.use(tablesExtension());
38
+ marked.use(linksExtension({ internalTarget: profile === "help" ? "_self" : "_blank" }));
39
+ marked.use(imagesExtension());
40
+ marked.use(katexExtension());
41
+ marked.use(codeExtension());
42
+ // Inline-style decorators come last so they run after structural tokenizers.
43
+ marked.use(markExtension());
44
+ marked.use(subSupExtension());
45
+ if (profile === "help") marked.use(guidedHelpExtension());
46
+
47
+ return marked;
48
+ };
49
+
50
+ const marked = createMarked();
51
+ const helpMarked = createMarked("help");
52
+
53
+ const sanitizeRenderedHtml = (html: string): string =>
54
+ sanitizeHtml(html, {
55
+ allowedTags: [
56
+ ...sanitizeHtml.defaults.allowedTags,
57
+ "annotation",
58
+ "aside",
59
+ "br",
60
+ "div",
61
+ "figcaption",
62
+ "figure",
63
+ "i",
64
+ "img",
65
+ "input",
66
+ "mark",
67
+ "math",
68
+ "mfrac",
69
+ "mi",
70
+ "mn",
71
+ "mo",
72
+ "mover",
73
+ "mrow",
74
+ "msqrt",
75
+ "msub",
76
+ "msubsup",
77
+ "msup",
78
+ "mtext",
79
+ "semantics",
80
+ "span",
81
+ "sub",
82
+ "sup",
83
+ "table",
84
+ "tbody",
85
+ "td",
86
+ "th",
87
+ "thead",
88
+ "tr",
89
+ ],
90
+ allowedAttributes: {
91
+ "*": ["aria-hidden", "aria-label", "class", "data-help-icon", "data-tone", "id", "title"],
92
+ a: ["href", "name", "rel", "target", "title"],
93
+ annotation: ["encoding"],
94
+ code: ["class"],
95
+ div: ["class", "data-block-name", "style"],
96
+ img: ["alt", "class", "height", "loading", "src", "title", "width", "style"],
97
+ input: ["checked", "class", "disabled", "type"],
98
+ math: ["xmlns"],
99
+ pre: ["class"],
100
+ span: ["aria-hidden", "class", "style", "title"],
101
+ },
102
+ allowedSchemes: ["http", "https", "mailto", "tel", "note", "attach"],
103
+ allowedSchemesByTag: {
104
+ img: ["http", "https", "attach"],
105
+ },
106
+ allowedStyles: {
107
+ div: {
108
+ height: [/^\d+(?:\.\d+)?px$/],
109
+ },
110
+ img: {
111
+ "max-height": [/^none$/, /^\d+(?:\.\d+)?px$/],
112
+ "max-width": [/^\d+(?:\.\d+)?px$/],
113
+ },
114
+ span: {
115
+ "margin-right": [/^-?\d+(?:\.\d+)?em$/],
116
+ top: [/^-?\d+(?:\.\d+)?em$/],
117
+ width: [/^\d+(?:\.\d+)?%$/],
118
+ },
119
+ },
120
+ });
121
+
122
+ /**
123
+ * Render markdown to HTML for server-side display.
124
+ * The output matches the visual styling of the CodeMirror editor.
125
+ *
126
+ * Supported features:
127
+ * - GFM (GitHub Flavored Markdown)
128
+ * - Notice cards (:::note, :::info, :::success, :::warning, :::danger)
129
+ * - Task lists with checkboxes
130
+ * - Tables with cell formatting
131
+ * - Styled links and images
132
+ * - Code blocks with language badges
133
+ * - Mermaid diagram containers (requires client-side init)
134
+ *
135
+ * @example
136
+ * ```tsx
137
+ * // In page.tsx (server-side):
138
+ * import { renderMarkdown } from "@/shared/markdown";
139
+ * const html = renderMarkdown(markdownContent);
140
+ *
141
+ * // Pass to MarkdownView component:
142
+ * import { MarkdownView } from "@k2b/ui";
143
+ * <MarkdownView trustedHtml={html} />
144
+ * ```
145
+ *
146
+ * @see MarkdownView component for displaying the rendered HTML
147
+ * @see initMarkdownEnhancements for client-side Mermaid support
148
+ */
149
+ export function renderMarkdown(content: string): string {
150
+ if (!content || typeof content !== "string") return "";
151
+
152
+ const html = marked.parse(content);
153
+ if (typeof html !== "string") return "";
154
+
155
+ return sanitizeRenderedHtml(html);
156
+ }
157
+
158
+ /**
159
+ * Render markdown to HTML synchronously.
160
+ */
161
+ export function renderMarkdownSync(content: string): string {
162
+ if (!content || typeof content !== "string") return "";
163
+
164
+ const html = marked.parse(content);
165
+ if (typeof html !== "string") return "";
166
+
167
+ return sanitizeRenderedHtml(html);
168
+ }
169
+
170
+ /**
171
+ * Render trusted documentation Markdown with guided sections and internal
172
+ * navigation. All fenced code remains visible source, as in ordinary Markdown.
173
+ */
174
+ export function renderHelpMarkdown(content: string): string {
175
+ if (!content || typeof content !== "string") return "";
176
+ const html = helpMarked.parse(content);
177
+ return typeof html === "string" ? sanitizeRenderedHtml(html) : "";
178
+ }
179
+
180
+ /** Plain text used by lightweight client-side help search indexes. */
181
+ export function markdownToPlainText(content: string): string {
182
+ const html = renderHelpMarkdown(content);
183
+ return sanitizeHtml(html, { allowedTags: [], allowedAttributes: {} }).replace(/\s+/g, " ").trim();
184
+ }
185
+
186
+ export { marked };
187
+
188
+ export const markdown = {
189
+ render: renderMarkdown,
190
+ renderSync: renderMarkdownSync,
191
+ marked,
192
+ client: markdownClient,
193
+ } as const;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared utilities for markdown rendering
3
+ *
4
+ * Used by both CodeMirror editor extensions and marked renderer extensions
5
+ * to ensure consistent behavior and avoid code duplication.
6
+ */
7
+
8
+ /**
9
+ * Escape HTML special characters to prevent XSS
10
+ *
11
+ * @example
12
+ * escapeHtml('<script>alert("xss")</script>')
13
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
14
+ */
15
+ export const escapeHtml = (text: string): string => {
16
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
17
+ };
18
+
19
+ // =============================================================================
20
+ // Shared CSS Classes (match CodeMirror and marked renderer)
21
+ // =============================================================================
22
+
23
+ /** Image widget styles */
24
+ export const IMAGE_STYLES = {
25
+ wrapper: "md-image-widget my-2",
26
+ figure: "flex flex-col items-center justify-center max-w-full",
27
+ img: "block max-h-[400px] rounded border border-gray-200 dark:border-gray-700",
28
+ caption: "text-sm text-gray-500 dark:text-gray-400 mt-2 italic",
29
+ } as const;
30
+
31
+ /** Link widget styles */
32
+ export const LINK_STYLES = {
33
+ link: "md-link-widget",
34
+ label: "md-link-label",
35
+ icon: "md-link-icon",
36
+ } as const;
@@ -0,0 +1,130 @@
1
+ import { Buffer } from "node:buffer";
2
+
3
+ export type MockCoverTheme = "blue" | "emerald" | "amber" | "rose" | "violet" | "slate" | "teal";
4
+
5
+ export type MockCoverIcon = "book" | "camera" | "device-projector" | "microphone" | "package";
6
+
7
+ export type MockCoverOptions = {
8
+ icon?: MockCoverIcon | string;
9
+ theme?: MockCoverTheme;
10
+ seed?: string;
11
+ label?: string;
12
+ size?: number;
13
+ };
14
+
15
+ export type MockCover = {
16
+ svg: string;
17
+ dataUrl: string;
18
+ mimeType: "image/svg+xml";
19
+ };
20
+
21
+ const themes: Record<MockCoverTheme, { from: string; to: string; accent: string; panel: string }> = {
22
+ blue: { from: "#dbeafe", to: "#93c5fd", accent: "#2563eb", panel: "#eff6ff" },
23
+ emerald: { from: "#d1fae5", to: "#6ee7b7", accent: "#059669", panel: "#ecfdf5" },
24
+ amber: { from: "#fef3c7", to: "#fbbf24", accent: "#d97706", panel: "#fffbeb" },
25
+ rose: { from: "#ffe4e6", to: "#fda4af", accent: "#e11d48", panel: "#fff1f2" },
26
+ violet: { from: "#ede9fe", to: "#c4b5fd", accent: "#7c3aed", panel: "#f5f3ff" },
27
+ slate: { from: "#e2e8f0", to: "#94a3b8", accent: "#475569", panel: "#f8fafc" },
28
+ teal: { from: "#ccfbf1", to: "#5eead4", accent: "#0f766e", panel: "#f0fdfa" },
29
+ };
30
+
31
+ const themeNames = Object.keys(themes) as MockCoverTheme[];
32
+
33
+ const iconPaths: Record<MockCoverIcon, string[]> = {
34
+ book: ["M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0", "M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0", "M3 6l0 13", "M12 6l0 13", "M21 6l0 13"],
35
+ camera: [
36
+ "M5 7h1a2 2 0 0 0 2 -2a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1a2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2",
37
+ "M9 13a3 3 0 1 0 6 0a3 3 0 0 0 -6 0",
38
+ ],
39
+ "device-projector": [
40
+ "M8 9a5 5 0 1 0 10 0a5 5 0 0 0 -10 0",
41
+ "M9 6h-4a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h14a2 2 0 0 0 2 -2v-8a2 2 0 0 0 -2 -2h-2",
42
+ "M6 15h1",
43
+ "M7 18l-1 2",
44
+ "M18 18l1 2",
45
+ ],
46
+ microphone: [
47
+ "M9 5a3 3 0 0 1 3 -3a3 3 0 0 1 3 3v5a3 3 0 0 1 -3 3a3 3 0 0 1 -3 -3l0 -5",
48
+ "M5 10a7 7 0 0 0 14 0",
49
+ "M8 21l8 0",
50
+ "M12 17l0 4",
51
+ ],
52
+ package: ["M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5", "M12 12l8 -4.5", "M12 12l0 9", "M12 12l-8 -4.5", "M16 5.25l-8 4.5"],
53
+ };
54
+
55
+ const aliases: Record<string, MockCoverIcon> = {
56
+ books: "book",
57
+ "ti ti-book": "book",
58
+ "ti ti-books": "book",
59
+ "ti ti-camera": "camera",
60
+ "ti ti-device-projector": "device-projector",
61
+ "ti ti-microphone": "microphone",
62
+ "ti ti-package": "package",
63
+ "ti ti-packages": "package",
64
+ };
65
+
66
+ const hashSeed = (seed: string): number => {
67
+ let hash = 2166136261;
68
+ for (let i = 0; i < seed.length; i += 1) {
69
+ hash ^= seed.charCodeAt(i);
70
+ hash = Math.imul(hash, 16777619);
71
+ }
72
+ return hash >>> 0;
73
+ };
74
+
75
+ const escapeXml = (value: string): string =>
76
+ value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
77
+
78
+ const resolveIcon = (icon: MockCoverOptions["icon"]): MockCoverIcon => {
79
+ const key = String(icon ?? "package")
80
+ .trim()
81
+ .toLowerCase();
82
+ if (key in iconPaths) return key as MockCoverIcon;
83
+ return aliases[key] ?? "package";
84
+ };
85
+
86
+ export const createMockCoverSvg = (options: MockCoverOptions = {}): string => {
87
+ const size = Math.max(160, Math.min(options.size ?? 720, 1600));
88
+ const seed = options.seed ?? options.label ?? options.icon ?? "mock-cover";
89
+ const themeName = options.theme ?? themeNames[hashSeed(String(seed)) % themeNames.length] ?? "blue";
90
+ const theme = themes[themeName];
91
+ const icon = resolveIcon(options.icon);
92
+ const gradientId = `g${hashSeed(`${seed}:gradient`).toString(36)}`;
93
+ const title = escapeXml(options.label ?? "Mock cover");
94
+ const iconMarkup = iconPaths[icon].map((path) => `<path d="${path}" />`).join("");
95
+
96
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 720 720" role="img" aria-label="${title}">
97
+ <defs>
98
+ <linearGradient id="${gradientId}" x1="72" x2="648" y1="72" y2="648" gradientUnits="userSpaceOnUse">
99
+ <stop offset="0" stop-color="${theme.from}" />
100
+ <stop offset="1" stop-color="${theme.to}" />
101
+ </linearGradient>
102
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
103
+ <feDropShadow dx="0" dy="22" stdDeviation="24" flood-color="#0f172a" flood-opacity=".16" />
104
+ </filter>
105
+ </defs>
106
+ <rect width="720" height="720" fill="url(#${gradientId})" />
107
+ <rect x="160" y="160" width="400" height="400" rx="96" fill="${theme.panel}" opacity=".92" filter="url(#shadow)" />
108
+ <g transform="translate(216 216) scale(12)" fill="none" stroke="${theme.accent}" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round">
109
+ ${iconMarkup}
110
+ </g>
111
+ </svg>`;
112
+ };
113
+
114
+ export const createMockCover = (options: MockCoverOptions = {}): MockCover => {
115
+ const svg = createMockCoverSvg(options);
116
+ return {
117
+ svg,
118
+ dataUrl: `data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`,
119
+ mimeType: "image/svg+xml",
120
+ };
121
+ };
122
+
123
+ export const parseDataUrl = (dataUrl: string): { mimeType: string; bytes: Uint8Array } | null => {
124
+ const match = /^data:([^;,]+);base64,(.+)$/s.exec(dataUrl);
125
+ if (!match) return null;
126
+ return {
127
+ mimeType: match[1] || "application/octet-stream",
128
+ bytes: new Uint8Array(Buffer.from(match[2] ?? "", "base64")),
129
+ };
130
+ };
@@ -0,0 +1,106 @@
1
+ const ipv4ToNumber = (address: string): number | null => {
2
+ const parts = address.split(".");
3
+ if (parts.length !== 4 || parts.some((part) => !/^(0|[1-9]\d{0,2})$/.test(part))) return null;
4
+ const values = parts.map(Number);
5
+ if (values.some((part) => part > 255)) return null;
6
+ return values.reduce((value, part) => (value * 256 + part) >>> 0, 0);
7
+ };
8
+
9
+ const ipv4InRange = (address: string, base: string, bits: number): boolean => {
10
+ const value = ipv4ToNumber(address);
11
+ const baseValue = ipv4ToNumber(base);
12
+ if (value === null || baseValue === null) return true;
13
+ const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;
14
+ return (value & mask) === (baseValue & mask);
15
+ };
16
+
17
+ const UNSAFE_IPV4_RANGES: ReadonlyArray<readonly [string, number]> = [
18
+ ["0.0.0.0", 8],
19
+ ["10.0.0.0", 8],
20
+ ["100.64.0.0", 10],
21
+ ["127.0.0.0", 8],
22
+ ["169.254.0.0", 16],
23
+ ["172.16.0.0", 12],
24
+ ["192.0.0.0", 24],
25
+ ["192.0.2.0", 24],
26
+ ["192.88.99.0", 24],
27
+ ["192.168.0.0", 16],
28
+ ["198.18.0.0", 15],
29
+ ["198.51.100.0", 24],
30
+ ["203.0.113.0", 24],
31
+ ["224.0.0.0", 4],
32
+ ["240.0.0.0", 4],
33
+ ];
34
+
35
+ const ipv6ToBigInt = (rawAddress: string): bigint | null => {
36
+ let address = rawAddress.toLowerCase().split("%")[0] ?? "";
37
+ if (address.includes(".")) {
38
+ const separator = address.lastIndexOf(":");
39
+ const ipv4 = ipv4ToNumber(address.slice(separator + 1));
40
+ if (separator < 0 || ipv4 === null) return null;
41
+ address = `${address.slice(0, separator)}:${(ipv4 >>> 16).toString(16)}:${(ipv4 & 0xffff).toString(16)}`;
42
+ }
43
+ const halves = address.split("::");
44
+ if (halves.length > 2) return null;
45
+ const left = halves[0] ? halves[0].split(":") : [];
46
+ const right = halves[1] ? halves[1].split(":") : [];
47
+ const missing = halves.length === 2 ? 8 - left.length - right.length : 0;
48
+ if (missing < 0 || (halves.length === 1 && left.length !== 8)) return null;
49
+ const groups = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
50
+ if (groups.length !== 8 || groups.some((group) => !/^[0-9a-f]{1,4}$/.test(group))) return null;
51
+ return groups.reduce((value, group) => (value << 16n) | BigInt(`0x${group}`), 0n);
52
+ };
53
+
54
+ const ipv6InRange = (address: bigint, base: bigint, bits: number): boolean => {
55
+ const shift = BigInt(128 - bits);
56
+ return address >> shift === base >> shift;
57
+ };
58
+
59
+ const UNSAFE_IPV6_RANGES: ReadonlyArray<readonly [bigint, number]> = [
60
+ [ipv6ToBigInt("::")!, 96],
61
+ [ipv6ToBigInt("64:ff9b::")!, 96],
62
+ [ipv6ToBigInt("64:ff9b:1::")!, 48],
63
+ [ipv6ToBigInt("100::")!, 64],
64
+ [ipv6ToBigInt("2001::")!, 32],
65
+ [ipv6ToBigInt("2001:2::")!, 48],
66
+ [ipv6ToBigInt("2001:db8::")!, 32],
67
+ [ipv6ToBigInt("2002::")!, 16],
68
+ [ipv6ToBigInt("fc00::")!, 7],
69
+ [ipv6ToBigInt("fe80::")!, 10],
70
+ [ipv6ToBigInt("fec0::")!, 10],
71
+ [ipv6ToBigInt("ff00::")!, 8],
72
+ ];
73
+
74
+ export const normalizeNetworkHostname = (hostname: string): string =>
75
+ hostname
76
+ .toLowerCase()
77
+ .replace(/^\[|\]$/g, "")
78
+ .replace(/\.$/, "");
79
+
80
+ export const networkAddressFamily = (rawAddress: string): 4 | 6 | null => {
81
+ const address = normalizeNetworkHostname(rawAddress);
82
+ if (ipv4ToNumber(address) !== null) return 4;
83
+ return ipv6ToBigInt(address) !== null ? 6 : null;
84
+ };
85
+
86
+ export const isUnsafeNetworkAddress = (rawAddress: string): boolean => {
87
+ const address = normalizeNetworkHostname(rawAddress);
88
+ const family = networkAddressFamily(address);
89
+ if (family === 4) return UNSAFE_IPV4_RANGES.some(([base, bits]) => ipv4InRange(address, base, bits));
90
+ if (family !== 6) return true;
91
+ const value = ipv6ToBigInt(address);
92
+ if (value === null) return true;
93
+ if (value >> 32n === 0xffffn) {
94
+ const mapped = Number(value & 0xffffffffn);
95
+ const ipv4 = [mapped >>> 24, (mapped >>> 16) & 255, (mapped >>> 8) & 255, mapped & 255].join(".");
96
+ return isUnsafeNetworkAddress(ipv4);
97
+ }
98
+ return UNSAFE_IPV6_RANGES.some(([base, bits]) => ipv6InRange(value, base, bits));
99
+ };
100
+
101
+ export const isUnsafeNetworkHostname = (rawHostname: string): boolean => {
102
+ const hostname = normalizeNetworkHostname(rawHostname);
103
+ return (
104
+ hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "metadata.google.internal" || hostname.endsWith(".internal")
105
+ );
106
+ };
@@ -0,0 +1,56 @@
1
+ const REDIRECT_BASE = "https://cloud.local";
2
+
3
+ /** Normalize a user-provided post-login redirect to a local Cloud path. */
4
+ export const normalizeRedirectTo = (value: string | null | undefined): string | undefined => {
5
+ if (!value) return undefined;
6
+
7
+ const trimmed = value.trim();
8
+ if (!trimmed || !trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.includes("\\")) return undefined;
9
+
10
+ try {
11
+ const url = new URL(trimmed, REDIRECT_BASE);
12
+ if (url.origin !== REDIRECT_BASE) return undefined;
13
+ return `${url.pathname}${url.search}${url.hash}`;
14
+ } catch {
15
+ return undefined;
16
+ }
17
+ };
18
+
19
+ /** Extract a safe post-login redirect target from an incoming request URL. */
20
+ export const redirectPathFromRequestUrl = (requestUrl: string): string => {
21
+ const url = new URL(requestUrl);
22
+ return normalizeRedirectTo(`${url.pathname}${url.search}`) ?? "/";
23
+ };
24
+
25
+ /** Build the local login URL for an incoming protected request. */
26
+ export const createLoginRedirectUrl = (requestUrl: string): string => {
27
+ const params = new URLSearchParams();
28
+ params.set("redirectTo", redirectPathFromRequestUrl(requestUrl));
29
+ return `/auth/login?${params.toString()}`;
30
+ };
31
+
32
+ /** Build an absolute auth login URL while preserving only safe local redirects. */
33
+ export const createAuthLoginUrl = (
34
+ appUrl: string,
35
+ params: { token?: string; method?: "email" | "ipa" | "admin"; redirectTo?: string | null | undefined } = {},
36
+ ): string => {
37
+ const url = new URL(`${appUrl.replace(/\/$/, "")}/auth/login`);
38
+ if (params.token) url.searchParams.set("token", params.token);
39
+ if (params.method) url.searchParams.set("method", params.method);
40
+
41
+ const redirectTo = normalizeRedirectTo(params.redirectTo);
42
+ if (redirectTo) url.searchParams.set("redirectTo", redirectTo);
43
+
44
+ return url.toString();
45
+ };
46
+
47
+ /** Build an absolute password-reset URL while preserving only safe local redirects. */
48
+ export const createAuthPasswordResetUrl = (appUrl: string, params: { token: string; redirectTo?: string | null | undefined }): string => {
49
+ const url = new URL(`${appUrl.replace(/\/$/, "")}/auth/password-reset`);
50
+ url.searchParams.set("token", params.token);
51
+
52
+ const redirectTo = normalizeRedirectTo(params.redirectTo);
53
+ if (redirectTo) url.searchParams.set("redirectTo", redirectTo);
54
+
55
+ return url.toString();
56
+ };
@@ -0,0 +1,140 @@
1
+ import { Liquid } from "liquidjs";
2
+
3
+ const TEMPLATE_MAX_BYTES = 200_000;
4
+ const RENDER_MAX_BYTES = 300_000;
5
+
6
+ type LiquidEngine = Liquid;
7
+ export type LiquidTemplateErrorReason = "render_too_large";
8
+
9
+ export class LiquidTemplateError extends Error {
10
+ constructor(
11
+ readonly reason: LiquidTemplateErrorReason,
12
+ message: string,
13
+ ) {
14
+ super(message);
15
+ this.name = "LiquidTemplateError";
16
+ }
17
+ }
18
+
19
+ export type LiquidTemplateFilter = Parameters<LiquidEngine["registerFilter"]>[1];
20
+ export type LiquidTemplateOptions = {
21
+ filters?: Record<string, LiquidTemplateFilter>;
22
+ escapeOutput?: boolean | ((value: unknown) => string);
23
+ templateMaxBytes?: number;
24
+ renderMaxBytes?: number;
25
+ memoryLimit?: number;
26
+ };
27
+
28
+ const ALLOWED_TAGS = new Set([
29
+ "if",
30
+ "elsif",
31
+ "else",
32
+ "endif",
33
+ "unless",
34
+ "endunless",
35
+ "for",
36
+ "break",
37
+ "continue",
38
+ "endfor",
39
+ "case",
40
+ "when",
41
+ "endcase",
42
+ "assign",
43
+ "capture",
44
+ "endcapture",
45
+ "comment",
46
+ "endcomment",
47
+ "raw",
48
+ "endraw",
49
+ ]);
50
+
51
+ const TEMPLATE_TAG_RE = /{%-?\s*([A-Za-z_][A-Za-z0-9_]*)\b/g;
52
+
53
+ const byteLength = (value: string): number => new TextEncoder().encode(value).byteLength;
54
+
55
+ export const escapeTemplateOutput = (value: unknown): string =>
56
+ String(value).replace(/[&<>"'`=/]/g, (char) => {
57
+ switch (char) {
58
+ case "&":
59
+ return "&amp;";
60
+ case "<":
61
+ return "&lt;";
62
+ case ">":
63
+ return "&gt;";
64
+ case '"':
65
+ return "&quot;";
66
+ case "'":
67
+ return "&#39;";
68
+ case "`":
69
+ return "&#x60;";
70
+ case "=":
71
+ return "&#x3D;";
72
+ case "/":
73
+ return "&#x2F;";
74
+ default:
75
+ return char;
76
+ }
77
+ });
78
+
79
+ const createEngine = (options: LiquidTemplateOptions = {}) => {
80
+ const outputEscape =
81
+ typeof options.escapeOutput === "function" ? options.escapeOutput : options.escapeOutput === false ? undefined : escapeTemplateOutput;
82
+ const engine = new Liquid({
83
+ strictVariables: true,
84
+ strictFilters: true,
85
+ ownPropertyOnly: true,
86
+ ...(outputEscape ? { outputEscape } : {}),
87
+ parseLimit: options.templateMaxBytes ?? TEMPLATE_MAX_BYTES,
88
+ renderLimit: options.renderMaxBytes ?? RENDER_MAX_BYTES,
89
+ memoryLimit: options.memoryLimit ?? 2_000_000,
90
+ cache: false,
91
+ dynamicPartials: false,
92
+ root: [],
93
+ layouts: [],
94
+ partials: [],
95
+ });
96
+
97
+ for (const [name, filter] of Object.entries(options.filters ?? {})) engine.registerFilter(name, filter);
98
+ return engine;
99
+ };
100
+
101
+ const defaultEngine = createEngine();
102
+ const engineFor = (options: LiquidTemplateOptions = {}) => (Object.keys(options).length > 0 ? createEngine(options) : defaultEngine);
103
+
104
+ export const migrateLegacyMustacheTemplate = (template: string): string =>
105
+ template
106
+ .replace(/{{#\s*([A-Za-z_][A-Za-z0-9_]*)\s*}}([\s\S]*?){{\/\s*\1\s*}}/g, "{% if $1 != blank %}$2{% endif %}")
107
+ .replace(/{{\^\s*([A-Za-z_][A-Za-z0-9_]*)\s*}}([\s\S]*?){{\/\s*\1\s*}}/g, "{% if $1 == blank %}$2{% endif %}");
108
+
109
+ export const validateLiquidTemplate = (
110
+ template: string,
111
+ options: LiquidTemplateOptions = {},
112
+ ): { ok: true } | { ok: false; error: string } => {
113
+ if (byteLength(template) > (options.templateMaxBytes ?? TEMPLATE_MAX_BYTES)) return { ok: false, error: "Template is too large" };
114
+ for (const match of template.matchAll(TEMPLATE_TAG_RE)) {
115
+ const tag = match[1]!;
116
+ if (!ALLOWED_TAGS.has(tag)) return { ok: false, error: `Liquid tag "${tag}" is not allowed` };
117
+ }
118
+ try {
119
+ engineFor(options).parse(template);
120
+ return { ok: true };
121
+ } catch (error) {
122
+ return { ok: false, error: error instanceof Error ? error.message : "Invalid Liquid template" };
123
+ }
124
+ };
125
+
126
+ export const renderLiquidTemplate = (template: string, data: Record<string, unknown>, options: LiquidTemplateOptions = {}): string => {
127
+ const valid = validateLiquidTemplate(template, options);
128
+ if (!valid.ok) throw new Error(valid.error);
129
+ const rendered = engineFor(options).parseAndRenderSync(template, data);
130
+ if (byteLength(rendered) > (options.renderMaxBytes ?? RENDER_MAX_BYTES)) {
131
+ throw new LiquidTemplateError("render_too_large", "Rendered template is too large");
132
+ }
133
+ return rendered;
134
+ };
135
+
136
+ export const liquidTemplateVariables = (template: string, options: LiquidTemplateOptions = {}): string[] => {
137
+ const valid = validateLiquidTemplate(template, options);
138
+ if (!valid.ok) throw new Error(valid.error);
139
+ return engineFor(options).globalFullVariablesSync(template);
140
+ };