okengine 0.10.2 → 0.11.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 (343) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/ai/mcp.mdx +0 -1
  3. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  4. package/site/content/docs/deployment/docker.mdx +43 -36
  5. package/site/content/docs/deployment/index.mdx +9 -9
  6. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  7. package/site/content/docs/elements/ai.mdx +7 -13
  8. package/site/content/docs/elements/channel.mdx +9 -13
  9. package/site/content/docs/elements/clock.mdx +8 -14
  10. package/site/content/docs/elements/flow.mdx +46 -54
  11. package/site/content/docs/elements/gate.mdx +6 -11
  12. package/site/content/docs/elements/signal.mdx +2 -8
  13. package/site/content/docs/elements/store.mdx +79 -93
  14. package/site/content/docs/elements/vault.mdx +89 -76
  15. package/site/content/docs/get-started/basic-usage.mdx +8 -14
  16. package/site/content/docs/get-started/installation.mdx +36 -36
  17. package/site/content/docs/get-started/introduction.mdx +11 -0
  18. package/site/content/docs/get-started/why.mdx +3 -4
  19. package/site/content/docs/index.mdx +0 -1
  20. package/site/content/docs/meta.json +0 -1
  21. package/site/content/docs/plugins/anonymous.mdx +1 -1
  22. package/site/content/docs/plugins/magic-link.mdx +11 -11
  23. package/site/content/docs/plugins/otp.mdx +2 -2
  24. package/site/content/docs/plugins/passkey.mdx +1 -1
  25. package/site/content/docs/plugins/two-factor.mdx +1 -1
  26. package/site/content/docs/plugins/username.mdx +1 -1
  27. package/site/content/docs/recipes/caddy.mdx +1 -1
  28. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  29. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  30. package/site/content/docs/recipes/index.mdx +1 -2
  31. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  32. package/site/content/docs/recipes/mailpit.mdx +11 -11
  33. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  34. package/site/content/docs/recipes/meta.json +0 -1
  35. package/site/content/docs/recipes/ollama.mdx +1 -1
  36. package/site/content/docs/recipes/pgdog.mdx +12 -9
  37. package/site/content/docs/recipes/postgres.mdx +3 -3
  38. package/site/content/docs/recipes/redis.mdx +2 -2
  39. package/site/content/docs/recipes/rustfs.mdx +10 -7
  40. package/site/content/docs/recipes/sglang.mdx +1 -1
  41. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  42. package/site/content/docs/recipes/timescale.mdx +2 -2
  43. package/site/content/docs/recipes/valkey.mdx +1 -1
  44. package/site/content/docs/recipes/vllm.mdx +1 -1
  45. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  46. package/site/content/docs/reference/cli.md +61 -38
  47. package/site/content/docs/reference/configuration.mdx +145 -70
  48. package/site/content/docs/reference/environment-variables.mdx +36 -28
  49. package/site/content/docs/reference/errors.mdx +28 -29
  50. package/site/content/docs/reference/fx.mdx +14 -7
  51. package/site/content/docs/reference/i18n.mdx +17 -6
  52. package/site/content/docs/reference/index.mdx +5 -0
  53. package/site/content/docs/reference/meta.json +1 -0
  54. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  55. package/site/content/docs/reference/plugins.mdx +2 -2
  56. package/src/auth/gate-auth.test.ts +12 -10
  57. package/src/cli/ai-setup/ai-setup.test.ts +30 -16
  58. package/src/cli/ai-setup/apply.ts +111 -15
  59. package/src/cli/ai-setup/catalog.ts +3 -3
  60. package/src/cli/ai-setup/index.ts +1 -1
  61. package/src/cli/ai-setup/prompts.ts +1 -1
  62. package/src/cli/ai-setup/recommend.test.ts +1 -1
  63. package/src/cli/ask-seed.test.ts +3 -3
  64. package/src/cli/build.ts +8 -9
  65. package/src/cli/db-auto-push.test.ts +6 -3
  66. package/src/cli/db-auto-push.ts +7 -5
  67. package/src/cli/db-seed.ts +7 -8
  68. package/src/cli/db.test.ts +70 -71
  69. package/src/cli/db.ts +86 -12
  70. package/src/cli/dev-controller.test.ts +35 -0
  71. package/src/cli/dev-controller.ts +298 -0
  72. package/src/cli/dev-controls.ts +11 -65
  73. package/src/cli/dev-db-push.test.ts +47 -15
  74. package/src/cli/dev-schema-sync.test.ts +11 -11
  75. package/src/cli/dev-schema-sync.ts +10 -10
  76. package/src/cli/dev-session-lock.test.ts +106 -0
  77. package/src/cli/dev-session-lock.ts +209 -0
  78. package/src/cli/dev.test.ts +246 -114
  79. package/src/cli/dev.ts +153 -205
  80. package/src/cli/doc-staleness.test.ts +3 -6
  81. package/src/cli/docker-clean.test.ts +33 -1
  82. package/src/cli/docker-clean.ts +28 -33
  83. package/src/cli/docker-cli.test.ts +12 -11
  84. package/src/cli/docker.ts +95 -20
  85. package/src/cli/doctor.test.ts +40 -0
  86. package/src/cli/doctor.ts +142 -9
  87. package/src/cli/drizzle-env.test.ts +11 -13
  88. package/src/cli/drizzle-env.ts +18 -11
  89. package/src/cli/ensure-drizzle-config.ts +11 -14
  90. package/src/cli/hero-meta.test.ts +15 -15
  91. package/src/cli/hero-meta.ts +10 -9
  92. package/src/cli/index.ts +36 -6
  93. package/src/cli/load-config.images.test.ts +44 -10
  94. package/src/cli/load-config.source.test.ts +50 -0
  95. package/src/cli/load-config.ts +35 -29
  96. package/src/cli/meilisearch-local.ts +1 -2
  97. package/src/cli/mode.ts +7 -58
  98. package/src/cli/registry.ts +175 -19
  99. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  100. package/src/cli/resolve-dev-sql-env.ts +13 -15
  101. package/src/cli/safe-defaults.test.ts +29 -11
  102. package/src/cli/schema.ts +65 -16
  103. package/src/cli/test.ts +58 -0
  104. package/src/cli/tui/App.tsx +252 -0
  105. package/src/cli/tui/DevLive.tsx +116 -0
  106. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  107. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  108. package/src/cli/tui/components/LogStream.tsx +47 -0
  109. package/src/cli/tui/components/PanelCard.tsx +39 -0
  110. package/src/cli/tui/components/SelectRow.tsx +37 -0
  111. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  112. package/src/cli/tui/components/Spinner.tsx +32 -0
  113. package/src/cli/tui/components/StatusBar.tsx +51 -0
  114. package/src/cli/tui/components/StatusDot.tsx +37 -0
  115. package/src/cli/tui/components/TabBar.tsx +49 -0
  116. package/src/cli/tui/components/Wordmark.tsx +43 -0
  117. package/src/cli/tui/format.ts +38 -0
  118. package/src/cli/tui/keys.test.ts +18 -0
  119. package/src/cli/tui/keys.ts +26 -0
  120. package/src/cli/tui/launch.test.ts +18 -0
  121. package/src/cli/tui/launch.ts +57 -0
  122. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  123. package/src/cli/tui/panels/Database.tsx +198 -0
  124. package/src/cli/tui/panels/Dev.tsx +257 -0
  125. package/src/cli/tui/panels/Docker.tsx +357 -0
  126. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  127. package/src/cli/tui/panels/Navigator.tsx +213 -0
  128. package/src/cli/tui/slash-catalog.test.ts +31 -0
  129. package/src/cli/tui/slash-catalog.ts +184 -0
  130. package/src/cli/tui/slash-run.ts +54 -0
  131. package/src/cli/tui/state/ports.ts +95 -0
  132. package/src/cli/tui/theme.ts +24 -0
  133. package/src/cli/vault-cmd.test.ts +402 -0
  134. package/src/cli/vault-cmd.ts +727 -69
  135. package/src/cli/vault-secure-input.test.ts +56 -0
  136. package/src/cli/vault-secure-input.ts +124 -0
  137. package/src/compiler/effects-infer.ts +7 -0
  138. package/src/compiler/extract.ts +12 -0
  139. package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
  140. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  141. package/src/compiler/fixtures/skyport.expected.json +1 -1
  142. package/src/compiler/generate-adopt.ts +27 -1
  143. package/src/compiler-entry.ts +7 -0
  144. package/src/config/define-config.test.ts +111 -47
  145. package/src/config/driver-defaults.test.ts +115 -0
  146. package/src/config/driver-defaults.ts +156 -0
  147. package/src/config/index.ts +246 -131
  148. package/src/config/resolve-driver.test.ts +14 -19
  149. package/src/console/server/app.ts +4 -2
  150. package/src/console/server/console.test.ts +58 -1
  151. package/src/console/server/flows.ts +22 -2
  152. package/src/console/server/index.ts +3 -0
  153. package/src/console/server/operator-db.test.ts +55 -13
  154. package/src/console/server/operator-db.ts +305 -263
  155. package/src/console/server/serve.ts +16 -7
  156. package/src/console/server/state.ts +31 -9
  157. package/src/console/server/vault.test.ts +120 -5
  158. package/src/console/server/vault.ts +208 -12
  159. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  160. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  161. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  162. package/src/console/ui/dist/index.html +2 -2
  163. package/src/console/ui/shell/client.ts +16 -0
  164. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  165. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  166. package/src/console/ui/vault/backend.test.ts +75 -0
  167. package/src/console/ui/vault/backend.ts +117 -0
  168. package/src/console/ui/vault/fixture.ts +21 -2
  169. package/src/console/ui/vault/index.ts +11 -1
  170. package/src/console/ui/vault/types.ts +26 -0
  171. package/src/docker/compose.ts +164 -61
  172. package/src/docker/derive.ts +106 -12
  173. package/src/docker/docker.test.ts +156 -110
  174. package/src/docker/helpers.ts +98 -0
  175. package/src/docker/images-config.test.ts +163 -0
  176. package/src/docker/index.ts +30 -5
  177. package/src/docker/recipes/index.ts +0 -3
  178. package/src/docker/recipes/llama-cpp.ts +4 -4
  179. package/src/docker/recipes/pgdog.ts +9 -2
  180. package/src/docker/resources.test.ts +71 -0
  181. package/src/docker/resources.ts +176 -0
  182. package/src/docker/stack-id.test.ts +11 -4
  183. package/src/docker/stack-id.ts +42 -32
  184. package/src/docker/stack.integration.test.ts +4 -15
  185. package/src/docker/types.ts +35 -3
  186. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  187. package/src/drivers/conformance.test.ts +11 -12
  188. package/src/drivers/drizzle-dialect.test.ts +0 -4
  189. package/src/drivers/drizzle-dialect.ts +7 -13
  190. package/src/drivers/index.ts +24 -7
  191. package/src/drivers/journal-postgres.test.ts +3 -1
  192. package/src/drivers/journal-postgres.ts +4 -2
  193. package/src/drivers/meilisearch.ts +1 -1
  194. package/src/drivers/pglite.ts +28 -5
  195. package/src/drivers/types.ts +9 -11
  196. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  197. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  198. package/src/drivers/vault-builtin.test.ts +110 -0
  199. package/src/drivers/vault-builtin.ts +184 -0
  200. package/src/drivers/vault-driver-removal.test.ts +13 -0
  201. package/src/drivers/vault-managed.test.ts +95 -0
  202. package/src/drivers/vault-managed.ts +101 -25
  203. package/src/drivers/vault-types.ts +25 -5
  204. package/src/elements/ai/runtime.ts +1 -1
  205. package/src/elements/clock/chaos.test.ts +4 -1
  206. package/src/elements/clock/durable.ts +57 -4
  207. package/src/elements/clock.test.ts +13 -14
  208. package/src/elements/gate/constants.ts +18 -0
  209. package/src/elements/gate.test.ts +1 -1
  210. package/src/elements/gate.ts +2 -2
  211. package/src/elements/store/domain-ddl.test.ts +5 -12
  212. package/src/elements/store/emit-drizzle.ts +44 -19
  213. package/src/elements/store/index-boot.test.ts +15 -116
  214. package/src/elements/store/load-plugin-tables.ts +1 -1
  215. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  216. package/src/elements/store/resource-list-docs.test.ts +64 -44
  217. package/src/elements/store/resource.test.ts +17 -10
  218. package/src/elements/store/runtime.ts +9 -12
  219. package/src/elements/store/schema-decl.test.ts +16 -32
  220. package/src/elements/store/seed.test.ts +1 -2
  221. package/src/elements/store/seed.ts +7 -8
  222. package/src/elements/store/sql-condition.test.ts +2 -2
  223. package/src/elements/store/sql-session.test.ts +8 -10
  224. package/src/elements/store/sql-session.ts +10 -2
  225. package/src/elements/store.test.ts +13 -13
  226. package/src/elements/vault/audit.ts +233 -0
  227. package/src/elements/vault/boot-chain.test.ts +78 -0
  228. package/src/elements/vault/boot-chain.ts +43 -39
  229. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  230. package/src/elements/vault/builtin-adapter.ts +1184 -0
  231. package/src/elements/vault/crypto.test.ts +237 -0
  232. package/src/elements/vault/crypto.ts +431 -0
  233. package/src/elements/vault/declare.ts +164 -3
  234. package/src/elements/vault/errors.ts +56 -0
  235. package/src/elements/vault/kms-unseal.test.ts +76 -0
  236. package/src/elements/vault/kms-unseal.ts +181 -0
  237. package/src/elements/vault/path.ts +90 -0
  238. package/src/elements/vault/resilience.test.ts +102 -0
  239. package/src/elements/vault/resilience.ts +107 -0
  240. package/src/elements/vault/runtime.ts +36 -4
  241. package/src/elements/vault/security-checklist.test.ts +130 -0
  242. package/src/elements/vault/storage.ts +448 -0
  243. package/src/elements/vault/types.ts +288 -0
  244. package/src/elements/vault/unseal.ts +150 -0
  245. package/src/elements/vault.test.ts +72 -4
  246. package/src/elements/vault.ts +91 -2
  247. package/src/full.ts +248 -0
  248. package/src/http.ts +41 -0
  249. package/src/i18n/interpolate-simple.ts +37 -0
  250. package/src/i18n/messages.ts +51 -4
  251. package/src/i18n-entry.ts +7 -0
  252. package/src/index.ts +35 -145
  253. package/src/journal-entry.ts +20 -0
  254. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  255. package/src/kernel/app.ts +40 -42
  256. package/src/kernel/auto-registry.test.ts +2 -2
  257. package/src/kernel/boot-bind/ai.test.ts +5 -5
  258. package/src/kernel/boot-bind/ai.ts +2 -1
  259. package/src/kernel/boot-bind/channel.test.ts +6 -6
  260. package/src/kernel/boot-bind/channel.ts +5 -4
  261. package/src/kernel/boot-bind/clock.ts +5 -5
  262. package/src/kernel/boot-bind/gate.ts +6 -7
  263. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  264. package/src/kernel/boot-bind/journal.ts +5 -5
  265. package/src/kernel/boot-bind/signal.ts +3 -1
  266. package/src/kernel/boot-bind/store.test.ts +55 -37
  267. package/src/kernel/boot-bind/store.ts +24 -34
  268. package/src/kernel/boot-bind/vault.ts +19 -4
  269. package/src/kernel/boot.test.ts +12 -4
  270. package/src/kernel/boot.ts +31 -19
  271. package/src/kernel/budget-entry.ts +1 -1
  272. package/src/kernel/compensate.test.ts +288 -1
  273. package/src/kernel/compensate.ts +126 -0
  274. package/src/kernel/concurrency.test.ts +10 -9
  275. package/src/kernel/effects-stamping.test.ts +27 -2
  276. package/src/kernel/element-registries.ts +5 -3
  277. package/src/kernel/flow.ts +4 -2
  278. package/src/kernel/fx.test.ts +119 -3
  279. package/src/kernel/fx.ts +215 -40
  280. package/src/kernel/index.ts +14 -0
  281. package/src/kernel/journal.ts +130 -7
  282. package/src/kernel/pipeline.test.ts +20 -6
  283. package/src/kernel/redacted.test.ts +35 -0
  284. package/src/kernel/redacted.ts +24 -2
  285. package/src/kernel/router/create-default.ts +16 -0
  286. package/src/kernel/router/create-edge.ts +18 -0
  287. package/src/kernel/router/create.ts +23 -0
  288. package/src/kernel/router/index.ts +23 -0
  289. package/src/kernel/router/linear.ts +88 -0
  290. package/src/kernel/router/regexp.ts +188 -0
  291. package/src/kernel/router/smart.ts +120 -0
  292. package/src/kernel/router/trie.ts +108 -0
  293. package/src/kernel/router/types.ts +56 -0
  294. package/src/kernel/router.ts +18 -532
  295. package/src/kernel-entry.ts +43 -0
  296. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  297. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  298. package/src/plugins/config-source.test.ts +7 -7
  299. package/src/release/build-lib.ts +58 -0
  300. package/src/release/exports.test.ts +4 -2
  301. package/src/release/exports.ts +30 -4
  302. package/src/release/measure.ts +101 -22
  303. package/src/runs/duckdb.ts +7 -2
  304. package/src/shared/optional-peer.ts +41 -0
  305. package/src/term.test.ts +3 -44
  306. package/src/test/create-test-app.ts +12 -0
  307. package/src/test/provisions.integration.test.ts +1 -1
  308. package/src/test/reset-element-registries.ts +2 -0
  309. package/src/upgrade/codemods.test.ts +7 -20
  310. package/src/upgrade/codemods.ts +93 -60
  311. package/site/content/docs/console/access.mdx +0 -29
  312. package/site/content/docs/console/ai.mdx +0 -35
  313. package/site/content/docs/console/architecture.mdx +0 -35
  314. package/site/content/docs/console/channels.mdx +0 -37
  315. package/site/content/docs/console/clock.mdx +0 -51
  316. package/site/content/docs/console/flows.mdx +0 -31
  317. package/site/content/docs/console/gates.mdx +0 -73
  318. package/site/content/docs/console/index.mdx +0 -54
  319. package/site/content/docs/console/manifest-diff.mdx +0 -34
  320. package/site/content/docs/console/meta.json +0 -24
  321. package/site/content/docs/console/overview.mdx +0 -40
  322. package/site/content/docs/console/plugins.mdx +0 -41
  323. package/site/content/docs/console/privacy.mdx +0 -32
  324. package/site/content/docs/console/runs.mdx +0 -50
  325. package/site/content/docs/console/signals.mdx +0 -40
  326. package/site/content/docs/console/store.mdx +0 -32
  327. package/site/content/docs/console/tenancy.mdx +0 -32
  328. package/site/content/docs/console/traces.mdx +0 -38
  329. package/site/content/docs/console/vault.mdx +0 -41
  330. package/site/content/docs/recipes/openbao.mdx +0 -141
  331. package/src/cli/ask-dev-mode.ts +0 -40
  332. package/src/cli/dev-mode.test.ts +0 -45
  333. package/src/cli/dev-mode.ts +0 -78
  334. package/src/cli/openbao-bootstrap.test.ts +0 -150
  335. package/src/cli/openbao-bootstrap.ts +0 -321
  336. package/src/cli/openbao-restart.integration.test.ts +0 -145
  337. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  338. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  339. package/src/docker/recipes/openbao.ts +0 -47
  340. package/src/drivers/libsql.ts +0 -179
  341. package/src/drivers/sqlite.ts +0 -44
  342. package/src/drivers/vault-openbao.test.ts +0 -97
  343. package/src/drivers/vault-openbao.ts +0 -143
