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
@@ -0,0 +1,43 @@
1
+ /**
2
+ * OKE ASCII wordmark — same art as `oke dev` / {@link formatOkeWordmark}.
3
+ */
4
+
5
+ import { Box, Text } from "ink";
6
+ import type { ReactElement } from "react";
7
+ import { TUI_BRAND, TUI_MUTED } from "../theme.ts";
8
+
9
+ /** Plain wordmark lines (no ANSI) — matches `formatOkeWordmark`. */
10
+ export const OKE_WORDMARK_LINES: readonly string[] = [
11
+ " ██████╗ ██╗ ██╗███████╗",
12
+ " ██╔═══██╗██║ ██╔╝██╔════╝",
13
+ " ██║ ██║█████╔╝ █████╗ ",
14
+ " ██║ ██║██╔═██╗ ██╔══╝ ",
15
+ " ╚██████╔╝██║ ██╗███████╗",
16
+ " ╚═════╝ ╚═╝ ╚═╝╚══════╝",
17
+ ];
18
+
19
+ /** Props for {@link Wordmark}. */
20
+ export type WordmarkProps = {
21
+ readonly version?: string;
22
+ };
23
+
24
+ /**
25
+ * Green block-letter OKE + version.
26
+ *
27
+ * @param props - Optional pinned version
28
+ */
29
+ export function Wordmark(props: WordmarkProps): ReactElement {
30
+ const version = props.version ?? "0.10.3";
31
+ return (
32
+ <Box flexDirection="column">
33
+ {OKE_WORDMARK_LINES.map((line) => (
34
+ <Text key={line} bold color={TUI_BRAND}>
35
+ {line}
36
+ </Text>
37
+ ))}
38
+ <Text color={TUI_MUTED}>
39
+ {" "}v{version}
40
+ </Text>
41
+ </Box>
42
+ );
43
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared TUI format helpers.
3
+ */
4
+
5
+ /**
6
+ * Format ms as compact uptime (`4m 21s`).
7
+ *
8
+ * @param ms - Elapsed milliseconds
9
+ */
10
+ export function formatUptime(ms: number): string {
11
+ const s = Math.max(0, Math.floor(ms / 1000));
12
+ const m = Math.floor(s / 60);
13
+ const h = Math.floor(m / 60);
14
+ if (h > 0) return `${h}h ${m % 60}m`;
15
+ if (m > 0) return `${m}m ${s % 60}s`;
16
+ return `${s}s`;
17
+ }
18
+
19
+ /**
20
+ * Open a URL in the default browser (best-effort).
21
+ *
22
+ * @param url - http(s) URL
23
+ */
24
+ export async function openUrl(url: string): Promise<void> {
25
+ const platform = process.platform;
26
+ const cmd =
27
+ platform === "darwin"
28
+ ? ["open", url]
29
+ : platform === "win32"
30
+ ? ["cmd", "/c", "start", "", url]
31
+ : ["xdg-open", url];
32
+ try {
33
+ const proc = Bun.spawn(cmd, { stdout: "ignore", stderr: "ignore" });
34
+ await proc.exited;
35
+ } catch {
36
+ // ignore — TUI still shows the link
37
+ }
38
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mapDevControlInput } from "./keys.ts";
3
+
4
+ describe("mapDevControlInput", () => {
5
+ test("maps ? r q u x and aliases", () => {
6
+ expect(mapDevControlInput("?", {})).toBe("?");
7
+ expect(mapDevControlInput("h", {})).toBe("?");
8
+ expect(mapDevControlInput("r", {})).toBe("r");
9
+ expect(mapDevControlInput("q", {})).toBe("q");
10
+ expect(mapDevControlInput("u", {})).toBe("u");
11
+ expect(mapDevControlInput("x", {})).toBe("x");
12
+ expect(mapDevControlInput("c", {})).toBeNull();
13
+ });
14
+
15
+ test("maps Ctrl+C to quit", () => {
16
+ expect(mapDevControlInput("c", { ctrl: true })).toBe("q");
17
+ });
18
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared keyboard mapping for Ink `useInput` and pure tests.
3
+ * Parity with legacy `parseDevControlKey` (`? r q u x`).
4
+ */
5
+
6
+ /** Dev control action from a keypress. */
7
+ export type DevControlKey = "?" | "r" | "q" | "u" | "x";
8
+
9
+ /**
10
+ * Map Ink input (+ ctrl) to a control key.
11
+ *
12
+ * @param input - Character from useInput
13
+ * @param key - Ink key modifiers
14
+ */
15
+ export function mapDevControlInput(
16
+ input: string,
17
+ key: { readonly ctrl?: boolean },
18
+ ): DevControlKey | null {
19
+ if (key.ctrl && input === "c") return "q";
20
+ if (input === "?" || input === "h" || input === "H") return "?";
21
+ if (input === "q" || input === "Q") return "q";
22
+ if (input === "r" || input === "R") return "r";
23
+ if (input === "u" || input === "U") return "u";
24
+ if (input === "x" || input === "X") return "x";
25
+ return null;
26
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { clearTerminalScreen } from "../../term.ts";
3
+ import { canRenderTui, clearTerminalViewport } from "./launch.ts";
4
+
5
+ describe("canRenderTui", () => {
6
+ test("requires stdout.isTTY", () => {
7
+ expect(canRenderTui({ isTTY: true })).toBe(true);
8
+ expect(canRenderTui({ isTTY: false })).toBe(false);
9
+ expect(canRenderTui({})).toBe(false);
10
+ });
11
+ });
12
+
13
+ describe("clearTerminalViewport", () => {
14
+ test("writes the same clear sequence as oke dev", () => {
15
+ expect(clearTerminalScreen()).toContain("\u001b[2J");
16
+ expect(typeof clearTerminalViewport).toBe("function");
17
+ });
18
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Launch the interactive Ink TUI (dynamic-imported from the bin).
3
+ */
4
+
5
+ import { clearTerminalScreen } from "../../term.ts";
6
+ import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "../exit.ts";
7
+ import { formatOkeHelp } from "../registry.ts";
8
+
9
+ /**
10
+ * True when stdout can host an interactive Ink app.
11
+ *
12
+ * @param stream - stdout-like
13
+ */
14
+ export function canRenderTui(stream: { readonly isTTY?: boolean } = process.stdout): boolean {
15
+ return stream.isTTY === true;
16
+ }
17
+
18
+ /**
19
+ * Wipe the viewport so boot logs never sit above the Ink frame.
20
+ */
21
+ export function clearTerminalViewport(): void {
22
+ process.stdout.write(clearTerminalScreen());
23
+ }
24
+
25
+ /**
26
+ * Start the TUI. Call only after {@link canRenderTui} is true.
27
+ *
28
+ * @param cwd - Project root
29
+ */
30
+ export async function launchTui(cwd: string = process.cwd()): Promise<number> {
31
+ try {
32
+ clearTerminalViewport();
33
+ const React = await import("react");
34
+ const { render } = await import("ink");
35
+ const { OkeTuiApp } = await import("./App.tsx");
36
+ const instance = render(React.createElement(OkeTuiApp, { cwd }), {
37
+ exitOnCtrlC: false,
38
+ });
39
+ await instance.waitUntilExit();
40
+ return EXIT_OK;
41
+ } catch (err) {
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ const missing =
44
+ /Cannot find (module|package)|ERR_MODULE_NOT_FOUND|Cannot find package/i.test(msg) ||
45
+ msg.includes("'ink'") ||
46
+ msg.includes('"ink"');
47
+ if (missing) {
48
+ console.error("oke: interactive TUI requires optional deps `ink` and `react`.");
49
+ console.error(" bun add ink react");
50
+ console.error(" Or run `oke --help` / `oke <command>` without the TUI.");
51
+ console.log(formatOkeHelp());
52
+ return EXIT_USAGE;
53
+ }
54
+ console.error(`oke: TUI failed — ${msg}`);
55
+ return EXIT_RUNTIME;
56
+ }
57
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Landing panel — OKE wordmark, session, aligned services, quick actions.
3
+ */
4
+
5
+ import { Box, Text, useInput } from "ink";
6
+ import { useEffect, useState, type ReactElement } from "react";
7
+ import type { FooterStatus } from "../components/StatusBar.tsx";
8
+ import { StatusDot, statusWord } from "../components/StatusDot.tsx";
9
+ import { Wordmark } from "../components/Wordmark.tsx";
10
+ import { formatUptime, openUrl } from "../format.ts";
11
+ import { pollPortSnapshot, type PortSnapshot } from "../state/ports.ts";
12
+ import { TUI_ACCENT, TUI_MUTED, TUI_OK, TUI_WARN } from "../theme.ts";
13
+
14
+ /** Props for {@link DashboardPanel}. */
15
+ export type DashboardPanelProps = {
16
+ readonly cwd: string;
17
+ readonly active: boolean;
18
+ readonly onJumpPanel: (panelId: string) => void;
19
+ readonly onStatus: (status: FooterStatus) => void;
20
+ };
21
+
22
+ const ACTIONS = [
23
+ { id: "open", label: "[O]pen Console", key: "o" },
24
+ { id: "mcp", label: "[M]CP Docs", key: "m" },
25
+ { id: "dev", label: "[D]ev Panel", key: "d" },
26
+ ] as const;
27
+
28
+ /**
29
+ * Dashboard panel — polls ports every 1.5s.
30
+ *
31
+ * @param props - cwd + focus/actions
32
+ */
33
+ export function DashboardPanel(props: DashboardPanelProps): ReactElement {
34
+ const [snap, setSnap] = useState<PortSnapshot | null>(null);
35
+ const [startedAt, setStartedAt] = useState<number | null>(null);
36
+ const [now, setNow] = useState(Date.now());
37
+ const [selected, setSelected] = useState(0);
38
+
39
+ useEffect(() => {
40
+ let cancelled = false;
41
+ const tick = async (): Promise<void> => {
42
+ const next = await pollPortSnapshot(props.cwd);
43
+ if (cancelled) return;
44
+ setSnap(next);
45
+ if (next.ownership === "managed") {
46
+ setStartedAt((prev) => prev ?? Date.now());
47
+ } else {
48
+ setStartedAt(null);
49
+ }
50
+ setNow(Date.now());
51
+ };
52
+ void tick();
53
+ const id = setInterval(() => void tick(), 1500);
54
+ return () => {
55
+ cancelled = true;
56
+ clearInterval(id);
57
+ };
58
+ }, [props.cwd]);
59
+
60
+ // Prefer lock startedAt when available via re-read
61
+ useEffect(() => {
62
+ let cancelled = false;
63
+ void (async () => {
64
+ const { readDevSessionLock } = await import("../../dev-session-lock.ts");
65
+ const lock = await readDevSessionLock(props.cwd);
66
+ if (!cancelled && lock) {
67
+ const t = Date.parse(lock.startedAt);
68
+ if (Number.isFinite(t)) setStartedAt(t);
69
+ }
70
+ })();
71
+ return () => {
72
+ cancelled = true;
73
+ };
74
+ }, [props.cwd, snap?.ownership]);
75
+
76
+ useInput(
77
+ (input, key) => {
78
+ if (!props.active) return;
79
+ if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
80
+ if (key.downArrow) setSelected((i) => Math.min(ACTIONS.length - 1, i + 1));
81
+ const lower = input.toLowerCase();
82
+ if (lower === "o") void runAction("open");
83
+ else if (lower === "m") void runAction("mcp");
84
+ else if (lower === "d") void runAction("dev");
85
+ else if (key.return) {
86
+ const a = ACTIONS[selected];
87
+ if (a) void runAction(a.id);
88
+ }
89
+ },
90
+ { isActive: props.active },
91
+ );
92
+
93
+ const runAction = async (id: (typeof ACTIONS)[number]["id"]): Promise<void> => {
94
+ if (id === "dev") {
95
+ props.onJumpPanel("dev");
96
+ return;
97
+ }
98
+ const consoleSurf = snap?.surfaces.find((s) => s.id === "console");
99
+ const docsSurf = snap?.surfaces.find((s) => s.id === "docsMcp");
100
+ if (id === "open") {
101
+ const url = consoleSurf?.url ?? "http://127.0.0.1:6533";
102
+ props.onStatus({ kind: "running", label: `Opening ${url}…` });
103
+ await openUrl(url);
104
+ props.onStatus({ kind: "done", label: "Opened Console" });
105
+ return;
106
+ }
107
+ const url = docsSurf?.url ?? "http://127.0.0.1:6536";
108
+ props.onStatus({ kind: "running", label: `Opening ${url}…` });
109
+ await openUrl(url);
110
+ props.onStatus({ kind: "done", label: "Opened MCP Docs" });
111
+ };
112
+
113
+ const ownership = snap?.ownership ?? "stopped";
114
+ const ownershipLabel =
115
+ ownership === "managed"
116
+ ? "Managed"
117
+ : ownership === "external"
118
+ ? "Connected (external)"
119
+ : "Stopped";
120
+ const ownershipColor = ownership === "managed" || ownership === "external" ? TUI_OK : TUI_WARN;
121
+ const uptime = startedAt && ownership === "managed" ? formatUptime(now - startedAt) : null;
122
+
123
+ return (
124
+ <Box flexDirection="column" gap={1} flexGrow={1}>
125
+ <Box justifyContent="space-between" alignItems="flex-start">
126
+ <Wordmark version="0.10.3" />
127
+ </Box>
128
+
129
+ <Box flexDirection="column">
130
+ <Text bold color={TUI_ACCENT}>
131
+ Session
132
+ </Text>
133
+ <Box gap={1}>
134
+ <StatusDot
135
+ status={ownership === "managed" || ownership === "external" ? "ready" : "idle"}
136
+ />
137
+ <Text bold color={ownershipColor}>
138
+ {ownershipLabel}
139
+ </Text>
140
+ {uptime ? <Text color={TUI_MUTED}>· uptime {uptime}</Text> : null}
141
+ </Box>
142
+ {ownership === "external" ? (
143
+ <Text color={TUI_WARN}>Read-only — will not start a second stack</Text>
144
+ ) : null}
145
+ {ownership === "stopped" ? (
146
+ <Text color={TUI_MUTED}>No live oke dev — press [D] or open Dev panel</Text>
147
+ ) : null}
148
+ </Box>
149
+
150
+ <Box flexDirection="column" flexGrow={1}>
151
+ <Box>
152
+ <Box width={14}>
153
+ <Text bold color={TUI_ACCENT}>
154
+ Services
155
+ </Text>
156
+ </Box>
157
+ <Box width={8}>
158
+ <Text color={TUI_MUTED}>Port</Text>
159
+ </Box>
160
+ <Box width={10}>
161
+ <Text color={TUI_MUTED}>Status</Text>
162
+ </Box>
163
+ <Text color={TUI_MUTED}>URL</Text>
164
+ </Box>
165
+ <Text color={TUI_MUTED}>{"─".repeat(56)}</Text>
166
+ {(snap?.surfaces ?? []).map((s) => {
167
+ const sw = statusWord(s.status);
168
+ return (
169
+ <Box key={s.id}>
170
+ <Box width={14} gap={1}>
171
+ <StatusDot status={s.status} />
172
+ <Text>{s.label.padEnd(9)}</Text>
173
+ </Box>
174
+ <Box width={8}>
175
+ <Text color={TUI_ACCENT}>{`:${String(s.port).padEnd(5)}`}</Text>
176
+ </Box>
177
+ <Box width={10}>
178
+ <Text color={sw.color}>{sw.word}</Text>
179
+ </Box>
180
+ <Text color={TUI_MUTED}>{s.url}</Text>
181
+ </Box>
182
+ );
183
+ })}
184
+ {!snap ? <Text color={TUI_MUTED}>Probing ports…</Text> : null}
185
+ </Box>
186
+
187
+ <Box gap={2}>
188
+ {ACTIONS.map((a, i) => (
189
+ <Text
190
+ key={a.id}
191
+ bold={props.active && i === selected}
192
+ color={props.active && i === selected ? TUI_ACCENT : TUI_MUTED}
193
+ >
194
+ {props.active && i === selected ? "› " : " "}
195
+ {a.label}
196
+ </Text>
197
+ ))}
198
+ </Box>
199
+ </Box>
200
+ );
201
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Database panel — 3-column action cards, no nested footer.
3
+ */
4
+
5
+ import { Box, Text, useInput } from "ink";
6
+ import { useState, type ReactElement } from "react";
7
+ import { ConfirmDialog } from "../components/ConfirmDialog.tsx";
8
+ import type { FooterStatus } from "../components/StatusBar.tsx";
9
+ import { TUI_ACCENT, TUI_MUTED, TUI_WARN } from "../theme.ts";
10
+
11
+ /** Props for {@link DatabasePanel}. */
12
+ export type DatabasePanelProps = {
13
+ readonly cwd: string;
14
+ readonly active: boolean;
15
+ readonly onLog: (line: string) => void;
16
+ readonly onStatus: (status: FooterStatus) => void;
17
+ };
18
+
19
+ type DbAction = "push" | "generate" | "migrate" | "seed" | "studio";
20
+
21
+ type Card = {
22
+ readonly id: DbAction;
23
+ readonly key: string;
24
+ readonly title: string;
25
+ readonly blurb: string;
26
+ readonly confirm: boolean;
27
+ };
28
+
29
+ const CARDS: readonly Card[] = [
30
+ {
31
+ id: "push",
32
+ key: "P",
33
+ title: "Push",
34
+ blurb: "sync schema → live DB",
35
+ confirm: true,
36
+ },
37
+ {
38
+ id: "generate",
39
+ key: "G",
40
+ title: "Generate",
41
+ blurb: "write SQL under drizzle/",
42
+ confirm: false,
43
+ },
44
+ {
45
+ id: "migrate",
46
+ key: "M",
47
+ title: "Migrate",
48
+ blurb: "apply pending migrations",
49
+ confirm: true,
50
+ },
51
+ {
52
+ id: "seed",
53
+ key: "E",
54
+ title: "Seed",
55
+ blurb: "run defineSeed",
56
+ confirm: true,
57
+ },
58
+ {
59
+ id: "studio",
60
+ key: "T",
61
+ title: "Studio",
62
+ blurb: "drizzle-kit (browser)",
63
+ confirm: false,
64
+ },
65
+ ];
66
+
67
+ /**
68
+ * Database actions as a selectable card grid.
69
+ *
70
+ * @param props - cwd + log sink
71
+ */
72
+ export function DatabasePanel(props: DatabasePanelProps): ReactElement {
73
+ const [selected, setSelected] = useState(0);
74
+ const [pending, setPending] = useState<DbAction | null>(null);
75
+ const [busy, setBusy] = useState(false);
76
+
77
+ useInput(
78
+ (input, key) => {
79
+ if (!props.active || pending || busy) return;
80
+ if (key.leftArrow) setSelected((i) => Math.max(0, i - 1));
81
+ if (key.rightArrow) setSelected((i) => Math.min(CARDS.length - 1, i + 1));
82
+ if (key.upArrow) setSelected((i) => Math.max(0, i - 3));
83
+ if (key.downArrow) setSelected((i) => Math.min(CARDS.length - 1, i + 3));
84
+ const upper = input.toUpperCase();
85
+ const byKey = CARDS.findIndex((c) => c.key === upper);
86
+ if (byKey >= 0) {
87
+ setSelected(byKey);
88
+ const card = CARDS[byKey]!;
89
+ if (card.confirm) setPending(card.id);
90
+ else void run(card.id);
91
+ return;
92
+ }
93
+ if (key.return) {
94
+ const action = CARDS[selected];
95
+ if (!action) return;
96
+ if (action.confirm) setPending(action.id);
97
+ else void run(action.id);
98
+ }
99
+ },
100
+ { isActive: props.active && !pending && !busy },
101
+ );
102
+
103
+ const run = async (id: DbAction): Promise<void> => {
104
+ setBusy(true);
105
+ setPending(null);
106
+ props.onStatus({ kind: "running", label: `Running oke db ${id}…` });
107
+ props.onLog(`oke db ${id}\n`);
108
+ const prevCwd = process.cwd();
109
+ try {
110
+ process.chdir(props.cwd);
111
+ const { dbCli } = await import("../../db.ts");
112
+ const writes: string[] = [];
113
+ const origLog = console.log;
114
+ const origErr = console.error;
115
+ console.log = (...args: unknown[]) => {
116
+ writes.push(args.map(String).join(" ") + "\n");
117
+ };
118
+ console.error = (...args: unknown[]) => {
119
+ writes.push(args.map(String).join(" ") + "\n");
120
+ };
121
+ let code = 1;
122
+ try {
123
+ code = await dbCli([id, "--force"]);
124
+ } finally {
125
+ console.log = origLog;
126
+ console.error = origErr;
127
+ }
128
+ for (const w of writes) props.onLog(w);
129
+ props.onStatus({
130
+ kind: "done",
131
+ label: code === 0 ? `db ${id} ok` : `db ${id} exited ${code}`,
132
+ });
133
+ } catch (err) {
134
+ const m = err instanceof Error ? err.message : String(err);
135
+ props.onLog(`${m}\n`);
136
+ props.onStatus({ kind: "done", label: m });
137
+ } finally {
138
+ process.chdir(prevCwd);
139
+ setBusy(false);
140
+ }
141
+ };
142
+
143
+ if (pending) {
144
+ return (
145
+ <ConfirmDialog
146
+ message={`Run oke db ${pending}? This may change the live database.`}
147
+ onConfirm={() => void run(pending)}
148
+ onCancel={() => setPending(null)}
149
+ />
150
+ );
151
+ }
152
+
153
+ const row1 = CARDS.slice(0, 3);
154
+ const row2 = CARDS.slice(3);
155
+
156
+ return (
157
+ <Box flexDirection="column" gap={1} flexGrow={1}>
158
+ <Text bold color={TUI_ACCENT}>
159
+ Database
160
+ </Text>
161
+ <Text color={TUI_MUTED} dimColor>
162
+ Domain schema via drizzle-kit
163
+ </Text>
164
+ <Box gap={1}>
165
+ {row1.map((card, i) => (
166
+ <ActionCard key={card.id} card={card} selected={props.active && selected === i} />
167
+ ))}
168
+ </Box>
169
+ <Box gap={1}>
170
+ {row2.map((card, i) => (
171
+ <ActionCard key={card.id} card={card} selected={props.active && selected === i + 3} />
172
+ ))}
173
+ </Box>
174
+ <Text color={TUI_WARN}>! Push & Migrate modify the database — confirmation required</Text>
175
+ </Box>
176
+ );
177
+ }
178
+
179
+ /**
180
+ * One database action card.
181
+ */
182
+ function ActionCard(props: { readonly card: Card; readonly selected: boolean }): ReactElement {
183
+ const { card, selected } = props;
184
+ return (
185
+ <Box
186
+ flexDirection="column"
187
+ borderStyle="single"
188
+ borderColor={selected ? TUI_ACCENT : TUI_MUTED}
189
+ width="32%"
190
+ paddingX={1}
191
+ >
192
+ <Text bold color={selected ? TUI_ACCENT : undefined}>
193
+ {selected ? "› " : " "}[{card.key}] {card.title}
194
+ </Text>
195
+ <Text color={TUI_MUTED}>{card.blurb}</Text>
196
+ </Box>
197
+ );
198
+ }