@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,516 @@
1
+ /**
2
+ * Notification delivery, batch campaigns, and announcements.
3
+ */
4
+ import { arg, command, confirmFlag, flag, paginationFlags, readCliInput } from "../index";
5
+ import {
6
+ apiGet,
7
+ apiJson,
8
+ type Pagination,
9
+ pageQuery,
10
+ printJsonOrTable,
11
+ queryString,
12
+ readJsonInput,
13
+ readOptionalInput,
14
+ truncate,
15
+ } from "./shared";
16
+
17
+ export type Notification = {
18
+ id: string;
19
+ recipient: string;
20
+ subject: string;
21
+ status: "sent" | "pending" | "error";
22
+ error: string | null;
23
+ sentAt: string | null;
24
+ createdAt: string;
25
+ };
26
+
27
+ export type NotificationBatchStatus = "draft" | "ready" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled";
28
+ export type NotificationRecipientStatus = "pending" | "sending" | "sent" | "skipped" | "error";
29
+
30
+ export type NotificationBatch = {
31
+ id: string;
32
+ subject: string;
33
+ bodyMarkdown: string;
34
+ selection: Record<string, unknown>;
35
+ selectionHash: string;
36
+ status: NotificationBatchStatus;
37
+ createdAt: string;
38
+ finalizedAt: string | null;
39
+ targetCount: number;
40
+ deliverableCount: number;
41
+ sentCount: number;
42
+ skippedCount: number;
43
+ errorCount: number;
44
+ lastError: string | null;
45
+ };
46
+
47
+ export type NotificationBatchRecipient = {
48
+ batchId: string;
49
+ userId: string;
50
+ recipient: string | null;
51
+ uid: string;
52
+ displayName: string;
53
+ provider: "local" | "ipa";
54
+ profile: "user" | "guest";
55
+ status: NotificationRecipientStatus;
56
+ notificationId: string | null;
57
+ error: string | null;
58
+ attemptCount: number;
59
+ sentAt: string | null;
60
+ updatedAt: string;
61
+ };
62
+
63
+ export type NotificationBatchPreview = {
64
+ targetCount: number;
65
+ deliverableCount: number;
66
+ skippedNoEmailCount: number;
67
+ duplicateCount: number;
68
+ recipientHash: string;
69
+ };
70
+
71
+ export type Announcement = {
72
+ id: string;
73
+ version: number;
74
+ kind: "announcement" | "banner";
75
+ title: string;
76
+ tone: "info" | "success" | "warning" | "danger";
77
+ publishedAt: string;
78
+ expiresAt: string | null;
79
+ };
80
+
81
+ export const parseExpiresAt = (value: string | undefined): string | null | undefined => {
82
+ if (value === undefined) return undefined;
83
+ if (value === "never" || value === "null") return null;
84
+ const date = new Date(value);
85
+ if (Number.isNaN(date.getTime())) throw new Error('--expires-at must be an ISO timestamp, "never", or "null".');
86
+ return date.toISOString();
87
+ };
88
+
89
+ export const announcementRows = (items: Announcement[]) =>
90
+ items.map((item) => ({
91
+ version: item.version,
92
+ kind: item.kind,
93
+ tone: item.tone,
94
+ title: item.title,
95
+ published: item.publishedAt,
96
+ expires: item.expiresAt ?? "",
97
+ id: item.id,
98
+ }));
99
+
100
+ export const batchRows = (items: NotificationBatch[]) =>
101
+ items.map((batch) => ({
102
+ status: batch.status,
103
+ subject: truncate(batch.subject, 72),
104
+ targets: batch.targetCount,
105
+ deliverable: batch.deliverableCount,
106
+ sent: batch.sentCount,
107
+ errors: batch.errorCount,
108
+ created: batch.createdAt,
109
+ id: batch.id,
110
+ }));
111
+
112
+ export const previewRows = (preview: NotificationBatchPreview) => [
113
+ {
114
+ targets: preview.targetCount,
115
+ deliverable: preview.deliverableCount,
116
+ skippedNoEmail: preview.skippedNoEmailCount,
117
+ duplicates: preview.duplicateCount,
118
+ recipientHash: preview.recipientHash,
119
+ },
120
+ ];
121
+
122
+ export const notificationCommands = [
123
+ command("notifications list", {
124
+ summary: "List email notifications",
125
+ flags: {
126
+ search: flag.string({ aliases: ["q"], description: "Search notifications" }),
127
+ status: flag.enum(["sent", "pending", "error"], { description: "Notification status" }),
128
+ ...paginationFlags({ defaultPerPage: 50, maxPerPage: 100 }),
129
+ },
130
+ run: async ({ ctx, flags }) => {
131
+ const result = await apiGet<{ notifications: Notification[]; pagination: Pagination }>(
132
+ ctx,
133
+ `/api/notifications${queryString({ search: flags.search, status: flags.status, ...pageQuery(flags) })}`,
134
+ );
135
+ const rows = result.notifications.map((item) => ({
136
+ status: item.status,
137
+ recipient: item.recipient,
138
+ subject: truncate(item.subject, 72),
139
+ error: truncate(item.error, 48),
140
+ created: item.createdAt,
141
+ id: item.id,
142
+ }));
143
+ printJsonOrTable(ctx, result, rows, [
144
+ { key: "status" },
145
+ { key: "recipient" },
146
+ { key: "subject" },
147
+ { key: "error" },
148
+ { key: "created" },
149
+ { key: "id" },
150
+ ]);
151
+ },
152
+ }),
153
+ command("notifications summary", {
154
+ summary: "Show notification status summary",
155
+ run: async ({ ctx }) => {
156
+ const result = await apiGet<Record<string, number>>(ctx, "/api/notifications/summary");
157
+ printJsonOrTable(ctx, result, [result], [{ key: "sent" }, { key: "pending" }, { key: "error" }]);
158
+ },
159
+ }),
160
+ command("notifications get", {
161
+ summary: "Show one notification",
162
+ args: { id: arg.required({ valueLabel: "id" }) },
163
+ run: async ({ ctx, args }) => {
164
+ const result = await apiGet<Notification>(ctx, `/api/notifications/${encodeURIComponent(args.id)}`);
165
+ printJsonOrTable(
166
+ ctx,
167
+ result,
168
+ [result as unknown as Record<string, unknown>],
169
+ [
170
+ { key: "status" },
171
+ { key: "recipient" },
172
+ { key: "subject" },
173
+ { key: "error" },
174
+ { key: "sentAt" },
175
+ { key: "createdAt" },
176
+ { key: "id" },
177
+ ],
178
+ );
179
+ },
180
+ }),
181
+ command("notifications resend", {
182
+ summary: "Resend a pending or failed notification",
183
+ args: { id: arg.required({ valueLabel: "id" }) },
184
+ flags: { yes: confirmFlag("Confirm resending this notification") },
185
+ run: async ({ ctx, args, flags }) => {
186
+ if (!flags.yes) throw new Error("Refusing to resend a notification without --yes.");
187
+ const result = await apiJson<{ message: string }>(ctx, "POST", `/api/notifications/${encodeURIComponent(args.id)}/resend`);
188
+ if (ctx.options.output === "json") ctx.json(result);
189
+ else ctx.print(result.message);
190
+ },
191
+ }),
192
+ command("notifications pending-system", {
193
+ summary: "Show pending system notification count",
194
+ run: async ({ ctx }) => {
195
+ const result = await apiGet<{ count: number }>(ctx, "/api/notifications/pending-system/count");
196
+ printJsonOrTable(ctx, result, [result], [{ key: "count" }]);
197
+ },
198
+ }),
199
+ command("notifications send-pending-system", {
200
+ summary: "Send all pending system notifications",
201
+ flags: { yes: confirmFlag("Confirm sending all pending system notifications") },
202
+ run: async ({ ctx, flags }) => {
203
+ if (!flags.yes) throw new Error("Refusing to send all pending system notifications without --yes.");
204
+ const result = await apiJson<unknown>(ctx, "POST", "/api/notifications/pending-system/send-all");
205
+ if (ctx.options.output === "json") ctx.json(result);
206
+ else ctx.print("Pending system notification send submitted.");
207
+ },
208
+ }),
209
+ command("notification-batches list", {
210
+ summary: "List account notification batches",
211
+ flags: {
212
+ status: flag.enum(["draft", "ready", "running", "completed", "completed_with_errors", "failed", "cancelled"], {
213
+ description: "Batch status",
214
+ }),
215
+ ...paginationFlags({ defaultPerPage: 50, maxPerPage: 100 }),
216
+ },
217
+ run: async ({ ctx, flags }) => {
218
+ const result = await apiGet<{ batches: NotificationBatch[]; pagination: Pagination }>(
219
+ ctx,
220
+ `/api/accounts/notifications/batches${queryString({ status: flags.status, ...pageQuery(flags) })}`,
221
+ );
222
+ printJsonOrTable(ctx, result, batchRows(result.batches), [
223
+ { key: "status" },
224
+ { key: "subject" },
225
+ { key: "targets" },
226
+ { key: "deliverable" },
227
+ { key: "sent" },
228
+ { key: "errors" },
229
+ { key: "created" },
230
+ { key: "id" },
231
+ ]);
232
+ },
233
+ }),
234
+ command("notification-batches preview", {
235
+ summary: "Resolve notification batch recipients from a selection JSON body",
236
+ flags: {
237
+ selection: flag.input({
238
+ fileName: "selection-file",
239
+ fileAliases: ["f"],
240
+ required: true,
241
+ description: "Audience selection JSON",
242
+ }),
243
+ },
244
+ run: async ({ ctx, flags }) => {
245
+ const selection = await readJsonInput<Record<string, unknown>>(flags.selection, "notification batch selection");
246
+ const result = await apiJson<NotificationBatchPreview>(ctx, "POST", "/api/accounts/notifications/batches/preview", { selection });
247
+ printJsonOrTable(ctx, result, previewRows(result), [
248
+ { key: "targets" },
249
+ { key: "deliverable" },
250
+ { key: "skippedNoEmail" },
251
+ { key: "duplicates" },
252
+ { key: "recipientHash" },
253
+ ]);
254
+ },
255
+ }),
256
+ command("notification-batches create", {
257
+ summary: "Create a draft account notification batch",
258
+ flags: {
259
+ subject: flag.string({ required: true, description: "Email subject" }),
260
+ body: flag.input({
261
+ fileName: "body-file",
262
+ fileAliases: ["b"],
263
+ required: true,
264
+ description: "Markdown body",
265
+ }),
266
+ selection: flag.input({
267
+ fileName: "selection-file",
268
+ fileAliases: ["f"],
269
+ required: true,
270
+ description: "Audience selection JSON",
271
+ }),
272
+ },
273
+ run: async ({ ctx, flags }) => {
274
+ const [bodyMarkdown, selection] = await Promise.all([
275
+ readCliInput(flags.body, { label: "notification batch body", required: true }),
276
+ readJsonInput<Record<string, unknown>>(flags.selection, "notification batch selection"),
277
+ ]);
278
+ const result = await apiJson<NotificationBatch>(ctx, "POST", "/api/accounts/notifications/batches", {
279
+ subject: flags.subject,
280
+ bodyMarkdown,
281
+ selection,
282
+ });
283
+ const row = batchRows([result])[0]!;
284
+ printJsonOrTable(
285
+ ctx,
286
+ result,
287
+ [row],
288
+ [
289
+ { key: "status" },
290
+ { key: "subject" },
291
+ { key: "targets" },
292
+ { key: "deliverable" },
293
+ { key: "sent" },
294
+ { key: "errors" },
295
+ { key: "created" },
296
+ { key: "id" },
297
+ ],
298
+ );
299
+ },
300
+ }),
301
+ command("notification-batches get", {
302
+ summary: "Show one account notification batch",
303
+ args: { id: arg.required({ valueLabel: "id" }) },
304
+ run: async ({ ctx, args }) => {
305
+ const result = await apiGet<NotificationBatch>(ctx, `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}`);
306
+ const row = batchRows([result])[0]!;
307
+ printJsonOrTable(
308
+ ctx,
309
+ result,
310
+ [row],
311
+ [
312
+ { key: "status" },
313
+ { key: "subject" },
314
+ { key: "targets" },
315
+ { key: "deliverable" },
316
+ { key: "sent" },
317
+ { key: "errors" },
318
+ { key: "created" },
319
+ { key: "id" },
320
+ ],
321
+ );
322
+ },
323
+ }),
324
+ command("notification-batches finalize", {
325
+ summary: "Finalize a draft batch and submit async delivery",
326
+ args: { id: arg.required({ valueLabel: "batch-id" }) },
327
+ flags: { yes: confirmFlag("Confirm finalizing and sending this notification batch") },
328
+ run: async ({ ctx, args, flags }) => {
329
+ if (!flags.yes) throw new Error("Refusing to finalize a notification batch without --yes.");
330
+ const batch = await apiGet<NotificationBatch>(ctx, `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}`);
331
+ if (batch.status !== "draft") throw new Error(`Only draft batches can be finalized. Current status: ${batch.status}.`);
332
+ const preview = await apiJson<NotificationBatchPreview>(ctx, "POST", "/api/accounts/notifications/batches/preview", {
333
+ selection: batch.selection,
334
+ });
335
+ if (preview.deliverableCount <= 0) throw new Error("No deliverable recipients match this notification batch.");
336
+ const result = await apiJson<{ batch: NotificationBatch; jobId: string }>(
337
+ ctx,
338
+ "POST",
339
+ `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}/finalize`,
340
+ {
341
+ expectedSelectionHash: batch.selectionHash,
342
+ expectedDeliverableCount: preview.deliverableCount,
343
+ expectedRecipientHash: preview.recipientHash,
344
+ },
345
+ );
346
+ if (ctx.options.output === "json") ctx.json(result);
347
+ else ctx.print(`Finalized batch ${result.batch.id}. Delivery job: ${result.jobId}`);
348
+ },
349
+ }),
350
+ command("notification-batches recipients", {
351
+ summary: "List account notification batch recipients",
352
+ args: { id: arg.required({ valueLabel: "batch-id" }) },
353
+ flags: {
354
+ status: flag.enum(["pending", "sending", "sent", "skipped", "error"], { description: "Recipient status" }),
355
+ ...paginationFlags({ defaultPerPage: 100, maxPerPage: 100 }),
356
+ },
357
+ run: async ({ ctx, args, flags }) => {
358
+ const result = await apiGet<{ recipients: NotificationBatchRecipient[]; pagination: Pagination }>(
359
+ ctx,
360
+ `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}/recipients${queryString({
361
+ status: flags.status,
362
+ ...pageQuery(flags),
363
+ })}`,
364
+ );
365
+ const rows = result.recipients.map((recipient) => ({
366
+ status: recipient.status,
367
+ user: recipient.displayName || recipient.uid,
368
+ uid: recipient.uid,
369
+ email: recipient.recipient ?? "",
370
+ provider: recipient.provider,
371
+ profile: recipient.profile,
372
+ attempts: recipient.attemptCount,
373
+ error: truncate(recipient.error, 60),
374
+ userId: recipient.userId,
375
+ }));
376
+ printJsonOrTable(ctx, result, rows, [
377
+ { key: "status" },
378
+ { key: "user" },
379
+ { key: "uid" },
380
+ { key: "email" },
381
+ { key: "provider" },
382
+ { key: "profile" },
383
+ { key: "attempts" },
384
+ { key: "error" },
385
+ { key: "userId" },
386
+ ]);
387
+ },
388
+ }),
389
+ command("notification-batches retry-failed", {
390
+ summary: "Retry all failed recipients in a finalized batch",
391
+ args: { id: arg.required({ valueLabel: "batch-id" }) },
392
+ flags: { yes: confirmFlag("Confirm retrying failed notification recipients") },
393
+ run: async ({ ctx, args, flags }) => {
394
+ if (!flags.yes) throw new Error("Refusing to retry failed recipients without --yes.");
395
+ const result = await apiJson<{ batch: NotificationBatch; jobId: string }>(
396
+ ctx,
397
+ "POST",
398
+ `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}/retry-failed`,
399
+ );
400
+ if (ctx.options.output === "json") ctx.json(result);
401
+ else ctx.print(`Retry submitted: ${result.jobId}`);
402
+ },
403
+ }),
404
+ command("notification-batches retry-recipient", {
405
+ summary: "Retry one failed recipient in a finalized batch",
406
+ args: {
407
+ id: arg.required({ valueLabel: "batch-id" }),
408
+ userId: arg.required({ valueLabel: "user-id" }),
409
+ },
410
+ flags: { yes: confirmFlag("Confirm retrying the failed notification recipient") },
411
+ run: async ({ ctx, args, flags }) => {
412
+ if (!flags.yes) throw new Error("Refusing to retry a recipient without --yes.");
413
+ const result = await apiJson<{ batch: NotificationBatch; jobId: string }>(
414
+ ctx,
415
+ "POST",
416
+ `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}/recipients/${encodeURIComponent(args.userId)}/retry`,
417
+ );
418
+ if (ctx.options.output === "json") ctx.json(result);
419
+ else ctx.print(`Retry submitted: ${result.jobId}`);
420
+ },
421
+ }),
422
+ command("notification-batches delete-draft", {
423
+ summary: "Delete a draft account notification batch",
424
+ args: { id: arg.required({ valueLabel: "batch-id" }) },
425
+ flags: { yes: confirmFlag("Confirm deleting the draft notification batch") },
426
+ run: async ({ ctx, args, flags }) => {
427
+ if (!flags.yes) throw new Error("Refusing to delete a draft notification batch without --yes.");
428
+ const result = await apiJson<{ id: string }>(ctx, "DELETE", `/api/accounts/notifications/batches/${encodeURIComponent(args.id)}`);
429
+ if (ctx.options.output === "json") ctx.json(result);
430
+ else ctx.print(`Deleted draft ${result.id}.`);
431
+ },
432
+ }),
433
+ command("announcements list", {
434
+ summary: "List platform announcements and banners",
435
+ flags: {
436
+ kind: flag.enum(["announcement", "banner"], { description: "Announcement kind" }),
437
+ search: flag.string({ aliases: ["q"], description: "Search title or body" }),
438
+ },
439
+ run: async ({ ctx, flags }) => {
440
+ const result = await apiGet<{ items: Announcement[] }>(
441
+ ctx,
442
+ `/api/admin/core/announcements${queryString({ kind: flags.kind, search: flags.search })}`,
443
+ );
444
+ printJsonOrTable(ctx, result, announcementRows(result.items), [
445
+ { key: "version" },
446
+ { key: "kind" },
447
+ { key: "tone" },
448
+ { key: "title" },
449
+ { key: "published" },
450
+ { key: "expires" },
451
+ { key: "id" },
452
+ ]);
453
+ },
454
+ }),
455
+ command("announcements create", {
456
+ summary: "Create an announcement or banner",
457
+ flags: {
458
+ kind: flag.enum(["announcement", "banner"], { default: "announcement", description: "Entry kind" }),
459
+ title: flag.string({ required: true, description: "Entry title" }),
460
+ body: flag.input({ required: true, description: "Markdown body" }),
461
+ tone: flag.enum(["info", "success", "warning", "danger"], { default: "info", description: "Visual tone" }),
462
+ publishedAt: flag.string({ name: "published-at", description: "ISO publish timestamp" }),
463
+ expiresAt: flag.string({ name: "expires-at", description: "ISO expiry timestamp, never, or null" }),
464
+ },
465
+ run: async ({ ctx, flags }) => {
466
+ const body = await readCliInput(flags.body, { label: "announcement body", required: true });
467
+ const result = await apiJson<Announcement>(ctx, "POST", "/api/admin/core/announcements", {
468
+ kind: flags.kind,
469
+ title: flags.title,
470
+ body,
471
+ tone: flags.tone,
472
+ publishedAt: flags.publishedAt,
473
+ expiresAt: parseExpiresAt(flags.expiresAt),
474
+ });
475
+ if (ctx.options.output === "json") ctx.json(result);
476
+ else ctx.print(`Created ${result.kind} v${result.version}: ${result.title}`);
477
+ },
478
+ }),
479
+ command("announcements update", {
480
+ summary: "Update an announcement or banner",
481
+ args: { id: arg.required({ valueLabel: "id" }) },
482
+ flags: {
483
+ kind: flag.enum(["announcement", "banner"], { description: "Entry kind" }),
484
+ title: flag.string({ description: "Entry title" }),
485
+ body: flag.input({ description: "Markdown body" }),
486
+ tone: flag.enum(["info", "success", "warning", "danger"], { description: "Visual tone" }),
487
+ publishedAt: flag.string({ name: "published-at", description: "ISO publish timestamp" }),
488
+ expiresAt: flag.string({ name: "expires-at", description: "ISO expiry timestamp, never, or null" }),
489
+ },
490
+ run: async ({ ctx, args, flags }) => {
491
+ const body = await readOptionalInput(flags.body, "announcement body");
492
+ const payload: Record<string, unknown> = {};
493
+ if (flags.kind) payload.kind = flags.kind;
494
+ if (flags.title) payload.title = flags.title;
495
+ if (body !== undefined) payload.body = body;
496
+ if (flags.tone) payload.tone = flags.tone;
497
+ if (flags.publishedAt) payload.publishedAt = flags.publishedAt;
498
+ const expiresAt = parseExpiresAt(flags.expiresAt);
499
+ if (expiresAt !== undefined) payload.expiresAt = expiresAt;
500
+ const result = await apiJson<Announcement>(ctx, "PATCH", `/api/admin/core/announcements/${encodeURIComponent(args.id)}`, payload);
501
+ if (ctx.options.output === "json") ctx.json(result);
502
+ else ctx.print(`Updated ${result.kind} v${result.version}: ${result.title}`);
503
+ },
504
+ }),
505
+ command("announcements delete", {
506
+ summary: "Delete an announcement or banner",
507
+ args: { id: arg.required({ valueLabel: "id" }) },
508
+ flags: { yes: confirmFlag("Confirm announcement deletion") },
509
+ run: async ({ ctx, args, flags }) => {
510
+ if (!flags.yes) throw new Error("Refusing to delete an announcement without --yes.");
511
+ const result = await apiJson<{ message: string }>(ctx, "DELETE", `/api/admin/core/announcements/${encodeURIComponent(args.id)}`);
512
+ if (ctx.options.output === "json") ctx.json(result);
513
+ else ctx.print(result.message);
514
+ },
515
+ }),
516
+ ];
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Primitives shared by every admin command group: HTTP access against the
3
+ * Cloud API, output shaping, and the flag conventions the CLI standardises on.
4
+ */
5
+
6
+ import { formatDurationMs, formatBytes as sharedBytes } from "@k2b/cloud/shared";
7
+ import { type CliInputFlagValue, type CloudCliContext, type CloudCliTableColumn, readCliInput } from "../index";
8
+
9
+ export type Pagination = {
10
+ page: number;
11
+ per_page: number;
12
+ total: number;
13
+ total_pages: number;
14
+ };
15
+
16
+ export const apiGet = async <T>(ctx: CloudCliContext, path: string): Promise<T> => ctx.readJson<T>(await ctx.fetch(path));
17
+
18
+ export const apiJson = async <T>(ctx: CloudCliContext, method: string, path: string, body?: unknown): Promise<T> =>
19
+ ctx.readJson<T>(
20
+ await ctx.fetch(path, {
21
+ method,
22
+ headers: body === undefined ? undefined : { "Content-Type": "application/json" },
23
+ body: body === undefined ? undefined : JSON.stringify(body),
24
+ }),
25
+ );
26
+
27
+ export const printJsonOrTable = <TRow extends Record<string, unknown>>(
28
+ ctx: CloudCliContext,
29
+ raw: unknown,
30
+ rows: TRow[],
31
+ columns: CloudCliTableColumn<TRow>[],
32
+ ) => {
33
+ if (ctx.options.output === "json") ctx.json(raw);
34
+ else ctx.table(rows, columns);
35
+ };
36
+
37
+ export const queryString = (params: Record<string, string | number | boolean | null | undefined>): string => {
38
+ const search = new URLSearchParams();
39
+ for (const [key, value] of Object.entries(params)) {
40
+ if (value === undefined || value === null || value === "" || value === false) continue;
41
+ search.set(key, String(value));
42
+ }
43
+ const value = search.toString();
44
+ return value ? `?${value}` : "";
45
+ };
46
+
47
+ export const pageQuery = (flags: { page?: number; perPage?: number }) => ({
48
+ page: flags.page ?? 1,
49
+ per_page: flags.perPage ?? 50,
50
+ });
51
+
52
+ export const truncate = (value: string | null | undefined, max = 90): string => {
53
+ if (!value) return "";
54
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value;
55
+ };
56
+
57
+ export const readJsonInput = async <T>(input: CliInputFlagValue, label: string): Promise<T> => {
58
+ const raw = await readCliInput(input, { label, required: true });
59
+ try {
60
+ return JSON.parse(raw ?? "") as T;
61
+ } catch (error) {
62
+ throw new Error(`Invalid ${label} JSON: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ };
65
+
66
+ export const readOptionalInput = async (input: CliInputFlagValue, label: string): Promise<string | undefined> =>
67
+ readCliInput(input, { label, trimFinalNewline: true });
68
+
69
+ export const sortByTimeDesc = <T extends { createdAt?: string; occurredAt?: string; time?: string }>(items: T[]): T[] =>
70
+ items
71
+ .slice()
72
+ .sort(
73
+ (a, b) =>
74
+ new Date(b.createdAt ?? b.occurredAt ?? b.time ?? 0).getTime() - new Date(a.createdAt ?? a.occurredAt ?? a.time ?? 0).getTime(),
75
+ );
76
+
77
+ export const parseLookbackHours = (value: string | undefined): number => {
78
+ const raw = value?.trim() || "24h";
79
+ const match = raw.match(/^(\d+)(m|h|d)$/i);
80
+ if (!match) throw new Error("--since must be a duration like 30m, 6h, or 7d.");
81
+ const amount = Number.parseInt(match[1]!, 10);
82
+ const unit = match[2]!.toLowerCase();
83
+ const hours = unit === "m" ? Math.max(1, Math.ceil(amount / 60)) : unit === "d" ? amount * 24 : amount;
84
+ return Math.min(Math.max(hours, 1), 24 * 31);
85
+ };
86
+
87
+ export const cleanObject = <T extends Record<string, unknown>>(value: T): Partial<T> =>
88
+ Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as Partial<T>;
89
+
90
+ export const safeCollect = async <T>(
91
+ label: string,
92
+ load: () => Promise<T>,
93
+ ): Promise<{ ok: true; data: T } | { ok: false; label: string; error: string }> => {
94
+ try {
95
+ return { ok: true, data: await load() };
96
+ } catch (error) {
97
+ return { ok: false, label, error: error instanceof Error ? error.message : String(error) };
98
+ }
99
+ };
100
+
101
+ export const skippedCollect = (label: string): { ok: false; label: string; skipped: true; error: string } => ({
102
+ ok: false,
103
+ label,
104
+ skipped: true,
105
+ error: "Skipped by diagnose filters.",
106
+ });
107
+
108
+ export const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
109
+
110
+ /** CLI output stays ASCII, so absent values use a plain dash. */
111
+ export const formatBytes = (bytes: number | null | undefined): string => sharedBytes(bytes, { fallback: "-" });
112
+
113
+ export const formatMs = (ms: number | null | undefined): string => formatDurationMs(ms, { fallback: "-" });