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
package/src/cli/dev.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `oke dev` — watch · hot reload · Console :6533 · MCP :6535 · client types on save.
3
- * Mode: `local` (in-memory) or `docker` (compose infra + host Bun).
3
+ * Always starts Docker Compose infra and runs the app on host Bun.
4
4
  */
5
5
 
6
6
  import { watch } from "node:fs";
@@ -19,7 +19,6 @@ import {
19
19
  stackAppSlug,
20
20
  stackInstanceId,
21
21
  writeDerivedFiles,
22
- formatStackEnv,
23
22
  type DeriveOptions,
24
23
  } from "../docker/index.ts";
25
24
  import type { Manifest } from "../manifest/types.ts";
@@ -45,19 +44,13 @@ import {
45
44
  } from "../term.ts";
46
45
  import { clientAdd } from "./client-add.ts";
47
46
  import { resolveDriverId } from "../config/index.ts";
48
- import { askDevMode, type AskDevModeFn } from "./ask-dev-mode.ts";
49
- import {
50
- formatDevControlsHint,
51
- startDevControls,
52
- type DevComposeControlAction,
53
- } from "./dev-controls.ts";
47
+ import type { DevComposeControlAction } from "./dev-controls.ts";
54
48
  import {
55
49
  createDebouncedRunner,
56
50
  isDomainSchemaWatchPath,
57
51
  resolveDevAutoPush,
58
52
  } from "./db-auto-push.ts";
59
53
  import { resolveDrizzleConfigPath, runPush } from "./db.ts";
