okengine 0.18.4 → 0.19.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 (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -1,213 +0,0 @@
1
- /**
2
- * Detect local Ollama — CLI (`ollama list` / `ps`) and HTTP `/api/tags`.
3
- */
4
-
5
- import { OLLAMA_DEFAULT_BASE_URL, normalizeOllamaBaseUrl } from "../../drivers/ai-ollama.ts";
6
- import { ALL_CURATED, type CatalogModel } from "./catalog.ts";
7
-
8
- /** Result of probing the local Ollama install. */
9
- export type OllamaDetectResult = {
10
- readonly available: boolean;
11
- readonly baseUrl: string;
12
- readonly installed: readonly string[];
13
- readonly running: readonly string[];
14
- readonly curatedInstalled: readonly CatalogModel[];
15
- readonly curatedMissing: readonly CatalogModel[];
16
- };
17
-
18
- /**
19
- * Parse `ollama list` tabular output into model names.
20
- *
21
- * @param stdout - CLI stdout
22
- */
23
- export function parseOllamaList(stdout: string): string[] {
24
- const lines = stdout
25
- .split("\n")
26
- .map((l) => l.trim())
27
- .filter(Boolean);
28
- const out: string[] = [];
29
- for (const line of lines) {
30
- if (/^NAME\b/i.test(line)) continue;
31
- const name = line.split(/\s+/)[0];
32
- if (name) out.push(name);
33
- }
34
- return out;
35
- }
36
-
37
- /**
38
- * Parse `ollama ps` tabular output into running model names.
39
- *
40
- * @param stdout - CLI stdout
41
- */
42
- export function parseOllamaPs(stdout: string): string[] {
43
- return parseOllamaList(stdout);
44
- }
45
-
46
- /**
47
- * Normalize installed tag for comparison (`qwen3.5:9b` vs `qwen3.5:9b-mlx`).
48
- *
49
- * @param id - Catalog or installed id
50
- * @param installed - Installed names
51
- */
52
- export function isInstalled(id: string, installed: readonly string[]): boolean {
53
- const base = id.split(":")[0] ?? id;
54
- return installed.some((name) => {
55
- if (name === id) return true;
56
- if (name.startsWith(`${id}-`)) return true;
57
- if (name.startsWith(`${base}:`) && id.startsWith(`${base}:`)) {
58
- // treat mlx / quant suffixes as matching the same family:tag when prefix matches
59
- const instTag = name.slice(base.length + 1);
60
- const wantTag = id.slice(base.length + 1);
61
- return (
62
- instTag === wantTag || instTag.startsWith(`${wantTag}-`) || wantTag.startsWith(instTag)
63
- );
64
- }
65
- return false;
66
- });
67
- }
68
-
69
- /**
70
- * Detect Ollama via CLI then HTTP fallback.
71
- *
72
- * @param options - Base URL / spawn / fetch seams
73
- */
74
- export async function detectOllama(
75
- options: {
76
- readonly baseUrl?: string;
77
- readonly fetch?: typeof globalThis.fetch;
78
- readonly run?: (cmd: readonly string[]) => Promise<{ code: number; stdout: string }>;
79
- } = {},
80
- ): Promise<OllamaDetectResult> {
81
- const baseUrl = normalizeOllamaBaseUrl(options.baseUrl ?? OLLAMA_DEFAULT_BASE_URL);
82
- const run =
83
- options.run ??
84
- (async (cmd) => {
85
- const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "pipe" });
86
- const stdout = await new Response(proc.stdout).text();
87
- const code = await proc.exited;
88
- return { code, stdout };
89
- });
90
-
91
- let installed: string[] = [];
92
- let running: string[] = [];
93
- let available = false;
94
-
95
- try {
96
- const list = await run(["ollama", "list"]);
97
- if (list.code === 0) {
98
- available = true;
99
- installed = parseOllamaList(list.stdout);
100
- }
101
- const ps = await run(["ollama", "ps"]);
102
- if (ps.code === 0) {
103
- running = parseOllamaPs(ps.stdout);
104
- }
105
- } catch {
106
- // CLI missing — try HTTP
107
- }
108
-
109
- if (!available) {
110
- const fetchFn = options.fetch ?? globalThis.fetch;
111
- try {
112
- const res = await fetchFn(`${baseUrl}/api/tags`, {
113
- method: "GET",
114
- signal: AbortSignal.timeout(2_000),
115
- });
116
- if (res.ok) {
117
- available = true;
118
- const json = (await res.json()) as { models?: readonly { name?: string }[] };
119
- installed = (json.models ?? [])
120
- .map((m) => m.name)
121
- .filter((n): n is string => typeof n === "string");
122
- }
123
- } catch {
124
- // unavailable
125
- }
126
- }
127
-
128
- const curatedInstalled = ALL_CURATED.filter((m) => isInstalled(m.id, installed));
129
- const curatedMissing = ALL_CURATED.filter((m) => !isInstalled(m.id, installed));
130
-
131
- return {
132
- available,
133
- baseUrl,
134
- installed,
135
- running,
136
- curatedInstalled,
137
- curatedMissing,
138
- };
139
- }
140
-
141
- /** Detected host hardware for the Ollama banner. */
142
- export type MachineInfo = {
143
- readonly osName: string;
144
- readonly cpuCount: number | null;
145
- readonly ramGb: number | null;
146
- };
147
-
148
- /**
149
- * Best-effort total system RAM in GB.
150
- */
151
- export function detectTotalRamGb(): number | null {
152
- try {
153
- if (process.platform === "darwin") {
154
- const proc = Bun.spawnSync(["sysctl", "-n", "hw.memsize"], { stdout: "pipe" });
155
- if (proc.exitCode === 0) {
156
- const bytes = Number(proc.stdout.toString().trim());
157
- if (Number.isFinite(bytes) && bytes > 0) return Math.round(bytes / 1024 ** 3);
158
- }
159
- }
160
- if (process.platform === "linux") {
161
- const proc = Bun.spawnSync(["awk", "/MemTotal/ {print $2}", "/proc/meminfo"], {
162
- stdout: "pipe",
163
- });
164
- if (proc.exitCode === 0) {
165
- const kb = Number(proc.stdout.toString().trim());
166
- if (Number.isFinite(kb) && kb > 0) return Math.round(kb / 1024 ** 2);
167
- }
168
- }
169
- } catch {
170
- // ignore
171
- }
172
- return null;
173
- }
174
-
175
- /**
176
- * Best-effort OS name · CPU count · RAM for the Ollama banner.
177
- */
178
- export function detectMachineInfo(): MachineInfo {
179
- const osName =
180
- process.platform === "darwin"
181
- ? "macOS"
182
- : process.platform === "linux"
183
- ? "Linux"
184
- : process.platform === "win32"
185
- ? "Windows"
186
- : process.platform;
187
-
188
- let cpuCount: number | null = null;
189
- try {
190
- if (process.platform === "darwin") {
191
- const proc = Bun.spawnSync(["sysctl", "-n", "hw.ncpu"], { stdout: "pipe" });
192
- if (proc.exitCode === 0) {
193
- const n = Number(proc.stdout.toString().trim());
194
- if (Number.isFinite(n) && n > 0) cpuCount = n;
195
- }
196
- } else if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator) {
197
- const n = Number(navigator.hardwareConcurrency);
198
- if (Number.isFinite(n) && n > 0) cpuCount = n;
199
- }
200
- } catch {
201
- // ignore
202
- }
203
- if (cpuCount === null) {
204
- try {
205
- const n = (process as { availableParallelism?: () => number }).availableParallelism?.();
206
- if (typeof n === "number" && Number.isFinite(n) && n > 0) cpuCount = n;
207
- } catch {
208
- // ignore
209
- }
210
- }
211
-
212
- return { osName, cpuCount, ramGb: detectTotalRamGb() };
213
- }
@@ -1,225 +0,0 @@
1
- /**
2
- * Smart Ollama recommendation — RAM tiers (no needs quiz).
3
- */
4
-
5
- import { describe, expect, test } from "bun:test";
6
- import {
7
- LLAMA_CPP_CHAT_MODELS,
8
- llamaCppModelsForTier,
9
- modelsForTier,
10
- recommendChatModel,
11
- recommendForTier,
12
- recommendLlamaCppForTier,
13
- } from "./catalog.ts";
14
- import {
15
- fittingChatModels,
16
- formatLlamaCppBanner,
17
- formatModelRow,
18
- formatModelTableHeader,
19
- formatOllamaBanner,
20
- isTightFit,
21
- modelFitsComfortably,
22
- modelFitsOnMachine,
23
- recommendChatForNeeds,
24
- suggestTierForRam,
25
- usableRamGb,
26
- } from "./recommend.ts";
27
-
28
- describe("modelFitsOnMachine", () => {
29
- test("catalog ramGb is machine tier, not download size", () => {
30
- expect(
31
- modelFitsOnMachine(
32
- {
33
- id: "x",
34
- label: "x",
35
- hint: "",
36
- role: "chat",
37
- ramGb: 16,
38
- tier: "balanced",
39
- modalities: ["text"],
40
- },
41
- 24,
42
- ),
43
- ).toBe(true);
44
- expect(
45
- modelFitsOnMachine(
46
- {
47
- id: "x",
48
- label: "x",
49
- hint: "",
50
- role: "chat",
51
- ramGb: 32,
52
- tier: "smart",
53
- modalities: ["text"],
54
- },
55
- 24,
56
- ),
57
- ).toBe(false);
58
- });
59
- });
60
-
61
- describe("tiers", () => {
62
- test("each tier exposes up to 10 models", () => {
63
- for (const tier of ["ultra-fast", "fast", "balanced", "smart"] as const) {
64
- expect(modelsForTier(tier).length).toBeGreaterThan(0);
65
- expect(modelsForTier(tier).length).toBeLessThanOrEqual(10);
66
- }
67
- });
68
-
69
- test("suggestTierForRam maps fit budget", () => {
70
- expect(suggestTierForRam(4)).toBe("ultra-fast");
71
- expect(suggestTierForRam(8)).toBe("fast");
72
- expect(suggestTierForRam(16)).toBe("balanced");
73
- expect(suggestTierForRam(32)).toBe("smart");
74
- });
75
-
76
- test("recommendForTier returns a model in that tier", () => {
77
- const pick = recommendForTier("fast", 16);
78
- expect(pick.tier).toBe("fast");
79
- });
80
- });
81
-
82
- describe("recommendChatForNeeds", () => {
83
- test("8GB → ultra-fast or fast entry", () => {
84
- const pick = recommendChatForNeeds(8);
85
- expect(pick.ramGb).toBeLessThanOrEqual(8);
86
- });
87
-
88
- test("16GB → never recommends 32GB-class", () => {
89
- const pick = recommendChatForNeeds(16);
90
- expect(pick.ramGb).toBeLessThanOrEqual(16);
91
- expect(pick.id).not.toBe("qwen3.5:27b");
92
- });
93
-
94
- test("24GB leaves headroom", () => {
95
- const pick = recommendChatForNeeds(24);
96
- expect(pick.ramGb).toBeLessThanOrEqual(usableRamGb(24));
97
- });
98
- });
99
-
100
- describe("comfortable vs tight", () => {
101
- test("16GB-class on 16GB machine is tight", () => {
102
- const qwen = fittingChatModels(16).find((m) => m.id === "qwen3.5:9b");
103
- expect(qwen).toBeDefined();
104
- expect(modelFitsOnMachine(qwen!, 16)).toBe(true);
105
- expect(modelFitsComfortably(qwen!, 16)).toBe(false);
106
- expect(isTightFit(qwen!, 16)).toBe(true);
107
- });
108
-
109
- test("16GB-class on 24GB machine is comfortable", () => {
110
- const qwen = fittingChatModels(24).find((m) => m.id === "qwen3.5:9b")!;
111
- expect(modelFitsComfortably(qwen, 24)).toBe(true);
112
- expect(isTightFit(qwen, 24)).toBe(false);
113
- });
114
- });
115
-
116
- describe("recommendChatModel", () => {
117
- test("respects tiers with headroom preference", () => {
118
- expect(recommendChatModel(8).ramGb).toBeLessThanOrEqual(8);
119
- expect(recommendChatModel(16).ramGb).toBeLessThanOrEqual(16);
120
- expect(recommendChatModel(24).ramGb).toBeLessThanOrEqual(16);
121
- });
122
- });
123
-
124
- describe("formatOllamaBanner", () => {
125
- test("shows OS · CPU · RAM · Fit RAM · detected — no comfortable list", () => {
126
- const text = formatOllamaBanner({ osName: "macOS", cpuCount: 10, ramGb: 24 }, ["gemma4:e4b"]);
127
- expect(text).toContain("OS macOS · CPU 10 · RAM ~24GB RAM");
128
- expect(text).toContain("Fit RAM ~20GB RAM");
129
- expect(text).toContain("Detected local models");
130
- expect(text).toContain("gemma4:e4b");
131
- expect(text).not.toContain("Fits comfortably");
132
- expect(text).toContain("Tiers · Ultra Fast");
133
- });
134
- });
135
-
136
- describe("llama.cpp catalog", () => {
137
- test("each tier has ≥10 curated Docker Hub ai/ models (UI shows up to 20)", () => {
138
- for (const tier of ["ultra-fast", "fast", "balanced", "smart"] as const) {
139
- const all = LLAMA_CPP_CHAT_MODELS.filter((m) => m.tier === tier);
140
- expect(all.length).toBeGreaterThanOrEqual(10);
141
- const list = llamaCppModelsForTier(tier);
142
- expect(list.length).toBeGreaterThanOrEqual(10);
143
- expect(list.length).toBeLessThanOrEqual(20);
144
- expect(list.length).toBe(Math.min(all.length, 20));
145
- expect(list.every((m) => !m.id.startsWith("ai/"))).toBe(true);
146
- for (let i = 1; i < list.length; i++) {
147
- expect(list[i]!.ramGb).toBeGreaterThanOrEqual(list[i - 1]!.ramGb);
148
- }
149
- }
150
- });
151
-
152
- test("recommendLlamaCppForTier returns a model in that tier", () => {
153
- expect(recommendLlamaCppForTier("ultra-fast", 8).id).toBe("granite3.3:2b");
154
- expect(recommendLlamaCppForTier("fast", 16).tier).toBe("fast");
155
- });
156
-
157
- test("visible list surfaces Gemma 4 and Qwen", () => {
158
- const ids = (tier: "fast" | "balanced" | "smart") =>
159
- llamaCppModelsForTier(tier).map((m) => m.id);
160
- expect(ids("fast").some((id) => id.startsWith("gemma4:"))).toBe(true);
161
- expect(ids("fast").some((id) => id.startsWith("qwen"))).toBe(true);
162
- expect(ids("balanced")).toContain("gemma4:e4b");
163
- expect(ids("balanced").some((id) => id.startsWith("qwen3"))).toBe(true);
164
- expect(ids("smart")).toContain("gemma4:31b");
165
- expect(ids("smart").some((id) => id.startsWith("qwen"))).toBe(true);
166
- });
167
-
168
- test("formatLlamaCppBanner shows Docker Hub ai/ source — not Ollama detect", () => {
169
- const text = formatLlamaCppBanner({ osName: "macOS", cpuCount: 10, ramGb: 24 });
170
- expect(text).toContain("OS macOS · CPU 10 · RAM ~24GB RAM");
171
- expect(text).toContain("Fit RAM ~20GB RAM");
172
- expect(text).toContain("Docker Hub ai/");
173
- expect(text).not.toContain("Detected local models");
174
- expect(text).toContain("Tiers · Ultra Fast");
175
- });
176
- });
177
-
178
- describe("formatModelRow", () => {
179
- test("aligned Model | RAM | Caps columns", () => {
180
- const row = formatModelRow({
181
- id: "x",
182
- label: "Gemma 4 4B",
183
- hint: "",
184
- role: "chat",
185
- ramGb: 8,
186
- tier: "fast",
187
- modalities: ["text", "code"],
188
- });
189
- const header = formatModelTableHeader();
190
- expect(header.startsWith("Model")).toBe(true);
191
- expect(header).toContain("RAM");
192
- expect(header).toContain("Caps");
193
- // Caps column starts at the same offset as the header.
194
- expect(row.indexOf("text")).toBe(header.indexOf("Caps"));
195
- expect(row).toContain("Gemma 4 4B");
196
- expect(row).toContain("≈8GB");
197
- expect(row).toContain("text · code");
198
- expect(row).not.toContain(" · ≈");
199
- });
200
-
201
- test("Arabic and CJK labels keep Caps aligned", () => {
202
- const header = formatModelTableHeader();
203
- const capsWidth = Bun.stringWidth(header.slice(0, header.indexOf("Caps")));
204
- const ar = formatModelRow({
205
- id: "ar",
206
- label: "مرحبا بالعالم",
207
- hint: "",
208
- role: "chat",
209
- ramGb: 8,
210
- tier: "fast",
211
- modalities: ["text"],
212
- });
213
- const cjk = formatModelRow({
214
- id: "cjk",
215
- label: "你好世界模型",
216
- hint: "",
217
- role: "chat",
218
- ramGb: 8,
219
- tier: "fast",
220
- modalities: ["text"],
221
- });
222
- expect(Bun.stringWidth(ar.slice(0, ar.indexOf("text")))).toBe(capsWidth);
223
- expect(Bun.stringWidth(cjk.slice(0, cjk.indexOf("text")))).toBe(capsWidth);
224
- });
225
- });
@@ -1,225 +0,0 @@
1
- /**
2
- * Machine fit helpers for local Ollama model picks.
3
- *
4
- * Catalog `ramGb` is the **recommended machine tier** for that model (not the
5
- * download size). Prefer picks that leave ~4GB for OS / IDE / browser.
6
- */
7
-
8
- import {
9
- CHAT_MODELS,
10
- MODEL_TIERS,
11
- type CatalogModel,
12
- type ModelTier,
13
- recommendForTier,
14
- } from "./catalog.ts";
15
-
16
- /** Reserved for OS + IDE + browser while the model runs. */
17
- export const OS_HEADROOM_GB = 4;
18
-
19
- /**
20
- * RAM left after reserving OS/IDE headroom.
21
- *
22
- * @param totalRamGb - Detected machine RAM
23
- */
24
- export function usableRamGb(totalRamGb: number): number {
25
- return Math.max(0, totalRamGb - OS_HEADROOM_GB);
26
- }
27
-
28
- /**
29
- * Whether the machine meets the model's recommended tier (`model.ramGb`).
30
- *
31
- * @param model - Catalog entry
32
- * @param totalRamGb - Machine RAM (null → only the catalog default recommended)
33
- */
34
- export function modelFitsOnMachine(model: CatalogModel, totalRamGb: number | null): boolean {
35
- if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
36
- return Boolean(model.recommended);
37
- }
38
- return model.ramGb <= totalRamGb;
39
- }
40
-
41
- /**
42
- * Whether the model fits with comfortable headroom for OS/IDE.
43
- *
44
- * @param model - Catalog entry
45
- * @param totalRamGb - Machine RAM
46
- */
47
- export function modelFitsComfortably(model: CatalogModel, totalRamGb: number | null): boolean {
48
- if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
49
- return Boolean(model.recommended);
50
- }
51
- const smallest = [...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb)[0]!;
52
- if (model.id === smallest.id) return model.ramGb <= totalRamGb;
53
- return model.ramGb + OS_HEADROOM_GB <= totalRamGb;
54
- }
55
-
56
- /**
57
- * True when the model meets the tier but leaves little room for OS/IDE.
58
- *
59
- * @param model - Catalog entry
60
- * @param totalRamGb - Machine RAM
61
- */
62
- export function isTightFit(model: CatalogModel, totalRamGb: number | null): boolean {
63
- if (totalRamGb === null || !Number.isFinite(totalRamGb)) return false;
64
- return modelFitsOnMachine(model, totalRamGb) && !modelFitsComfortably(model, totalRamGb);
65
- }
66
-
67
- /**
68
- * Chat models that meet the machine tier, smallest → largest.
69
- *
70
- * @param totalRamGb - Machine RAM
71
- */
72
- export function fittingChatModels(totalRamGb: number | null): readonly CatalogModel[] {
73
- const fits = CHAT_MODELS.filter((m) => modelFitsOnMachine(m, totalRamGb));
74
- if (fits.length > 0) return [...fits].sort((a, b) => a.ramGb - b.ramGb);
75
- return [[...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb)[0]!];
76
- }
77
-
78
- /**
79
- * Prefer comfortable fits; fall back to any tier-fit if needed.
80
- *
81
- * @param totalRamGb - Machine RAM
82
- */
83
- export function comfortableChatModels(totalRamGb: number | null): readonly CatalogModel[] {
84
- const comfortable = CHAT_MODELS.filter((m) => modelFitsComfortably(m, totalRamGb));
85
- if (comfortable.length > 0) {
86
- return [...comfortable].sort((a, b) => a.ramGb - b.ramGb);
87
- }
88
- return fittingChatModels(totalRamGb);
89
- }
90
-
91
- /**
92
- * Suggest a tier for the machine's fit RAM budget.
93
- *
94
- * @param totalRamGb - Machine RAM
95
- */
96
- export function suggestTierForRam(totalRamGb: number | null): ModelTier {
97
- if (totalRamGb === null || !Number.isFinite(totalRamGb)) return "fast";
98
- const budget = usableRamGb(totalRamGb);
99
- if (budget >= 24) return "smart";
100
- if (budget >= 8) return "balanced";
101
- if (budget >= 4) return "fast";
102
- return "ultra-fast";
103
- }
104
-
105
- /**
106
- * Recommend a chat model for host RAM (balanced default — no quiz).
107
- *
108
- * @param totalRamGb - Machine RAM
109
- */
110
- export function recommendChatForNeeds(totalRamGb: number | null): CatalogModel {
111
- return recommendForTier(suggestTierForRam(totalRamGb), totalRamGb);
112
- }
113
-
114
- /**
115
- * Shared hardware lines for local AI banners.
116
- *
117
- * @param machine - Detected hardware
118
- */
119
- function formatMachineBannerLines(machine: {
120
- readonly osName: string;
121
- readonly cpuCount: number | null;
122
- readonly ramGb: number | null;
123
- }): readonly string[] {
124
- const ram =
125
- machine.ramGb !== null && Number.isFinite(machine.ramGb)
126
- ? `~${machine.ramGb}GB RAM`
127
- : "RAM unknown";
128
- const cpu =
129
- machine.cpuCount !== null && Number.isFinite(machine.cpuCount) ? String(machine.cpuCount) : "?";
130
- const fit =
131
- machine.ramGb !== null && Number.isFinite(machine.ramGb)
132
- ? `~${usableRamGb(machine.ramGb)}GB RAM`
133
- : "unknown";
134
- const sep = " · ";
135
- return [[`OS ${machine.osName}`, `CPU ${cpu}`, `RAM ${ram}`].join(sep), `Fit RAM ${fit}`];
136
- }
137
-
138
- /**
139
- * Ollama banner lines — OS · CPU · RAM · fit · detected · tiers.
140
- *
141
- * @param machine - Detected hardware
142
- * @param detectedIds - Installed model ids
143
- */
144
- export function formatOllamaBanner(
145
- machine: {
146
- readonly osName: string;
147
- readonly cpuCount: number | null;
148
- readonly ramGb: number | null;
149
- },
150
- detectedIds: readonly string[],
151
- ): string {
152
- const sep = " · ";
153
- const lines = [
154
- ...formatMachineBannerLines(machine),
155
- "",
156
- "Detected local models",
157
- ...(detectedIds.length > 0 ? detectedIds.map((id) => id) : ["(none detected)"]),
158
- "",
159
- `Tiers${sep}${MODEL_TIERS.map((t) => t.label).join(sep)}`,
160
- ];
161
- return lines.join("\n");
162
- }
163
-
164
- /**
165
- * llama.cpp banner — OS · CPU · RAM · fit · Docker Hub `ai/` source · tiers.
166
- *
167
- * @param machine - Detected hardware
168
- */
169
- export function formatLlamaCppBanner(machine: {
170
- readonly osName: string;
171
- readonly cpuCount: number | null;
172
- readonly ramGb: number | null;
173
- }): string {
174
- const sep = " · ";
175
- const lines = [
176
- ...formatMachineBannerLines(machine),
177
- "",
178
- "Model source",
179
- "Docker Hub ai/ (curated) — pulled on first container start",
180
- "",
181
- `Tiers${sep}${MODEL_TIERS.map((t) => t.label).join(sep)}`,
182
- ];
183
- return lines.join("\n");
184
- }
185
-
186
- /** Model name column width (monospace CLI table). */
187
- const MODEL_COL_NAME = 26;
188
- /** RAM column width, right-aligned (`≈32GB`). */
189
- const MODEL_COL_RAM = 7;
190
-
191
- /**
192
- * Clip or pad a cell for aligned CLI columns.
193
- *
194
- * @param value - Cell text
195
- * @param width - Fixed width
196
- */
197
- function clipPad(value: string, width: number): string {
198
- const bunAnsi = Bun as typeof Bun & {
199
- stringWidth(text: string): number;
200
- sliceAnsi(text: string, start: number, end: number, omission?: string): string;
201
- };
202
- const display = bunAnsi.stringWidth(value);
203
- if (display === width) return value;
204
- if (display < width) return value + " ".repeat(width - display);
205
- return bunAnsi.sliceAnsi(value, 0, width, "…");
206
- }
207
-
208
- /**
209
- * Column header for the model pick list (aligns with {@link formatModelRow}).
210
- */
211
- export function formatModelTableHeader(): string {
212
- return `${clipPad("Model", MODEL_COL_NAME)} ${"RAM".padStart(MODEL_COL_RAM)} Caps`;
213
- }
214
-
215
- /**
216
- * One model row as aligned columns: Model | RAM | Caps.
217
- *
218
- * @param model - Catalog entry
219
- */
220
- export function formatModelRow(model: CatalogModel): string {
221
- const name = clipPad(model.label, MODEL_COL_NAME);
222
- const ram = `≈${model.ramGb}GB`.padStart(MODEL_COL_RAM);
223
- const caps = model.modalities.join(" · ");
224
- return `${name} ${ram} ${caps}`;
225
- }