@@ -3,12 +3,46 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
+ import { flattenImagesConfig } from "../config/index.ts";
6
7
  import {
7
8
  defaultImagesFromConfig,
8
9
  resolveImages,
9
10
  dockerDevDriverMismatches,
10
11
  } from "./load-config.ts";
11
12
 
13
+ describe("flattenImagesConfig", () => {
14
+ test("flattens store/channel nesting to dotted keys; flat roles pass through", () => {
15
+ expect(
16
+ flattenImagesConfig({
17
+ store: {
18
+ sql: "postgres:18-alpine",
19
+ kv: "redis:8-alpine",
20
+ files: "rustfs/rustfs:1.0.0",
21
+ index: "getmeili/meilisearch:v1.37",
22
+ },
23
+ channel: { email: "axllent/mailpit:v1.22.3" },
24
+ ai: "ollama/ollama:0.32.6",
25
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.51",
26
+ proxy: "caddy:2-alpine",
27
+ }),
28
+ ).toEqual({
29
+ "store.sql": "postgres:18-alpine",
30
+ "store.kv": "redis:8-alpine",
31
+ "store.files": "rustfs/rustfs:1.0.0",
32
+ "store.index": "getmeili/meilisearch:v1.37",
33
+ "channel.email": "axllent/mailpit:v1.22.3",
34
+ ai: "ollama/ollama:0.32.6",
35
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.51",
36
+ proxy: "caddy:2-alpine",
37
+ });
38
+ });
39
+
40
+ test("undefined / empty input yields empty map", () => {
41
+ expect(flattenImagesConfig(undefined)).toEqual({});
42
+ expect(flattenImagesConfig({})).toEqual({});
43
+ });
44
+ });
45
+
12
46
  describe("defaultImagesFromConfig", () => {
13
47
  test("maps postgres + redis prod drivers", () => {
14
48
  const images = defaultImagesFromConfig({
@@ -67,7 +101,7 @@ describe("defaultImagesFromConfig", () => {
67
101
  expect(defaultImagesFromConfig({})).toEqual({});
68
102
  expect(
69
103
  defaultImagesFromConfig({
70
- drivers: { store: { sql: { prod: "sqlite" } } },
104
+ drivers: { store: { sql: { prod: "memory" } } },
71
105
  }),
72
106
  ).toEqual({});
73
107
  });
@@ -82,7 +116,7 @@ describe("resolveImages", () => {
82
116
  kv: { prod: "redis" },
83
117
  },
84
118
  },
85
- images: { "store.sql": "pgvector/pgvector:pg17" },
119
+ images: { store: { sql: "pgvector/pgvector:pg17" } },
86
120
  });
87
121
  expect(images).toEqual({ "store.sql": "pgvector/pgvector:pg17" });
88
122
  });
@@ -102,20 +136,20 @@ describe("resolveImages", () => {
102
136
  });
103
137
 
104
138
  describe("dockerDevDriverMismatches", () => {
105
- test("flags docker/prod sqlite/memory when containers are up", () => {
139
+ test("flags dev pglite/memory when containers are up", () => {
106
140
  const mismatches = dockerDevDriverMismatches(
107
141
  {
108
142
  drivers: {
109
143
  store: {
110
- sql: { local: "sqlite", docker: "sqlite", prod: "sqlite" },
111
- kv: { local: "memory", docker: "memory", prod: "memory" },
144
+ sql: { dev: "pglite", test: "pglite", prod: "postgres" },
145
+ kv: { test: "memory", dev: "memory", prod: "memory" },
112
146
  },
113
147
  },
114
148
  },
115
149
  ["store.sql", "store.kv"],
116
150
  );
117
151
  expect(mismatches).toEqual([
118
- { label: "sql", using: "sqlite", container: "postgres" },
152
+ { label: "sql", using: "pglite", container: "postgres" },
119
153
  { label: "kv", using: "memory", container: "redis" },
120
154
  ]);
121
155
  });
@@ -125,8 +159,8 @@ describe("dockerDevDriverMismatches", () => {
125
159
  {
126
160
  drivers: {
127
161
  store: {
128
- sql: { local: "sqlite", docker: "postgres", prod: "postgres" },
129
- kv: { local: "memory", docker: "redis", prod: "redis" },
162
+ sql: { dev: "postgres", test: "pglite", prod: "postgres" },
163
+ kv: { dev: "redis", test: "memory", prod: "redis" },
130
164
  },
131
165
  },
132
166
  },
@@ -140,8 +174,8 @@ describe("dockerDevDriverMismatches", () => {
140
174
  {
141
175
  drivers: {
142
176
  store: {
143
- sql: { local: "sqlite", prod: "postgres" },
144
- kv: { local: "memory", prod: "redis" },
177
+ sql: { dev: "postgres", test: "pglite", prod: "postgres" },
178
+ kv: { dev: "redis", test: "memory", prod: "redis" },
145
179
  },
146
180
  },
147
181
  },
@@ -0,0 +1,50 @@
1
+ /**
2
+ * CLI rejects removed env keys / drivers in oke.config source.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { assertOkeConfigSource, loadOkeConfig } from "./load-config.ts";
10
+
11
+ describe("assertOkeConfigSource", () => {
12
+ test("rejects local: env keys", () => {
13
+ expect(() =>
14
+ assertOkeConfigSource(`drivers: { store: { sql: { local: "postgres" } } }`),
15
+ ).toThrow(`"local" is no longer a valid environment key in oke.config.ts.`);
16
+ });
17
+
18
+ test('rejects "sqlite" driver pins', () => {
19
+ expect(() => assertOkeConfigSource(`drivers: { store: { sql: { dev: "sqlite" } } }`)).toThrow(
20
+ `"sqlite" is no longer a valid driver in oke.config.ts.`,
21
+ );
22
+ });
23
+
24
+ test("allows modern three-key maps", () => {
25
+ expect(() =>
26
+ assertOkeConfigSource(`sql: { dev: "postgres", test: "pglite", prod: "postgres" }`),
27
+ ).not.toThrow();
28
+ });
29
+
30
+ test("does not false-positive on localhost", () => {
31
+ expect(() => assertOkeConfigSource(`url: "http://localhost:5432"`)).not.toThrow();
32
+ });
33
+ });
34
+
35
+ describe("loadOkeConfig source gate", () => {
36
+ test("fails before import when local: is present", async () => {
37
+ const dir = await mkdtemp(join(tmpdir(), "oke-cfg-local-"));
38
+ try {
39
+ await writeFile(
40
+ join(dir, "oke.config.ts"),
41
+ `export default { drivers: { store: { sql: { local: "postgres", test: "pglite", prod: "postgres" } } } };\n`,
42
+ );
43
+ await expect(loadOkeConfig(dir)).rejects.toThrow(
44
+ `"local" is no longer a valid environment key in oke.config.ts.`,
45
+ );
46
+ } finally {
47
+ await rm(dir, { recursive: true, force: true });
48
+ }
49
+ });
50
+ });
@@ -3,7 +3,13 @@
3
3
  */
4
4
 
5
5
  import { resolve } from "node:path";
6
- import type { DriverRef, OkeConfig } from "../config/index.ts";
6
+ import {
7
+ driverRefId,
8
+ flattenImagesConfig,
9
+ normalizeEnvDriverMap,
10
+ type EnvDriverInput,
11
+ type OkeConfig,
12
+ } from "../config/index.ts";
7
13
  import type { Manifest } from "../manifest/types.ts";
8
14
 
9
15
  /** Default image pins when `images` is omitted but prod drivers need containers. */
@@ -11,27 +17,15 @@ const DEFAULT_SQL_IMAGE = "postgres:18-alpine";
11
17
  const DEFAULT_PGVECTOR_IMAGE = "pgvector/pgvector:pg17";
12
18
  const DEFAULT_PGDOG_IMAGE = "ghcr.io/pgdogdev/pgdog:v0.1.51";
13
19
  const DEFAULT_KV_IMAGE = "redis:8-alpine";
14
- const DEFAULT_VAULT_IMAGE = "openbao/openbao:2.6.1";
15
20
 
16
21
  /**
17
- * Extract protocol id from a driver ref.
22
+ * Prefer `dev` then `prod` for a driver map (compose infra ≈ production).
18
23
  *
19
- * @param ref - String or `{ driver }` object
24
+ * @param map - Env driver input (normalized first)
20
25
  */
21
- function driverId(ref: DriverRef | undefined): string | undefined {
22
- if (ref === undefined) return undefined;
23
- return typeof ref === "string" ? ref : ref.driver;
24
- }
25
-
26
- /**
27
- * Prefer `docker` then `prod` for a driver map (compose infra ≈ production).
28
- *
29
- * @param map - Env driver map
30
- */
31
- function dockerOrProdId(
32
- map: { readonly docker?: DriverRef; readonly prod?: DriverRef } | undefined,
33
- ): string | undefined {
34
- return driverId(map?.docker) ?? driverId(map?.prod);
26
+ function dockerOrProdId(map: EnvDriverInput | undefined): string | undefined {
27
+ const normalized = normalizeEnvDriverMap(map);
28
+ return driverRefId(normalized?.dev) ?? driverRefId(normalized?.prod);
35
29
  }
36
30
 
37
31
  /**
@@ -75,11 +69,6 @@ export function defaultImagesFromConfig(config: OkeConfig): Readonly<Record<stri
75
69
  out["store.kv"] = DEFAULT_KV_IMAGE;
76
70
  }
77
71
 
78
- const vault = dockerOrProdId(config.drivers?.vault);
79
- if (vault === "openbao") {
80
- out.vault = DEFAULT_VAULT_IMAGE;
81
- }
82
-
83
72
  return out;
84
73
  }
85
74
 
@@ -89,6 +78,21 @@ export interface LoadedConfig {
89
78
  readonly path: string;
90
79
  }
91
80
 
81
+ /**
82
+ * Reject removed env keys / drivers in `oke.config.*` source.
83
+ * Migration is documented — no soft-compat rewrite.
84
+ *
85
+ * @param source - File contents
86
+ */
87
+ export function assertOkeConfigSource(source: string): void {
88
+ if (/(^|[^\w.])local\s*:/.test(source)) {
89
+ throw new Error(`"local" is no longer a valid environment key in oke.config.ts.`);
90
+ }
91
+ if (/(["'])sqlite\1/.test(source)) {
92
+ throw new Error(`"sqlite" is no longer a valid driver in oke.config.ts.`);
93
+ }
94
+ }
95
+
92
96
  /**
93
97
  * Load `oke.config.ts` (or `.js` / `.mts`) from a directory.
94
98
  *
@@ -110,6 +114,8 @@ export async function loadOkeConfig(
110
114
  for (const path of candidates) {
111
115
  const file = Bun.file(path);
112
116
  if (!(await file.exists())) continue;
117
+ const source = await file.text();
118
+ assertOkeConfigSource(source);
113
119
  const mod = (await import(path)) as { default?: OkeConfig };
114
120
  if (!mod.default || typeof mod.default !== "object") {
115
121
  throw new Error(`oke: ${path} must default-export defineConfig({...})`);
@@ -145,7 +151,7 @@ export function resolveImages(
145
151
  config?: OkeConfig,
146
152
  manifest?: Manifest,
147
153
  ): Readonly<Record<string, string>> {
148
- const explicit = config?.images ?? manifest?.images;
154
+ const explicit = config?.images ? flattenImagesConfig(config.images) : manifest?.images;
149
155
  if (explicit && Object.keys(explicit).length > 0) return explicit;
150
156
  if (config) return defaultImagesFromConfig(config);
151
157
  return {};
@@ -174,14 +180,14 @@ export function dockerDevDriverMismatches(
174
180
  ): readonly StackDriverMismatch[] {
175
181
  const roles = new Set(stackRoles);
176
182
  const out: StackDriverMismatch[] = [];
177
- const sqlDocker =
178
- driverId(config.drivers?.store?.sql?.docker) ?? driverId(config.drivers?.store?.sql?.prod);
179
- const kvDocker =
180
- driverId(config.drivers?.store?.kv?.docker) ?? driverId(config.drivers?.store?.kv?.prod);
183
+ const sqlMap = normalizeEnvDriverMap(config.drivers?.store?.sql);
184
+ const kvMap = normalizeEnvDriverMap(config.drivers?.store?.kv);
185
+ const sqlDocker = driverRefId(sqlMap?.dev) ?? driverRefId(sqlMap?.prod);
186
+ const kvDocker = driverRefId(kvMap?.dev) ?? driverRefId(kvMap?.prod);
181
187
 
182
188
  if (
183
189
  roles.has("store.sql") &&
184
- (sqlDocker === "sqlite" || sqlDocker === "memory" || sqlDocker === undefined)
190
+ (sqlDocker === "memory" || sqlDocker === "pglite" || sqlDocker === undefined)
185
191
  ) {
186
192
  out.push({
187
193
  label: "sql",
@@ -5,8 +5,7 @@
5
5
  * opts into the `meilisearch` store.index driver. This module:
6
6
  *
7
7
  * - generates the master key once and persists it under `.oke/meilisearch/`
8
- * (`0700` dir / `0600` file, atomic write — same material discipline as
9
- * OpenBao, minus the init/unseal ceremony: a single static key),
8
+ * (`0700` dir / `0600` file, atomic write — a single static key),
10
9
  * - resolves the `meilisearch` binary from `PATH` (a documented prerequisite,
11
10
  * like Docker for `--docker`; we never auto-download binaries),
12
11
  * - spawns it via `Bun.spawn` with its own data dir + port, polls `/health`,
package/src/cli/mode.ts CHANGED
@@ -1,65 +1,14 @@
1
1
  /**
2
- * `oke mode [local|docker]` — read or set the saved `oke dev` preference.
3
- * Setting a mode also syncs domain schema for that env (emit + push).
2
+ * `oke mode` — removed. `oke dev` always uses Docker Compose.
4
3
  */
5
4
 
6
- import { parseDevMode, readDevMode, writeDevMode, type DevMode } from "./dev-mode.ts";
7
- import { sqlDialectForEnv, syncDevSchema } from "./dev-schema-sync.ts";
8
- import { loadOkeConfig } from "./load-config.ts";
9
-
10
5
  /**
11
- * CLI entry for `oke mode`.
6
+ * CLI entry for deprecated `oke mode`.
12
7
  *
13
- * @param args - Args after `mode`
8
+ * @param _args - Ignored
14
9
  */
15
- export async function modeCli(args: readonly string[]): Promise<number> {
16
- const cwd = process.cwd();
17
- for (const a of args) {
18
- if (a === "--help" || a === "-h") {
19
- console.log(`oke mode [local|docker]
20
-
21
- Get or set the default infrastructure mode for \`oke dev\`
22
- (saved in .oke/mode). Setting a mode syncs domain schema for that env:
23
- emits schema.generated.ts for the active dialect, then runs \`oke db push\`.
24
- Data planes stay isolated — switching modes never copies rows.
25
- Session flags \`oke dev --local\` / \`oke dev --docker\` never change this
26
- preference.
27
- `);
28
- return 0;
29
- }
30
- }
31
-
32
- const positional = args.filter((a) => !a.startsWith("-"));
33
- if (positional.length === 0) {
34
- const saved = await readDevMode(cwd);
35
- if (saved === null) {
36
- console.log("oke mode: unset (default local until chosen)");
37
- return 0;
38
- }
39
- console.log(saved);
40
- return 0;
41
- }
42
-
43
- const next = parseDevMode(positional[0]);
44
- if (next === null) {
45
- console.error(`oke mode: expected local|docker, got ${JSON.stringify(positional[0])}`);
46
- return 1;
47
- }
48
- await writeDevMode(cwd, next satisfies DevMode);
49
-
50
- try {
51
- const result = await syncDevSchema(cwd, next);
52
- console.log(`oke mode: saved ${next} · dialect ${result.dialect}`);
53
- return result.code;
54
- } catch (err) {
55
- const loaded = await loadOkeConfig(cwd).catch(() => null);
56
- const dialect = loaded?.config
57
- ? sqlDialectForEnv(loaded.config, next).dialect
58
- : next === "docker"
59
- ? "postgresql"
60
- : "sqlite";
61
- console.error(`oke mode: saved ${next} · dialect ${dialect}`);
62
- console.error(err instanceof Error ? err.message : String(err));
63
- return 1;
64
- }
10
+ export async function modeCli(_args: readonly string[]): Promise<number> {
11
+ void _args;
12
+ console.error("oke mode was removed — `oke dev` always uses Docker Compose");
13
+ return 1;
65
14
  }
@@ -84,6 +84,27 @@ const HELP: CliFlag = {
84
84
  summary: "Show help",
85
85
  };
86
86
 
87
+ const VAULT_KEY: CliFlag = {
88
+ long: "--key",
89
+ takesValue: true,
90
+ valueName: "base64|-",
91
+ summary: "Master key (`-` = stdin; else env / interactive prompt)",
92
+ };
93
+
94
+ const VAULT_NEW_KEY: CliFlag = {
95
+ long: "--new-key",
96
+ takesValue: true,
97
+ valueName: "base64|-",
98
+ summary: "New master key for resume (`-` = stdin)",
99
+ };
100
+
101
+ const VAULT_URL: CliFlag = {
102
+ long: "--url",
103
+ takesValue: true,
104
+ valueName: "url",
105
+ summary: "SQL URL (else DATABASE_URL)",
106
+ };
107
+
87
108
  /**
88
109
  * Canonical `oke` command catalogue.
89
110
  *
@@ -93,24 +114,19 @@ const HELP: CliFlag = {
93
114
  export const OKE_COMMANDS: readonly CliCommand[] = [
94
115
  {
95
116
  name: "dev",
96
- summary: "watch · hot reload · Console · client types",
117
+ summary: "watch · hot reload · Console · client types (Docker Compose)",
97
118
  leaf: true,
98
119
  flags: [
99
- {
100
- long: "--local",
101
- short: "-l",
102
- summary: "Session-only local mode (in-memory; no compose)",
103
- },
104
120
  {
105
121
  long: "--docker",
106
122
  short: "-d",
107
123
  takesValue: true,
108
124
  valueName: "roles",
109
- summary: "Session-only docker compose (optional role list)",
125
+ summary: "Optional compose role filter (default: all roles)",
110
126
  },
111
127
  {
112
128
  long: "--no-db-push",
113
- summary: "Disable auto `oke db push` on schema change (local)",
129
+ summary: "Disable auto `oke db push` on schema change",
114
130
  },
115
131
  ENTRY,
116
132
  HELP,
@@ -118,7 +134,13 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
118
134
  },
119
135
  {
120
136
  name: "mode",
121
- summary: "get/set default oke dev mode (local|docker)",
137
+ summary: "deprecated oke dev always uses Docker Compose",
138
+ leaf: true,
139
+ flags: [HELP],
140
+ },
141
+ {
142
+ name: "test",
143
+ summary: "run bun test with PGLite test posture",
122
144
  leaf: true,
123
145
  flags: [HELP],
124
146
  },
@@ -182,11 +204,11 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
182
204
  },
183
205
  {
184
206
  name: "schema",
185
- summary: "core + plugin tables → schema/oke.ts",
207
+ summary: "core + plugin tables → .oke/schema/oke.ts",
186
208
  subcommands: [
187
209
  {
188
210
  name: "generate",
189
- summary: "Emit schema/oke.ts",
211
+ summary: "Emit .oke/schema/oke.ts",
190
212
  flags: [
191
213
  {
192
214
  long: "--check",
@@ -202,7 +224,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
202
224
  },
203
225
  {
204
226
  name: "db",
205
- summary: "domain schema — push · generate · migrate · seed",
227
+ summary: "domain schema — push · generate · migrate · seed · studio",
206
228
  subcommands: [
207
229
  {
208
230
  name: "push",
@@ -219,7 +241,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
219
241
  long: "--env",
220
242
  takesValue: true,
221
243
  valueName: "name",
222
- summary: "Config env (local|docker|test|prod)",
244
+ summary: "Config env (dev|test|prod)",
223
245
  },
224
246
  HELP,
225
247
  ],
@@ -239,7 +261,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
239
261
  long: "--env",
240
262
  takesValue: true,
241
263
  valueName: "name",
242
- summary: "Config env (local|docker|test|prod)",
264
+ summary: "Config env (dev|test|prod)",
243
265
  },
244
266
  HELP,
245
267
  ],
@@ -259,7 +281,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
259
281
  long: "--env",
260
282
  takesValue: true,
261
283
  valueName: "name",
262
- summary: "Config env (local|docker|test|prod)",
284
+ summary: "Config env (dev|test|prod)",
263
285
  },
264
286
  HELP,
265
287
  ],
@@ -272,7 +294,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
272
294
  long: "--env",
273
295
  takesValue: true,
274
296
  valueName: "name",
275
- summary: "Config env (local|docker|test|prod)",
297
+ summary: "Config env (dev|test|prod)",
276
298
  },
277
299
  {
278
300
  long: "--force",
@@ -282,6 +304,26 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
282
304
  HELP,
283
305
  ],
284
306
  },
307
+ {
308
+ name: "studio",
309
+ summary: "Open drizzle-kit Studio (long-running)",
310
+ flags: [
311
+ {
312
+ long: "--config",
313
+ short: "-c",
314
+ takesValue: true,
315
+ valueName: "path",
316
+ summary: "drizzle.config.ts path",
317
+ },
318
+ {
319
+ long: "--env",
320
+ takesValue: true,
321
+ valueName: "name",
322
+ summary: "Config env (dev|test|prod)",
323
+ },
324
+ HELP,
325
+ ],
326
+ },
285
327
  ],
286
328
  },
287
329
  {
@@ -298,7 +340,7 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
298
340
  },
299
341
  {
300
342
  name: "vault",
301
- summary: "set · list · import · key rotate",
343
+ summary: "secrets — set · list · seal/unseal · rotate · audit · backup",
302
344
  subcommands: [
303
345
  {
304
346
  name: "set",
@@ -319,6 +361,96 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
319
361
  positionals: "rotate",
320
362
  flags: [HELP],
321
363
  },
364
+ {
365
+ name: "init",
366
+ summary: "Create the builtin vault; print the master key once",
367
+ flags: [VAULT_URL, HELP],
368
+ },
369
+ {
370
+ name: "status",
371
+ summary: "Seal state · KEK generation · secret count",
372
+ flags: [VAULT_KEY, VAULT_URL, JSON_FLAG, HELP],
373
+ },
374
+ {
375
+ name: "seal",
376
+ summary: "Drop the master key; reads fail until unsealed",
377
+ flags: [VAULT_URL, HELP],
378
+ },
379
+ {
380
+ name: "unseal",
381
+ summary: "Verify the master key and clear the sealed flag",
382
+ flags: [VAULT_KEY, VAULT_URL, HELP],
383
+ },
384
+ {
385
+ name: "rotate",
386
+ summary: "New version of a secret under a fresh data key",
387
+ positionals: "<path> [value]",
388
+ flags: [VAULT_KEY, VAULT_URL, HELP],
389
+ },
390
+ {
391
+ name: "rotate-master",
392
+ summary: "New master key; re-wrap every data key",
393
+ flags: [VAULT_KEY, VAULT_NEW_KEY, VAULT_URL, HELP],
394
+ },
395
+ {
396
+ name: "audit",
397
+ summary: "Read · verify · purge the tamper-evident chain",
398
+ positionals: "[verify|purge]",
399
+ flags: [
400
+ {
401
+ long: "--limit",
402
+ takesValue: true,
403
+ valueName: "n",
404
+ summary: "Rows to show (default 50)",
405
+ },
406
+ {
407
+ long: "--path",
408
+ takesValue: true,
409
+ valueName: "path",
410
+ summary: "Restrict to one secret path",
411
+ },
412
+ {
413
+ long: "--before",
414
+ takesValue: true,
415
+ valueName: "iso",
416
+ summary: "Purge rows older than this instant",
417
+ },
418
+ VAULT_URL,
419
+ JSON_FLAG,
420
+ HELP,
421
+ ],
422
+ },
423
+ {
424
+ name: "purge-expired",
425
+ summary: "Hard-delete secret rows past expires_at",
426
+ flags: [
427
+ VAULT_KEY,
428
+ VAULT_URL,
429
+ {
430
+ long: "--dry-run",
431
+ summary: "Count expired rows without deleting",
432
+ },
433
+ {
434
+ long: "--before",
435
+ takesValue: true,
436
+ valueName: "iso",
437
+ summary: "Exclusive upper bound (default: now)",
438
+ },
439
+ HELP,
440
+ ],
441
+ },
442
+ {
443
+ name: "backup",
444
+ summary: "Write every live secret as one encrypted bundle",
445
+ positionals: "<file>",
446
+ flags: [VAULT_KEY, VAULT_URL, HELP],
447
+ },
448
+ {
449
+ name: "restore",
450
+ summary: "Replay a bundle as new versions",
451
+ positionals: "<file>",
452
+ flags: [VAULT_KEY, VAULT_URL, HELP],
453
+ },
322
454
  ],
323
455
  },
324
456
  {
@@ -549,7 +681,15 @@ export const OKE_COMMANDS: readonly CliCommand[] = [
549
681
  * @returns Help text (no trailing exit-code block)
550
682
  */
551
683
  export function formatOkeHelp(): string {
552
- const lines = ["oke — okengine CLI", "", "Commands:"];
684
+ const lines = [
685
+ "oke — okengine CLI",
686
+ "",
687
+ " oke Interactive TUI (TTY). Non-TTY prints this help.",
688
+ " oke --tui Force the interactive TUI",
689
+ " oke --help This command list",
690
+ "",
691
+ "Commands:",
692
+ ];
553
693
  for (const cmd of OKE_COMMANDS) {
554
694
  const usage = formatCommandUsage(cmd);
555
695
  lines.push(` ${pad(usage, 34)} ${cmd.summary}`);
@@ -608,7 +748,7 @@ function formatCommandUsage(cmd: CliCommand): string {
608
748
  return `oke ${cmd.name} ${sub.name}${pos}${flagHint ? ` ${flagHint}` : ""}`;
609
749
  }
610
750
  if (cmd.subcommands && cmd.subcommands.length > 1) {
611
- const subs = cmd.subcommands.map((s) => s.name).join("|");
751
+ const subs = summarizeSubcommands(cmd.subcommands);
612
752
  // Prefer shared JSON flag hint when every list-like sub supports it.
613
753
  const jsonHint = cmd.subcommands.some((s) => s.flags?.some((f) => f.long === "--json"))
614
754
  ? " [--json|-j]"
@@ -624,6 +764,22 @@ function formatCommandUsage(cmd: CliCommand): string {
624
764
  return `oke ${cmd.name}${pos}${flagHint ? ` ${flagHint}` : ""}`;
625
765
  }
626
766
 
767
+ /** Longest `a|b|c` sub list the one-line help layout can carry. */
768
+ const MAX_SUBCOMMAND_HINTS = 4;
769
+
770
+ /**
771
+ * Join subcommand names for the one-line usage, eliding a long tail.
772
+ *
773
+ * Commands with a large surface (`vault`) would otherwise push the summary
774
+ * column off screen; the full list still reaches completion and `--help`.
775
+ *
776
+ * @param subs - Subcommands in registry order
777
+ */
778
+ function summarizeSubcommands(subs: readonly CliSubcommand[]): string {
779
+ const shown = subs.slice(0, MAX_SUBCOMMAND_HINTS).map((s) => s.name);
780
+ return subs.length > MAX_SUBCOMMAND_HINTS ? `${shown.join("|")}|…` : shown.join("|");
781
+ }
782
+
627
783
  function formatFlagHint(f: CliFlag): string {
628
784
  const name = f.short ? `${f.long}|${f.short}` : f.long;
629
785
  if (f.takesValue) {