60
- import { readDevMode, shouldAskDevMode, writeDevMode, type DevMode } from "./dev-mode.ts";
61
54
  import {
62
55
  buildDevHeroSnapshot,
63
56
  encodeHeroSnapshot,
@@ -68,6 +61,7 @@ import {
68
61
  import { loadManifest, loadOkeConfig, resolveImages } from "./load-config.ts";
69
62
  import { mcpContextFromConsole } from "./mcp-from-console.ts";
70
63
  import { resolveDevPorts } from "./ports.ts";
64
+ import { clearDevSessionLock, writeDevSessionLock } from "./dev-session-lock.ts";
71
65
 
72
66
  /** Max wait for the `bun --hot` app child to bind a port. */
73
67
  const APP_READY_TIMEOUT_MS = 30_000;
@@ -153,12 +147,10 @@ export interface DevOptions {
153
147
  readonly cwd?: string;
154
148
  readonly entry?: string;
155
149
  /**
156
- * Session-only mode override. `true` / role list docker; `false` local.
157
- * When unset, resolve from `.oke/mode` / TTY ask / non-TTY default `local`.
150
+ * Optional compose role filter. `true` / unsetall roles from images;
151
+ * a string list only those roles.
158
152
  */
159
153
  readonly docker?: boolean | readonly string[];
160
- /** Force local for this session only (never writes `.oke/mode`). */
161
- readonly local?: boolean;
162
154
  /**
163
155
  * Opt out of auto `oke db push` on schema change for this session.
164
156
  * Also set via `--no-db-push`.
@@ -178,11 +170,13 @@ export interface DevOptions {
178
170
  readonly onDbAutoPush?: (filename: string) => void;
179
171
  /**
180
172
  * Injectable `.adopt()` barrel regeneration (tests). Default: real
181
- * `generateAdoptBarrel` + write to `<cwd>/src/flows/generated.ts`. Runs
182
- * once per `oke dev` session, before the entry is resolved/imported —
183
- * a real file on disk, so `oke dev`'s runtime `import()` and `oke build`'s
184
- * `Bun.build()` resolve it identically (unlike a virtual-module Bun
185
- * plugin, which does not — see `compiler/generate-adopt.ts`).
173
+ * `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/generated.ts`
174
+ * (`generated.ts.tmp` → rename). Runs once per `oke dev` session, before
175
+ * the entry is resolved/imported — a real file on disk, so `oke dev`'s
176
+ * runtime `import()` and `oke build`'s `Bun.build()` resolve it
177
+ * identically (unlike a virtual-module Bun plugin, which does not — see
178
+ * `compiler/generate-adopt.ts`). Atomic so a concurrent import during
179
+ * `bun --hot` never sees a torn mid-write file.
186
180
  *
187
181
  * @param cwd - Project root
188
182
  */
@@ -284,14 +278,16 @@ export interface DevOptions {
284
278
  * app code while preserving the listen socket.
285
279
  */
286
280
  readonly startApp?: (entry: string, env: Record<string, string>) => Promise<DevAppHandle>;
287
- /**
288
- * Injectable mode prompt (tests). Default: {@link askDevMode}.
289
- */
290
- readonly ask?: AskDevModeFn;
291
281
  /**
292
282
  * Override stdin TTY detection (tests). Default: `process.stdin.isTTY`.
293
283
  */
294
284
  readonly stdinIsTTY?: boolean;
285
+ /**
286
+ * Probe Docker daemon availability (injectable). Default: `docker info`.
287
+ * Skipped on {@link dryRun}; when {@link composeUp} is injected and this is
288
+ * omitted, the probe is a no-op (unit tests never shell out).
289
+ */
290
+ readonly assertDocker?: () => Promise<void>;
295
291
  }
296
292
 
297
293
  /** Result of a dry-run / prepared / live-but-detached dev session. */
@@ -346,18 +342,18 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
346
342
  const cwd = options.cwd ?? process.cwd();
347
343
 
348
344
  // One-shot `.adopt()` barrel regen for the session — real file on disk
349
- // (`src/flows/generated.ts`), written before the entry is resolved/imported
350
- // below so a freshly-added flows unit is adoptable without a hand edit.
351
- // Best-effort like `syncDevSchema` below: a project with no `src/flows`
352
- // yet (or a synthetic test tree) never blocks the dev session.
345
+ // (`src/flows/generated.ts`), written atomically (tmp rename) before the
346
+ // entry is resolved/imported below so a freshly-added flows unit is
347
+ // adoptable without a hand edit. Best-effort like `syncDevSchema` below:
348
+ // a project with no `src/flows` yet (or a synthetic test tree) never
349
+ // blocks the dev session.
353
350
  const syncAdoptBarrel =
354
351
  options.syncAdoptBarrel ??
355
352
  (async (root: string) => {
356
- const { generateAdoptBarrel } = await import("../compiler/generate-adopt.ts");
357
- const { mkdir, writeFile } = await import("node:fs/promises");
353
+ const { generateAdoptBarrel, writeAdoptBarrel } =
354
+ await import("../compiler/generate-adopt.ts");
358
355
  const { source, units } = await generateAdoptBarrel({ rootDir: root });
359
- await mkdir(resolve(root, "src/flows"), { recursive: true });
360
- await writeFile(resolve(root, "src/flows/generated.ts"), source);
356
+ await writeAdoptBarrel(root, source);
361
357
  return units;
362
358
  });
363
359
  try {
@@ -409,42 +405,16 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
409
405
  return { code: 1 };
410
406
  }
411
407
 
412
- const explicitLocal = options.local === true;
413
- const explicitDocker = options.docker === true || Array.isArray(options.docker);
414
- if (explicitLocal && explicitDocker) {
415
- console.error("oke dev: use either --local or --docker, not both");
416
- restoreWarn();
417
- return { code: 1 };
418
- }
419
-
420
- const savedMode = await readDevMode(cwd);
421
- const explicit = explicitLocal || explicitDocker;
422
- let mode: DevMode;
423
- if (explicitLocal) {
424
- mode = "local";
425
- } else if (explicitDocker) {
426
- mode = "docker";
427
- } else if (savedMode !== null) {
428
- mode = savedMode;
429
- } else if (
430
- shouldAskDevMode({
431
- saved: savedMode,
432
- explicit,
433
- stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
434
- })
435
- ) {
436
- const ask = options.ask ?? askDevMode;
437
- const chosen = await ask();
438
- if (chosen === null) {
439
- console.error("oke dev: cancelled");
408
+ // Fail fast when the Docker daemon is down (skip on dryRun / injected compose).
409
+ if (!options.dryRun) {
410
+ const probe = options.assertDocker ?? (options.composeUp ? async () => {} : assertDockerDaemon);
411
+ try {
412
+ await probe();
413
+ } catch (err) {
414
+ console.error(err instanceof Error ? err.message : String(err));
440
415
  restoreWarn();
441
416
  return { code: 1 };
442
417
  }
443
- mode = chosen;
444
- await writeDevMode(cwd, mode);
445
- } else {
446
- // Non-TTY + unset: deterministic local — zero ask, zero docker, no save.
447
- mode = "local";
448
418
  }
449
419
 
450
420
  // Paint chrome immediately so docker/vault work is never a blank 6s wait.
@@ -457,7 +427,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
457
427
  } catch {
458
428
  // shipped binary may not sit next to package.json
459
429
  }
460
- const earlyProfile = resolveDevProfile({ docker: mode === "docker", nodeEnv: "development" });
430
+ const earlyProfile = resolveDevProfile({ docker: true, nodeEnv: "development" });
461
431
  write(
462
432
  formatDevBanner({
463
433
  profile: earlyProfile,
@@ -507,26 +477,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
507
477
  loadedConfig = null;
508
478
  }
509
479
 
510
- // Local mode + a configured `meilisearch` store.index: bring the server up
511
- // as a fourth local process (binary on PATH — a documented prerequisite,
512
- // like Docker for --docker). Docker mode instead relies on the recipe.
513
- let meili: import("./meilisearch-local.ts").LocalMeilisearchHandle | null = null;
514
- if (mode === "local") {
515
- const indexId = resolveDriverId(loadedConfig?.drivers?.store?.index, "local") ?? "memory";
516
- if (indexId === "meilisearch") {
517
- try {
518
- const { startLocalMeilisearch } = await import("./meilisearch-local.ts");
519
- meili = await startLocalMeilisearch({ cwd });
520
- write(formatStatusLine(`meilisearch local server on ${meili.url}`));
521
- } catch (err) {
522
- console.error(err instanceof Error ? err.message : String(err));
523
- restoreWarn();
524
- return { code: 1 };
525
- }
526
- }
527
- }
528
-
529
- if (mode === "docker") {
480
+ {
530
481
  let images = options.images;
531
482
  if (!images) {
532
483
  if (!loadedConfig) {
@@ -571,6 +522,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
571
522
  images,
572
523
  app: appSlug,
573
524
  prod: false,
525
+ layout: "single",
574
526
  includeApp: false,
575
527
  composeDir,
576
528
  instanceId,
@@ -578,11 +530,15 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
578
530
  ...(controls ? { controls } : {}),
579
531
  host: "127.0.0.1",
580
532
  });
581
- // Layer 4 is user-owned — include only if the file already exists.
582
- const existingOverride = await Bun.file(resolve(dockerOut, "compose.override.yml")).exists();
583
- composeFiles = derived.composeFiles.filter(
584
- (f) => f !== "compose.override.yml" || existingOverride,
585
- );
533
+ // User-owned overrides — include only if the file already exists.
534
+ composeFiles = [];
535
+ for (const f of derived.composeFiles) {
536
+ if (f === "compose.override.yml" || f === "docker-compose.override.yml") {
537
+ if (await Bun.file(resolve(dockerOut, f)).exists()) composeFiles.push(f);
538
+ continue;
539
+ }
540
+ composeFiles.push(f);
541
+ }
586
542
  stackEnv = { ...derived.stackEnv };
587
543
 
588
544
  const roleLabel = (role: string): string => {
@@ -646,7 +602,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
646
602
  if (code !== 0) {
647
603
  const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
648
604
  throw new Error(
649
- `oke dev --docker: docker compose exited ${code}` + (detail ? `\n${detail}` : ""),
605
+ `oke dev: docker compose exited ${code}` + (detail ? `\n${detail}` : ""),
650
606
  );
651
607
  }
652
608
  });
@@ -661,27 +617,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
661
617
  env: { ...stackEnv! },
662
618
  };
663
619
 
664
- // Real OpenBao vault: init once / unseal every start, then expose the
665
- // least-privilege app token to the app + Console (never root).
666
- const vaultSpec = derived.specs.find((s) => s.role === "vault");
667
- if (vaultSpec) {
668
- bootProgress.set("vault", formatStatusLine("vault unseal…", undefined, "pending"));
669
- const { ensureOpenBao, openbaoStackEnv } = await import("./openbao-bootstrap.ts");
670
- const vaultNames = await tryLoadVaultSecretNames(cwd);
671
- const boot = await ensureOpenBao({
672
- cwd,
673
- url: `http://127.0.0.1:${vaultSpec.hostPort}`,
674
- names: vaultNames,
675
- });
676
- Object.assign(stackEnv!, openbaoStackEnv(boot));
677
- Object.assign(dockerStarted.env, openbaoStackEnv(boot));
678
- // Rewrite .env.docker with the full stack env (including token) so a
679
- // later ensureDockerStack / writeDerivedFiles does not drop it.
680
- const envPath = resolve(dockerOut, ".env.docker");
681
- await Bun.write(envPath, formatStackEnv(stackEnv!));
682
- bootProgress.set("vault", formatStatusLine("vault ready", undefined, "ready"));
683
- }
684
-
685
620
  // Stream compose health until infra is up (AI may stay pending — model load).
686
621
  const aiServiceName = derived.specs.find((s) => s.role === "ai")?.serviceName;
687
622
  const labelForService = (serviceName: string): string => {
@@ -745,7 +680,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
745
680
  const model =
746
681
  stackEnv!.OKE_AI_MODEL?.trim() ||
747
682
  process.env.OKE_AI_MODEL?.trim() ||
748
- (recipeId === "ollama" ? "qwen3.5:9b" : "smollm2");
683
+ (recipeId === "ollama" ? "qwen3.5:9b" : "granite3.3:2b");
749
684
  const url = `http://127.0.0.1:${aiSpec.hostPort}`;
750
685
  const paintAi = (status: DevStatus, detail?: string) => {
751
686
  heroAiStatus = status;
@@ -805,7 +740,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
805
740
  } catch (err) {
806
741
  const msg = err instanceof Error ? err.message : String(err);
807
742
  console.error(msg);
808
- console.error("oke dev: docker mode failed — fix compose, or run `oke mode local`");
743
+ console.error("oke dev: Docker Compose failed — fix compose and retry");
809
744
  restoreWarn();
810
745
  return { code: 1 };
811
746
  }
@@ -831,9 +766,9 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
831
766
  }
832
767
  const heroSnapshot = buildDevHeroSnapshot({
833
768
  config: loadedConfig,
834
- docker: mode === "docker",
835
- sqlDriver: mode === "docker" ? stackSqlDriver : undefined,
836
- kvDriver: mode === "docker" ? stackKvDriver : undefined,
769
+ docker: true,
770
+ sqlDriver: stackSqlDriver,
771
+ kvDriver: stackKvDriver,
837
772
  aiModel: heroAiModel,
838
773
  aiStatus: heroAiStatus,
839
774
  version: okeVersion,
@@ -948,7 +883,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
948
883
  bootProgress.clear();
949
884
  const bootBoard = createAnchoredBoard(write);
950
885
  bootBoard.paint(paintBootBoard(heroAiStatus));
951
- // Track everything printed below so can rewrite in place for the session.
886
+ // Track lines printed below the board so live repaints stay anchored.
952
887
  write = bootBoard.wrapWrite(write);
953
888
 
954
889
  if (options.dryRun) {
@@ -1043,27 +978,26 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1043
978
  setProcessEnv("OKE_KV_DRIVER", stackKvDriver);
1044
979
  }
1045
980
 
1046
- // One-shot schema sync for the session profile (both local and docker):
1047
- // emits schema.generated.ts for the active dialect, then pushes via
1048
- // drizzle-kit. Data planes stay isolated — session flags never rewrite
1049
- // `.oke/mode`. Watch auto-push below remains local-only.
981
+ // One-shot schema sync for the compose (`dev`) profile: emits
982
+ // schema.drizzle.ts for the active dialect, then pushes via drizzle-kit.
1050
983
  if (!options.noDbPush) {
1051
984
  const { syncDevSchema } = await import("./dev-schema-sync.ts");
985
+ const configEnv = "dev" as const;
1052
986
  try {
1053
- const result = await syncDevSchema(cwd, mode, {
987
+ const result = await syncDevSchema(cwd, configEnv, {
1054
988
  write: chromeWrite,
1055
- quietComposeReady: mode === "docker",
989
+ quietComposeReady: true,
1056
990
  });
1057
991
  write(
1058
992
  formatStatusLine(
1059
- `oke db push (${mode} · ${result.dialect}) ${result.code === 0 ? "ok" : "failed"}`,
993
+ `oke db push (dev · ${result.dialect}) ${result.code === 0 ? "ok" : "failed"}`,
1060
994
  ),
1061
995
  );
1062
996
  if (result.code === 0) {
1063
997
  const { maybeAskSeed } = await import("./ask-seed.ts");
1064
998
  await maybeAskSeed({
1065
999
  cwd,
1066
- env: mode,
1000
+ env: configEnv,
1067
1001
  write,
1068
1002
  stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1069
1003
  });
@@ -1071,7 +1005,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1071
1005
  } catch (err) {
1072
1006
  write(
1073
1007
  formatStatusLine(
1074
- `oke db push (${mode}) skipped — ${err instanceof Error ? err.message : String(err)}`,
1008
+ `oke db push (dev) skipped — ${err instanceof Error ? err.message : String(err)}`,
1075
1009
  ),
1076
1010
  );
1077
1011
  }
@@ -1102,7 +1036,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1102
1036
  OKE_KV_DRIVER: stackKvDriver,
1103
1037
  }
1104
1038
  : {}),
1105
- ...(meili?.overlay ?? {}),
1106
1039
  /** Backend child — parent Console owns process-local boot notices. */
1107
1040
  OKE_SUPPRESS_BOOT_WARN: "1",
1108
1041
  };
@@ -1250,7 +1183,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1250
1183
  options.dbPush ??
1251
1184
  (async (projectCwd: string) => {
1252
1185
  const configPath = await resolveDrizzleConfigPath(projectCwd);
1253
- return runPush(configPath, (t) => write(t), { cwd: projectCwd, env: mode });
1186
+ return runPush(configPath, (t) => write(t), {
1187
+ cwd: projectCwd,
1188
+ env: "dev",
1189
+ });
1254
1190
  });
1255
1191
 
1256
1192
  let lastSchemaFilename = "";
@@ -1272,9 +1208,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1272
1208
  }
1273
1209
 
1274
1210
  write(formatDevLogSeparator());
1275
- if (dockerStarted && (options.stdinIsTTY ?? process.stdin.isTTY)) {
1276
- write(formatDevControlsHint());
1277
- }
1278
1211
 
1279
1212
  const watchFs: DevWatchFn =
1280
1213
  options.watchFs ?? ((path, watchOptions, listener) => watch(path, watchOptions, listener));
@@ -1306,11 +1239,11 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1306
1239
  bootBoard.stop();
1307
1240
  autoPushRunner.cancel();
1308
1241
  watcher.close();
1309
- meili?.stop();
1310
1242
  app.stop();
1311
1243
  consoleServer.stop();
1312
1244
  mcpServer?.stop();
1313
1245
  docsMcpServer?.stop();
1246
+ void clearDevSessionLock(cwd);
1314
1247
  if (dockerStarted) {
1315
1248
  const started = dockerStarted;
1316
1249
  dockerStarted = null;
@@ -1371,9 +1304,22 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1371
1304
  consoleState,
1372
1305
  };
1373
1306
 
1307
+ // Record ownership so a later `oke` TUI can attach without spawning a second stack.
1308
+ await writeDevSessionLock(cwd, {
1309
+ pid: process.pid,
1310
+ ports: {
1311
+ app: boundAppPort,
1312
+ console: boundConsolePort,
1313
+ mcp: boundMcpPort,
1314
+ docsMcp: boundDocsMcpPort,
1315
+ },
1316
+ startedAt: new Date().toISOString(),
1317
+ cwd,
1318
+ });
1319
+
1374
1320
  if (options.onReady) await options.onReady(session);
1375
1321
 
1376
- // Keyboard controls (docker + TTY): start/stop/restart services without leaving oke dev.
1322
+ // Keyboard controls (docker + TTY): Ink replaces raw-mode stdin.
1377
1323
  if (keepAlive && dockerStarted && (options.stdinIsTTY ?? process.stdin.isTTY)) {
1378
1324
  const started = dockerStarted;
1379
1325
  const syncComposeBoard = async (): Promise<void> => {
@@ -1389,7 +1335,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1389
1335
  };
1390
1336
  const refreshChrome = async (): Promise<void> => {
1391
1337
  await syncComposeBoard();
1392
- // Clear log noise; reprint head + latest ● board + surfaces + empty Logs.
1393
1338
  write(clearTerminalScreen());
1394
1339
  bootBoard.reset();
1395
1340
  write(
@@ -1410,48 +1355,49 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1410
1355
  write(formatServiceLine("Docs MCP", `http://127.0.0.1:${boundDocsMcpPort}`));
1411
1356
  }
1412
1357
  write(formatDevLogSeparator());
1413
- write(formatDevControlsHint());
1414
1358
  };
1415
- const controls = startDevControls({
1416
- write,
1417
- isTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1418
- onQuit: () => {
1419
- stop();
1420
- process.exit(0);
1421
- },
1422
- onRefresh: () => refreshChrome(),
1423
- // Refresh first so help never sits in the middle of request logs.
1424
- onShowPanel: async (body) => {
1425
- await refreshChrome();
1426
- write(body);
1427
- },
1428
- onComposeSettled: () => syncComposeBoard(),
1429
- composeAction: async (action: DevComposeControlAction) => {
1430
- const files = started.files;
1431
- const finalArgs =
1432
- action === "up"
1433
- ? ["compose", ...files.flatMap((f) => ["-f", f]), "up", "-d"]
1434
- : ["compose", ...files.flatMap((f) => ["-f", f]), action];
1435
- const proc = Bun.spawn(["docker", ...finalArgs], {
1436
- cwd: started.cwd,
1437
- stdout: "pipe",
1438
- stderr: "pipe",
1439
- env: { ...process.env, ...started.env },
1440
- });
1441
- const [stdout, stderr, code] = await Promise.all([
1442
- new Response(proc.stdout).text(),
1443
- new Response(proc.stderr).text(),
1444
- proc.exited,
1445
- ]);
1446
- if (code !== 0) {
1447
- const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
1448
- throw new Error(
1449
- `docker compose ${action} exited ${code}` + (detail ? `\n${detail}` : ""),
1450
- );
1451
- }
1452
- },
1453
- });
1454
- stopDevControls = () => controls.stop();
1359
+ const composeAction = async (action: DevComposeControlAction): Promise<void> => {
1360
+ const files = started.files;
1361
+ const finalArgs =
1362
+ action === "up"
1363
+ ? ["compose", ...files.flatMap((f) => ["-f", f]), "up", "-d"]
1364
+ : ["compose", ...files.flatMap((f) => ["-f", f]), action];
1365
+ const proc = Bun.spawn(["docker", ...finalArgs], {
1366
+ cwd: started.cwd,
1367
+ stdout: "pipe",
1368
+ stderr: "pipe",
1369
+ env: { ...process.env, ...started.env },
1370
+ });
1371
+ const [stdout, stderr, code] = await Promise.all([
1372
+ new Response(proc.stdout).text(),
1373
+ new Response(proc.stderr).text(),
1374
+ proc.exited,
1375
+ ]);
1376
+ if (code !== 0) {
1377
+ const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
1378
+ throw new Error(`docker compose ${action} exited ${code}` + (detail ? `\n${detail}` : ""));
1379
+ }
1380
+ await syncComposeBoard();
1381
+ };
1382
+
1383
+ try {
1384
+ const { launchDevLiveControls } = await import("./tui/DevLive.tsx");
1385
+ await launchDevLiveControls({
1386
+ onRefresh: () => refreshChrome(),
1387
+ onComposeUp: () => composeAction("up"),
1388
+ onComposeStop: () => composeAction("stop"),
1389
+ onQuit: () => {
1390
+ stop();
1391
+ process.exit(0);
1392
+ },
1393
+ });
1394
+ // Esc / unmount without q — still tear down.
1395
+ stop();
1396
+ } catch {
1397
+ // Ink optional — fall back to keep-alive without keyboard chrome.
1398
+ await new Promise(() => {});
1399
+ }
1400
+ return { code: 0, plan: session.plan, session };
1455
1401
  }
1456
1402
 
1457
1403
  if (!keepAlive) {
@@ -1463,21 +1409,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1463
1409
  return { code: 0, plan: session.plan, session };
1464
1410
  }
1465
1411
 
1466
- /**
1467
- * Declared vault secret names from the project Manifest (policy scope).
1468
- *
1469
- * @param cwd - Project root
1470
- */
1471
- async function tryLoadVaultSecretNames(cwd: string): Promise<readonly string[]> {
1472
- try {
1473
- const { extractManifest } = await import("../compiler/extract.ts");
1474
- const manifest = await extractManifest({ rootDir: cwd });
1475
- return Object.keys(manifest.vault ?? {});
1476
- } catch {
1477
- return [];
1478
- }
1479
- }
1480
-
1481
1412
  /**
1482
1413
  * True when a Manifest has no declared flows (scaffold / empty extract).
1483
1414
  *
@@ -1609,20 +1540,34 @@ async function waitForAppReady(
1609
1540
  }
1610
1541
 
1611
1542
  /**
1612
- * CLI entry for `oke dev [--local|-l] [--docker|-d [roles]]`.
1543
+ * Probe `docker info` throws a clear error when the daemon is unavailable.
1544
+ */
1545
+ async function assertDockerDaemon(): Promise<void> {
1546
+ const proc = Bun.spawn(["docker", "info"], {
1547
+ stdout: "pipe",
1548
+ stderr: "pipe",
1549
+ });
1550
+ const [stderr, code] = await Promise.all([new Response(proc.stderr).text(), proc.exited]);
1551
+ if (code === 0) return;
1552
+ const detail = stderr.trim();
1553
+ throw new Error(
1554
+ "oke dev: Docker daemon unavailable — start Docker Desktop (or the Docker daemon) and retry" +
1555
+ (detail ? `\n${detail}` : ""),
1556
+ );
1557
+ }
1558
+
1559
+ /**
1560
+ * CLI entry for `oke dev [-d [roles]]`.
1613
1561
  *
1614
1562
  * @param args - Args after `dev`
1615
1563
  */
1616
1564
  export async function devCli(args: readonly string[]): Promise<number> {
1617
1565
  let docker: boolean | string[] | undefined;
1618
- let local = false;
1619
1566
  let noDbPush = false;
1620
1567
  let entry: string | undefined;
1621
1568
  for (let i = 0; i < args.length; i++) {
1622
1569
  const a = args[i]!;
1623
- if (a === "--local" || a === "-l") {
1624
- local = true;
1625
- } else if (a === "--docker" || a === "-d") {
1570
+ if (a === "--docker" || a === "-d") {
1626
1571
  const next = args[i + 1];
1627
1572
  if (next && !next.startsWith("-") && /^[\w.,]+$/.test(next)) {
1628
1573
  docker = next
@@ -1637,23 +1582,26 @@ export async function devCli(args: readonly string[]): Promise<number> {
1637
1582
  noDbPush = true;
1638
1583
  } else if (a === "--entry" || a === "-e") entry = args[++i];
1639
1584
  else if (a === "--help" || a === "-h") {
1640
- console.log(`oke dev [--local|-l] [--docker|-d [roles]] [--no-db-push] [--entry|-e src/app.ts]
1585
+ console.log(`oke dev [-d [roles]] [--no-db-push] [--entry|-e src/app.ts]
1641
1586
 
1642
1587
  Watch · hot reload · Console :6533 · app :6530 · MCP :6535
1643
- Regenerates client types on every save.
1644
- Local mode auto-runs \`oke db push\` when schema.ts changes (opt out: --no-db-push).
1645
-
1646
- Bare \`oke dev\` uses .oke/mode (one-time prompt on a TTY; non-TTY → local).
1647
- --local / --docker override for this session only (never write .oke/mode).
1648
- --docker boots infra under docker/ and runs the app on host Bun with prod
1649
- store drivers (Postgres/Redis). Partial roles: -d store.sql,store.kv.
1650
- Change the saved default with \`oke mode local|docker\`.
1588
+ Always starts Docker Compose under docker/ and runs the app on host Bun
1589
+ with compose store drivers (Postgres/Redis). Regenerates client types on
1590
+ every save. Auto-runs \`oke db push\` when schema.ts changes (opt out:
1591
+ --no-db-push).
1592
+
1593
+ Optional \`-d\` / \`--docker\` filters which compose roles to start
1594
+ (e.g. \`-d store.sql,store.kv\`). Requires a running Docker daemon.
1595
+ Use \`oke test\` for PGLite / in-memory test posture (no Compose).
1651
1596
  `);
1652
1597
  return 0;
1598
+ } else if (a === "--local" || a === "-l") {
1599
+ console.error("oke dev: --local was removed — `oke dev` always uses Docker Compose");
1600
+ console.error("Use `oke test` for PGLite test posture.");
1601
+ return 1;
1653
1602
  }
1654
1603
  }
1655
1604
  const { code } = await runDev({
1656
- ...(local ? { local: true } : {}),
1657
1605
  ...(docker === undefined ? {} : { docker }),
1658
1606
  ...(noDbPush ? { noDbPush: true } : {}),
1659
1607
  entry,
@@ -11,9 +11,7 @@ import { join } from "node:path";
11
11
 
12
12
  const ROOT = join(import.meta.dir, "../..");
13
13
 
14
- /** Forbidden CLI flag, split so this file does not match itself. */
15
- const FORBIDDEN_STACK_FLAG = ["--", "stack"].join("");
16
- /** Forbidden compose env filename. */
14
+ /** Forbidden compose env filename (legacy soft-compat). */
17
15
  const FORBIDDEN_ENV_STACK = [".env.", "stack"].join("");
18
16
  /** Forbidden env var — name avoids the contiguous token so this file is clean. */
19
17
  const FORBIDDEN_LEGACY_ENV = ["OKE_", "STACK"].join("");
@@ -47,9 +45,8 @@ function assertZeroGitGrep(pattern: string, ignorePrefixes: readonly string[] =
47
45
  }
48
46
 
49
47
  describe("legacy stack-mode removal gate", () => {
50
- test("tracked tree has zero legacy docker-mode flag outside changelog", () => {
51
- assertZeroGitGrep(FORBIDDEN_STACK_FLAG, ["changelog.md:"]);
52
- });
48
+ // `--stack` is first-class again (Swarm `docker-stack.yml` layout). Legacy
49
+ // soft-compat was the env filename + vault helper surface — those stay forbidden.
53
50
 
54
51
  test("tracked tree has zero legacy compose env filename outside changelog", () => {
55
52
  assertZeroGitGrep(FORBIDDEN_ENV_STACK, ["changelog.md:"]);