okengine 0.9.0 → 0.10.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.
- package/AGENTS.md +1 -1
- package/manifest.v1.schema.json +9 -1
- package/package.json +3 -3
- package/site/content/docs/console/runs.mdx +10 -0
- package/site/content/docs/console/traces.mdx +4 -0
- package/site/content/docs/elements/ai.mdx +54 -32
- package/site/content/docs/elements/channel.mdx +105 -14
- package/site/content/docs/elements/clock.mdx +1 -0
- package/site/content/docs/elements/flow.mdx +42 -8
- package/site/content/docs/get-started/basic-usage.mdx +5 -1
- package/site/content/docs/get-started/installation.mdx +9 -5
- package/site/content/docs/meta.json +2 -0
- package/site/content/docs/plugins/otp.mdx +58 -46
- package/site/content/docs/providers/azure-redis.mdx +85 -0
- package/site/content/docs/providers/cockroachdb.mdx +86 -0
- package/site/content/docs/providers/digitalocean-caching.mdx +88 -0
- package/site/content/docs/providers/dragonfly-cloud.mdx +82 -0
- package/site/content/docs/providers/elasticache.mdx +92 -0
- package/site/content/docs/providers/index.mdx +101 -0
- package/site/content/docs/providers/memorystore.mdx +89 -0
- package/site/content/docs/providers/meta.json +20 -0
- package/site/content/docs/providers/neon.mdx +115 -0
- package/site/content/docs/providers/redis-cloud.mdx +93 -0
- package/site/content/docs/providers/supabase.mdx +117 -0
- package/site/content/docs/providers/upstash.mdx +91 -0
- package/site/content/docs/providers/yugabytedb.mdx +95 -0
- package/site/content/docs/recipes/caddy.mdx +128 -0
- package/site/content/docs/recipes/cockroachdb.mdx +127 -0
- package/site/content/docs/recipes/dragonfly.mdx +125 -0
- package/site/content/docs/recipes/index.mdx +146 -0
- package/site/content/docs/recipes/llama-cpp.mdx +153 -0
- package/site/content/docs/recipes/mailpit.mdx +136 -0
- package/site/content/docs/recipes/meilisearch.mdx +139 -0
- package/site/content/docs/recipes/meta.json +31 -0
- package/site/content/docs/recipes/ollama.mdx +142 -0
- package/site/content/docs/recipes/openbao.mdx +141 -0
- package/site/content/docs/recipes/pgdog.mdx +137 -0
- package/site/content/docs/recipes/postgres.mdx +136 -0
- package/site/content/docs/recipes/redis.mdx +130 -0
- package/site/content/docs/recipes/rustfs.mdx +148 -0
- package/site/content/docs/recipes/sglang.mdx +105 -0
- package/site/content/docs/recipes/supabase-docker.mdx +145 -0
- package/site/content/docs/recipes/timescale.mdx +129 -0
- package/site/content/docs/recipes/traefik.mdx +136 -0
- package/site/content/docs/recipes/valkey.mdx +121 -0
- package/site/content/docs/recipes/vllm.mdx +106 -0
- package/site/content/docs/recipes/yugabytedb.mdx +135 -0
- package/site/content/docs/reference/cli.md +42 -6
- package/site/content/docs/reference/configuration.mdx +5 -3
- package/site/content/docs/reference/environment-variables.mdx +12 -12
- package/site/content/docs/reference/fx.mdx +38 -4
- package/src/auth/otp-capability.ts +14 -14
- package/src/cli/ai-setup/ai-setup.test.ts +32 -12
- package/src/cli/ai-setup/apply.ts +65 -23
- package/src/cli/ai-setup/catalog.ts +1316 -35
- package/src/cli/ai-setup/detect-ollama.ts +47 -0
- package/src/cli/ai-setup/index.ts +44 -7
- package/src/cli/ai-setup/prompts.ts +430 -486
- package/src/cli/ai-setup/recommend.test.ts +150 -50
- package/src/cli/ai-setup/recommend.ts +118 -101
- package/src/cli/ask-seed.test.ts +68 -0
- package/src/cli/ask-seed.ts +79 -0
- package/src/cli/db-seed-redact.test.ts +25 -0
- package/src/cli/db-seed.ts +36 -7
- package/src/cli/db.test.ts +1 -1
- package/src/cli/db.ts +2 -2
- package/src/cli/dev-controls.test.ts +107 -0
- package/src/cli/dev-controls.ts +354 -0
- package/src/cli/dev-schema-sync.ts +12 -4
- package/src/cli/dev.test.ts +50 -30
- package/src/cli/dev.ts +607 -66
- package/src/cli/drizzle-env.test.ts +40 -6
- package/src/cli/drizzle-env.ts +22 -1
- package/src/cli/ensure-drizzle-config.ts +6 -4
- package/src/cli/hero-meta.test.ts +13 -1
- package/src/cli/hero-meta.ts +28 -6
- package/src/cli/index.ts +5 -0
- package/src/cli/registry.ts +24 -0
- package/src/cli/replay.test.ts +105 -0
- package/src/cli/replay.ts +206 -0
- package/src/compiler/extract.ts +3 -3
- package/src/config/index.ts +3 -3
- package/src/console/server/claim.ts +3 -1
- package/src/console/ui/dist/assets/{index-ClX0ZCe5.js → index-D-9KFce5.js} +2 -2
- package/src/console/ui/dist/assets/panel-overview-CW0CQEc2.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BxuyDI3x.js +1 -0
- package/src/console/ui/dist/assets/{panel-signals-Dsxdu_AR.js → panel-signals-BXofbj7v.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-hy7O8HOs.js → panel-store-B8mH-arU.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-CBiAe5go.js → panel-traces-DSA-ZWnf.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/overview/slo.ts +31 -3
- package/src/console/ui/overview/types.ts +6 -0
- package/src/console/ui/runs/errors.test.ts +53 -0
- package/src/console/ui/runs/errors.ts +88 -0
- package/src/console/ui/runs/index.ts +8 -0
- package/src/console/ui/runs/search.ts +16 -0
- package/src/console/ui/shell/panels/runs/RunsPanel.tsx +72 -1
- package/src/docker/ai-model-status.test.ts +101 -0
- package/src/docker/ai-model-status.ts +276 -0
- package/src/docker/compose-health.test.ts +115 -0
- package/src/docker/compose-health.ts +234 -0
- package/src/docker/compose.ts +24 -8
- package/src/docker/derive.ts +35 -9
- package/src/docker/docker.test.ts +304 -12
- package/src/docker/helpers.ts +56 -0
- package/src/docker/index.ts +52 -0
- package/src/docker/ollama-pull.ts +232 -0
- package/src/docker/recipes/cockroach.ts +24 -0
- package/src/docker/recipes/index.ts +32 -0
- package/src/docker/recipes/llama-cpp.ts +273 -0
- package/src/docker/recipes/ollama.ts +19 -23
- package/src/docker/recipes/pgdog.ts +4 -1
- package/src/docker/recipes/postgres.ts +5 -18
- package/src/docker/recipes/sglang.ts +55 -0
- package/src/docker/recipes/supabase.ts +21 -0
- package/src/docker/recipes/timescale.ts +18 -0
- package/src/docker/recipes/vllm.ts +44 -0
- package/src/docker/recipes/yugabyte.ts +23 -0
- package/src/docker/stack-id.test.ts +22 -0
- package/src/docker/stack-id.ts +38 -4
- package/src/docker/types.ts +15 -0
- package/src/drivers/ai-ollama.integration.test.ts +7 -4
- package/src/drivers/signal-engine.ts +10 -0
- package/src/drivers/signal-postgres.ts +24 -2
- package/src/drivers/signal-types.ts +10 -0
- package/src/elements/channel/fallback.test.ts +91 -0
- package/src/elements/channel/hard-bounce.test.ts +82 -0
- package/src/elements/channel/injection.test.ts +153 -0
- package/src/elements/channel/locale-ar.test.ts +102 -0
- package/src/elements/channel/otp-delivery.ts +5 -2
- package/src/elements/channel/receipts-ledger.test.ts +100 -0
- package/src/elements/channel/runtime.ts +24 -1
- package/src/elements/channel/test-helpers.ts +60 -0
- package/src/elements/channel/whatsapp-compliance.test.ts +124 -0
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/emit-drizzle.ts +43 -9
- package/src/elements/store/runtime.ts +1 -1
- package/src/elements/store/schema-decl.test.ts +2 -0
- package/src/elements/store/schema-decl.ts +15 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/index.ts +1 -0
- package/src/kernel/app.ts +118 -11
- package/src/kernel/boot-bind/signal.ts +6 -2
- package/src/kernel/boot-bind/store.ts +2 -1
- package/src/kernel/boot.test.ts +6 -5
- package/src/kernel/boot.ts +8 -3
- package/src/kernel/compensate.test.ts +131 -0
- package/src/kernel/correlation.test.ts +108 -0
- package/src/kernel/effect-timing.test.ts +82 -0
- package/src/kernel/flow.ts +38 -0
- package/src/kernel/fx-runs.test.ts +49 -0
- package/src/kernel/fx.test.ts +14 -0
- package/src/kernel/fx.ts +111 -1
- package/src/manifest/types.ts +8 -2
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +3 -3
- package/src/plugins/auth-methods.test.ts +1 -1
- package/src/plugins/index.ts +2 -2
- package/src/plugins/otp.test.ts +21 -19
- package/src/plugins/otp.ts +36 -34
- package/src/plugins/taqnyat.live.test.ts +3 -3
- package/src/runs/collect.ts +3 -0
- package/src/runs/export-otlp.ts +64 -0
- package/src/runs/index.ts +16 -0
- package/src/runs/parquet.ts +9 -0
- package/src/runs/types.ts +5 -0
- package/src/runs/window.test.ts +68 -0
- package/src/runs/window.ts +148 -0
- package/src/runtime/boot-warn.test.ts +45 -0
- package/src/runtime/boot-warn.ts +17 -0
- package/src/runtime/dev-request-log.ts +5 -5
- package/src/term.test.ts +95 -10
- package/src/term.ts +369 -14
- package/src/console/ui/dist/assets/panel-overview-DONDzBd2.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-D5zf-D9c.js +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "vLLM"
|
|
3
|
+
description: "Production-tier self-hosted inference — OpenAI-compatible GPU serving when you run inference yourself."
|
|
4
|
+
icon: "Cpu"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
vLLM is for **multi-user / production concurrency on real GPU hardware** when
|
|
9
|
+
you choose to self-host inference instead of a managed provider. Driver id stays
|
|
10
|
+
`openai-compatible`. Most apps should prefer Anthropic / OpenAI / … in prod.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Pin an explicit image tag (never `latest`), publish only on loopback, and treat this as “I run
|
|
14
|
+
production inference myself” — not the default create path.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
<Steps>
|
|
20
|
+
|
|
21
|
+
<Step>
|
|
22
|
+
### Pin driver + image
|
|
23
|
+
|
|
24
|
+
```typescript title="oke.config.ts"
|
|
25
|
+
drivers: {
|
|
26
|
+
ai: { docker: "openai-compatible", prod: "openai-compatible", test: "mock" },
|
|
27
|
+
},
|
|
28
|
+
images: {
|
|
29
|
+
ai: "vllm/vllm-openai:v0.26.0", // never :latest
|
|
30
|
+
},
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or `oke ai setup --provider vllm --yes`.
|
|
34
|
+
|
|
35
|
+
</Step>
|
|
36
|
+
|
|
37
|
+
<Step>
|
|
38
|
+
### Env + GPU
|
|
39
|
+
|
|
40
|
+
```bash title="docker/.env.docker"
|
|
41
|
+
OKE_AI_URL=http://127.0.0.1:8000/v1
|
|
42
|
+
OKE_AI_MODEL=Qwen/Qwen3-0.6B
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The recipe sets `ipc: host` and an NVIDIA device reservation. Host needs the
|
|
46
|
+
NVIDIA Container Toolkit.
|
|
47
|
+
|
|
48
|
+
</Step>
|
|
49
|
+
|
|
50
|
+
<Step>
|
|
51
|
+
### Decide deliberately
|
|
52
|
+
|
|
53
|
+
| Path | When |
|
|
54
|
+
| ------------------------------------ | -------------------------------------------- |
|
|
55
|
+
| Managed provider | Default for most production apps |
|
|
56
|
+
| **vLLM** (this recipe) | Self-hosted multi-user GPU serving |
|
|
57
|
+
| [SGLang](/docs/recipes/sglang) | Self-hosted structured / agent GPU workloads |
|
|
58
|
+
| [llama.cpp](/docs/recipes/llama-cpp) | Single-user / dev (default local) |
|
|
59
|
+
|
|
60
|
+
</Step>
|
|
61
|
+
|
|
62
|
+
</Steps>
|
|
63
|
+
|
|
64
|
+
## Security
|
|
65
|
+
|
|
66
|
+
| Control | Requirement |
|
|
67
|
+
| ------------ | --------------------------------------------- |
|
|
68
|
+
| Host publish | `127.0.0.1:8000:8000` only — never `0.0.0.0` |
|
|
69
|
+
| Image pin | Explicit version tag — never `latest` |
|
|
70
|
+
| Model source | Trusted Hugging Face repos / your own weights |
|
|
71
|
+
|
|
72
|
+
## What the recipe configures
|
|
73
|
+
|
|
74
|
+
| Field | Value |
|
|
75
|
+
| -------------- | ----------------------------------------------------- |
|
|
76
|
+
| Container port | `8000` |
|
|
77
|
+
| Host publish | `127.0.0.1:8000:8000` |
|
|
78
|
+
| `ipc` | `host` |
|
|
79
|
+
| GPU | `deploy.resources.reservations.devices` (nvidia, all) |
|
|
80
|
+
| Healthcheck | `GET /health`, long start period for model load |
|
|
81
|
+
| Connection URL | `http://host:8000/v1` |
|
|
82
|
+
|
|
83
|
+
## Troubleshooting
|
|
84
|
+
|
|
85
|
+
<Accordions>
|
|
86
|
+
<Accordion title="GPU not visible / container exits">
|
|
87
|
+
|
|
88
|
+
Install the NVIDIA Container Toolkit on the host. CPU-only hosts should use
|
|
89
|
+
[llama.cpp](/docs/recipes/llama-cpp) or [Ollama](/docs/recipes/ollama) instead.
|
|
90
|
+
|
|
91
|
+
</Accordion>
|
|
92
|
+
</Accordions>
|
|
93
|
+
|
|
94
|
+
## Learn more
|
|
95
|
+
|
|
96
|
+
- [llama.cpp](/docs/recipes/llama-cpp) — default local AI
|
|
97
|
+
- [SGLang](/docs/recipes/sglang) — structured / agent GPU path
|
|
98
|
+
- [AI](/docs/elements/ai) — drivers and `fx.ask`
|
|
99
|
+
|
|
100
|
+
## Next
|
|
101
|
+
|
|
102
|
+
<Cards>
|
|
103
|
+
<Card title="SGLang" description="Agent / structured GPU serving." href="/docs/recipes/sglang" />
|
|
104
|
+
<Card title="llama.cpp" description="Default local AI." href="/docs/recipes/llama-cpp" />
|
|
105
|
+
<Card title="AI" description="Prompts and guardrails." href="/docs/elements/ai" />
|
|
106
|
+
</Cards>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "YugabyteDB"
|
|
3
|
+
description: "Self-hosted single-node YSQL — YSQL_* env, port 5433, driver stays postgres."
|
|
4
|
+
icon: "Database"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
YugabyteDB's **YSQL** API speaks Postgres wire. Pin `yugabytedb/yugabyte` as
|
|
9
|
+
`store.sql` and `oke docker` derives a single-node `yugabyted` service —
|
|
10
|
+
credentials, healthcheck, and `DATABASE_URL`. Driver id stays `postgres`.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Use the YSQL API only — never treat YCQL as `store.sql`. Vendor choice lives in
|
|
14
|
+
`images["store.sql"]`; host maps `:5432` → container `:5433`.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
<Steps>
|
|
20
|
+
|
|
21
|
+
<Step>
|
|
22
|
+
### Pin the image
|
|
23
|
+
|
|
24
|
+
```typescript title="oke.config.ts"
|
|
25
|
+
images: {
|
|
26
|
+
"store.sql": "yugabytedb/yugabyte:2025.1.0.0-b100", // pin a real tag
|
|
27
|
+
},
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step>
|
|
31
|
+
|
|
32
|
+
<Step>
|
|
33
|
+
### Bring the stack up
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
oke dev --docker
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`oke docker` injects `YSQL_USER` / `YSQL_PASSWORD` / `YSQL_DB` from `OKE_STORE_SQL_*`
|
|
40
|
+
(password presence turns authentication on), runs
|
|
41
|
+
`bin/yugabyted start --background=false`, and publishes YSQL on host `:5432`.
|
|
42
|
+
|
|
43
|
+
</Step>
|
|
44
|
+
|
|
45
|
+
<Step>
|
|
46
|
+
### Connect
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
echo "$DATABASE_URL"
|
|
50
|
+
# postgres://oke:…@127.0.0.1:5432/oke
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
First healthy state is slower than plain Postgres — the healthcheck allows a long
|
|
54
|
+
start period.
|
|
55
|
+
|
|
56
|
+
</Step>
|
|
57
|
+
|
|
58
|
+
</Steps>
|
|
59
|
+
|
|
60
|
+
## Required env
|
|
61
|
+
|
|
62
|
+
| Variable | Who sets it | Meaning |
|
|
63
|
+
| ------------------------ | ---------------------- | -------------------------------- |
|
|
64
|
+
| `OKE_STORE_SQL_USER` | `oke docker` → compose | → `YSQL_USER` |
|
|
65
|
+
| `OKE_STORE_SQL_PASSWORD` | `oke docker` → compose | → `YSQL_PASSWORD` (enables auth) |
|
|
66
|
+
| `OKE_STORE_SQL_DB` | `oke docker` → compose | → `YSQL_DB` |
|
|
67
|
+
| `DATABASE_URL` | stack env for the app | Host `:5432` → container `:5433` |
|
|
68
|
+
|
|
69
|
+
## Data and backup
|
|
70
|
+
|
|
71
|
+
| Path | What lives there |
|
|
72
|
+
| ------------------------ | ---------------------------------------------------- |
|
|
73
|
+
| `/home/yugabyte/yb_data` | Named volume `store-sql-data` — `yugabyted` base dir |
|
|
74
|
+
|
|
75
|
+
**Backup means:** volume backup of `store-sql-data`, or Yugabyte's backup tooling.
|
|
76
|
+
Losing that volume loses the universe data.
|
|
77
|
+
|
|
78
|
+
## Production note
|
|
79
|
+
|
|
80
|
+
Single-node `yugabyted` is for local and small self-hosted use. For managed Aeon
|
|
81
|
+
clusters, use the [YugabyteDB provider](/docs/providers/yugabytedb). Pin a concrete
|
|
82
|
+
tag; the image is large and cold-start is slower than Postgres.
|
|
83
|
+
|
|
84
|
+
## What the recipe configures
|
|
85
|
+
|
|
86
|
+
| Field | Value |
|
|
87
|
+
| -------------- | ----------------------------------------------------- |
|
|
88
|
+
| Container port | `5433` (host publishes `5432`) |
|
|
89
|
+
| Command | `bin/yugabyted start --base_dir=… --background=false` |
|
|
90
|
+
| Healthcheck | `ysqlsh … SELECT 1` (90s `start_period`) |
|
|
91
|
+
| Connection URL | `postgres://user:pass@host:5432/db` |
|
|
92
|
+
|
|
93
|
+
## Troubleshooting
|
|
94
|
+
|
|
95
|
+
<Accordions>
|
|
96
|
+
<Accordion title="store-sql unhealthy for a long time">
|
|
97
|
+
|
|
98
|
+
Yugabyte cold-start is slow. Wait through the healthcheck `start_period`, then read
|
|
99
|
+
`docker compose … logs store-sql`.
|
|
100
|
+
|
|
101
|
+
macOS AirPlay on `:7000` can conflict with Yugabyte's master UI — stop AirPlay
|
|
102
|
+
Receiver or remap that port in `compose.override.yml` if you need the UI.
|
|
103
|
+
|
|
104
|
+
</Accordion>
|
|
105
|
+
<Accordion title="password authentication failed">
|
|
106
|
+
|
|
107
|
+
`YSQL_PASSWORD` must match `DATABASE_URL`. Regenerate `docker/.env.docker` with
|
|
108
|
+
`oke dev --docker`. Auth is off only when `YSQL_PASSWORD` is unset — this recipe
|
|
109
|
+
always sets it.
|
|
110
|
+
|
|
111
|
+
</Accordion>
|
|
112
|
+
</Accordions>
|
|
113
|
+
|
|
114
|
+
## Learn more
|
|
115
|
+
|
|
116
|
+
- [YugabyteDB (provider)](/docs/providers/yugabytedb) — managed Aeon Connect flow
|
|
117
|
+
- [CockroachDB](/docs/recipes/cockroachdb) — self-hosted distributed peer
|
|
118
|
+
- [Postgres](/docs/recipes/postgres) — default SQL recipe
|
|
119
|
+
- [Store · SQL](/docs/elements/store#sql) — schema workflows
|
|
120
|
+
|
|
121
|
+
## Next
|
|
122
|
+
|
|
123
|
+
<Cards>
|
|
124
|
+
<Card
|
|
125
|
+
title="YugabyteDB Aeon"
|
|
126
|
+
description="Managed cloud alternative."
|
|
127
|
+
href="/docs/providers/yugabytedb"
|
|
128
|
+
/>
|
|
129
|
+
<Card
|
|
130
|
+
title="CockroachDB"
|
|
131
|
+
description="Self-hosted Cockroach recipe."
|
|
132
|
+
href="/docs/recipes/cockroachdb"
|
|
133
|
+
/>
|
|
134
|
+
<Card title="Postgres" description="The default store.sql image." href="/docs/recipes/postgres" />
|
|
135
|
+
</Cards>
|
|
@@ -45,14 +45,44 @@ oke build --target edge # < 15 kB kernel profile
|
|
|
45
45
|
oke eval # run prompt eval sets; fails CI on regression
|
|
46
46
|
oke ai setup # configure AI driver + models (TTY wizard or flags)
|
|
47
47
|
oke branch prod --at "yesterday" # fork journaled state into a sandbox
|
|
48
|
+
oke replay --request-id <id> # re-invoke a past Flow from a Runs WideEvent (dry-run if send/ask)
|
|
48
49
|
oke privacy erase --subject <id> # crypto-shredding: deletes the key, not the terabytes
|
|
49
50
|
oke upgrade # run codemods for a breaking change, print the diff
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
`oke replay` loads a Runs WideEvent by id and re-enters the Flow with stored
|
|
54
|
+
input. Defaults to dry-run when the ledger has `send` / `ask` (pass `--live`
|
|
55
|
+
to override). Needs a build that persisted `WideEvent.input`.
|
|
56
|
+
|
|
52
57
|
In development, app, Console, MCP, and docs MCP prefer the canonical ports and move upward
|
|
53
58
|
when occupied. Docker infrastructure uses stable per-project offsets in disjoint ranges for
|
|
54
59
|
each built-in service, preventing one service's offset port from overlapping another's.
|
|
55
60
|
|
|
61
|
+
`oke dev` boot chrome prints immediately (wordmark + Starting + profile), then
|
|
62
|
+
streams background work (compose up, vault, per-service health, AI probe) in a
|
|
63
|
+
live progress pane — no blank multi-second wait. That pane clears before the
|
|
64
|
+
elements + Docker board. Status ● is green ready · yellow pending/loading ·
|
|
65
|
+
red error · dim idle. Compose health keeps polling (`docker compose ps -a`)
|
|
66
|
+
so ● updates live if you stop a container; AI ● tracks model phase while the
|
|
67
|
+
AI container is up. Boot does not wait for the model to become ready.
|
|
68
|
+
|
|
69
|
+
On a TTY with `-d`, keyboard controls stay active under Logs. Status ● for
|
|
70
|
+
compose/AI live on the board **above** Logs (not as log lines). Press `c` to
|
|
71
|
+
clear the log pane and reprint the latest board. `l` / `?` also refresh first
|
|
72
|
+
so the services/help panel is not mixed into request logs.
|
|
73
|
+
|
|
74
|
+
| Key | Action |
|
|
75
|
+
| ---------------------------- | ----------------------------------- |
|
|
76
|
+
| `?` | help |
|
|
77
|
+
| `c` | refresh — clear logs, show latest ● |
|
|
78
|
+
| `q` | quit `oke dev` |
|
|
79
|
+
| `l` | list docker services |
|
|
80
|
+
| `u` / `x` | compose up / stop (whole stack) |
|
|
81
|
+
| `1`–`9` then `u` / `x` / `r` | start / stop / restart one service |
|
|
82
|
+
|
|
83
|
+
Compose stays quiet unless it fails. Process-local boot notices print once under
|
|
84
|
+
the hero (not once per process).
|
|
85
|
+
|
|
56
86
|
### create-oke
|
|
57
87
|
|
|
58
88
|
```bash
|
|
@@ -66,9 +96,11 @@ bunx create-oke@latest my-app --no-ai
|
|
|
66
96
|
|
|
67
97
|
On a TTY: pick **standard** or **advanced**, then recommended defaults or
|
|
68
98
|
customize. Customize asks **local** or **docker** first, walks facets for that
|
|
69
|
-
side only
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
side only (including `store.index` with a `none` opt-out — no separate enable
|
|
100
|
+
prompt), then whether to customize the other (defaults if no). **AI setup**
|
|
101
|
+
offers Recommended (llama.cpp) · Customize · Off. Writes user-global
|
|
102
|
+
`~/.oke/create-defaults.json` (reuse only when `template` matches). Scaffold
|
|
103
|
+
writes `.oke/mode` from the primary side. Non-TTY / `--yes` / explicit
|
|
72
104
|
`--template` never prompt.
|
|
73
105
|
|
|
74
106
|
### Additional commands
|
|
@@ -79,8 +111,9 @@ oke doctor --json # -j JSON on stdout; hints on stderr (agents /
|
|
|
79
111
|
oke stack --json # -j
|
|
80
112
|
oke images list # recipe · image · tag · digest · size (--json|-j)
|
|
81
113
|
oke gates list # Module:Action catalogue (--json|-j)
|
|
82
|
-
oke ai setup --provider
|
|
83
|
-
|
|
114
|
+
oke ai setup --provider llama-cpp --yes # default local AI
|
|
115
|
+
oke ai setup --provider ollama --yes # fully supported alternative
|
|
116
|
+
# TTY: llama.cpp / Ollama → banner · tiers · manual; vLLM / SGLang / cloud → model
|
|
84
117
|
```
|
|
85
118
|
|
|
86
119
|
### Shell completion
|
|
@@ -115,7 +148,10 @@ Long form is canonical in docs; short form is convenience only. Shared letters f
|
|
|
115
148
|
| `--diff` | | `doctor` |
|
|
116
149
|
| `--json` | `-j` | `doctor`, `stack`, `images list`, `gates list` |
|
|
117
150
|
| `--manifest` | `-m` | most Manifest readers |
|
|
118
|
-
| `--entry` | `-e` | `dev`, `start`, `build`
|
|
151
|
+
| `--entry` | `-e` | `dev`, `start`, `build`, `replay` |
|
|
152
|
+
| `--request-id` | `-r` | `replay` |
|
|
153
|
+
| `--dry-run` | | `replay` |
|
|
154
|
+
| `--live` | | `replay` |
|
|
119
155
|
| `--out` / `--outdir` | `-o` | writers |
|
|
120
156
|
| `--subject` | `-s` | `privacy erase` |
|
|
121
157
|
| `--before` | `-b` | `doctor --diff` |
|
|
@@ -82,7 +82,7 @@ images: {
|
|
|
82
82
|
"store.files": "rustfs/rustfs:1.0.0-beta.11",
|
|
83
83
|
"channel.email": "axllent/mailpit:v1.22.3",
|
|
84
84
|
vault: "openbao/openbao:2.6.1",
|
|
85
|
-
ai: "ollama/ollama:
|
|
85
|
+
ai: "ghcr.io/ggml-org/llama.cpp:server-b10290", // default; or ollama/ollama:0.32.6
|
|
86
86
|
// opt-in TLS edge — omit for the default (app publishes 6530):
|
|
87
87
|
// proxy: "caddy:2-alpine", // simplest automatic HTTPS
|
|
88
88
|
// proxy: "traefik:v3.3", // label auto-discovery for --scale app=N
|
|
@@ -96,7 +96,9 @@ When both `store.sql` and `pgdog` are pinned, `DATABASE_URL` points at PgDog
|
|
|
96
96
|
For `store.kv`, pin Redis (default), Valkey, or Dragonfly — driver id stays
|
|
97
97
|
`redis`, same `REDIS_URL`. Image table + licenses: [Store · KV](/docs/elements/store#kv).
|
|
98
98
|
|
|
99
|
-
For `ai`,
|
|
99
|
+
For `ai`, default is [llama.cpp](/docs/recipes/llama-cpp) (`OKE_AI_URL` →
|
|
100
|
+
`/v1`, curated Docker Hub `ai/` model). Ollama / vLLM / SGLang are explicit
|
|
101
|
+
alternatives — see [Recipes · AI](/docs/recipes#ai-local--self-hosted).
|
|
100
102
|
|
|
101
103
|
For `proxy`, pin **either** Caddy or Traefik — never both. Opt-in only; see
|
|
102
104
|
[Reverse proxy](/docs/deployment/reverse-proxy).
|
|
@@ -165,7 +167,7 @@ Domain schema sync for `oke db push | generate | migrate` (Drizzle). Unrelated t
|
|
|
165
167
|
|
|
166
168
|
| Option | Default | Surface |
|
|
167
169
|
| --------- | ------- | ------- |
|
|
168
|
-
| `app` | `6530` |
|
|
170
|
+
| `app` | `6530` | Backend |
|
|
169
171
|
| `console` | `6533` | Console |
|
|
170
172
|
| `mcp` | `6535` | MCP |
|
|
171
173
|
|
|
@@ -109,18 +109,18 @@ provider's real credentials — credentials alone never send.
|
|
|
109
109
|
|
|
110
110
|
## AI providers
|
|
111
111
|
|
|
112
|
-
| Variable | Used for
|
|
113
|
-
| --------------------- |
|
|
114
|
-
| `ANTHROPIC_API_KEY` | `anthropic` driver credential
|
|
115
|
-
| `ANTHROPIC_MODEL` | Model override for the anthropic driver
|
|
116
|
-
| `OPENAI_API_KEY` | `openai-compatible` driver credential
|
|
117
|
-
| `OPENAI_BASE_URL` | `openai-compatible` base URL
|
|
118
|
-
| `OKE_AI_DRIVER` | Force the AI driver id (honoured in docker mode)
|
|
119
|
-
| `OKE_AI_URL` |
|
|
120
|
-
| `OKE_AI_MODEL` | Default model
|
|
121
|
-
| `OKE_AI_VISION_MODEL` | Vision model id written by `oke ai setup` (logical `ai.model("vision")`)
|
|
122
|
-
| `OKE_AI_EMBED_MODEL` | Embedding model id written by `oke ai setup`
|
|
123
|
-
| `OLLAMA_HOST` | Alternate Ollama host (`host:port` or URL) when `OKE_AI_URL` is unset
|
|
112
|
+
| Variable | Used for | Default when unset |
|
|
113
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
114
|
+
| `ANTHROPIC_API_KEY` | `anthropic` driver credential | — |
|
|
115
|
+
| `ANTHROPIC_MODEL` | Model override for the anthropic driver | — |
|
|
116
|
+
| `OPENAI_API_KEY` | `openai-compatible` driver credential | — |
|
|
117
|
+
| `OPENAI_BASE_URL` | `openai-compatible` base URL | OpenAI cloud |
|
|
118
|
+
| `OKE_AI_DRIVER` | Force the AI driver id (honoured in docker mode) | config map |
|
|
119
|
+
| `OKE_AI_URL` | Local / openai-compatible base URL | `http://127.0.0.1:8080/v1` (llama.cpp) · `http://127.0.0.1:11434` (ollama) |
|
|
120
|
+
| `OKE_AI_MODEL` | Default model id — Docker Hub `ai/` for llama.cpp; Ollama library tag for ollama; HF id for vLLM/SGLang | `smollm2` (llama.cpp) / `qwen3.5:9b` (ollama) |
|
|
121
|
+
| `OKE_AI_VISION_MODEL` | Vision model id written by `oke ai setup` (logical `ai.model("vision")`) | — |
|
|
122
|
+
| `OKE_AI_EMBED_MODEL` | Embedding model id written by `oke ai setup` | — |
|
|
123
|
+
| `OLLAMA_HOST` | Alternate Ollama host (`host:port` or URL) when `OKE_AI_URL` is unset | — |
|
|
124
124
|
|
|
125
125
|
## Framework behavior
|
|
126
126
|
|
|
@@ -33,9 +33,42 @@ See [Store](/docs/elements/store) for the query-builder surface.
|
|
|
33
33
|
|
|
34
34
|
## Signals
|
|
35
35
|
|
|
36
|
-
| Signature | Records | Notes
|
|
37
|
-
| ------------------------------------- | ------- |
|
|
38
|
-
| `fx.emit(signal, payload?, { key? })` | `emit` | Commits the signal outbox when the call resolves; optional `key` serializes `once` per key; throws **OKE1042** (orphan) or **OKE1043** (schema) |
|
|
36
|
+
| Signature | Records | Notes |
|
|
37
|
+
| ------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `fx.emit(signal, payload?, { key? })` | `emit` | Commits the signal outbox when the call resolves; optional `key` serializes `once` per key; stamps producer run id as `parentRunId` for trace chains; throws **OKE1042** (orphan) or **OKE1043** (schema) |
|
|
39
|
+
|
|
40
|
+
## Runs (observability read)
|
|
41
|
+
|
|
42
|
+
Declare `effects: { reads: ["runs"] }`. Powers native SLO checkers (Clock + Channel) without a parallel metrics API.
|
|
43
|
+
|
|
44
|
+
| Signature | Records | Returns |
|
|
45
|
+
| ---------------------------------------- | ------------- | --------------------------------------------- |
|
|
46
|
+
| `fx.runs.query(sql)` | `read` `runs` | SQL rows (`FROM runs` on files/memory) |
|
|
47
|
+
| `fx.runs.all()` | `read` `runs` | All wide events |
|
|
48
|
+
| `fx.runs.window(flow, windowMs?)` | `read` `runs` | Rolling P50/P95/P99 + error rate (default 5m) |
|
|
49
|
+
| `fx.runs.checkSlo(flow, slo, windowMs?)` | `read` `runs` | Availability / latency breaches |
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
on(
|
|
53
|
+
every("5m"),
|
|
54
|
+
flow({
|
|
55
|
+
name: "ops.slo-check",
|
|
56
|
+
effects: { reads: ["runs"], sends: ["slo-alert"] },
|
|
57
|
+
do: async (_, fx) => {
|
|
58
|
+
const breaches = await fx.runs.checkSlo(
|
|
59
|
+
"checkout.create",
|
|
60
|
+
{ availability: "99.9%", latency: { p95: "200ms" } },
|
|
61
|
+
5 * 60_000,
|
|
62
|
+
);
|
|
63
|
+
if (breaches.length === 0) return;
|
|
64
|
+
await fx.send(sloAlert, {
|
|
65
|
+
to: "oncall@example.com",
|
|
66
|
+
data: { flow: "checkout.create", count: breaches.length },
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
```
|
|
39
72
|
|
|
40
73
|
## Flows
|
|
41
74
|
|
|
@@ -228,7 +261,8 @@ must log who started the call chain.
|
|
|
228
261
|
|
|
229
262
|
<Callout title="No fx.metric">
|
|
230
263
|
Investigated and declined. [Runs](/docs/console/runs) already own per-invocation observability as
|
|
231
|
-
wide events
|
|
264
|
+
wide events. Native alerting is `fx.runs` + Clock + Channel — not a second counter/gauge API.
|
|
265
|
+
Optional OTLP export for existing Grafana/Datadog stacks is additive and never required.
|
|
232
266
|
</Callout>
|
|
233
267
|
|
|
234
268
|
## Learn more
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fail-loud capability checks for the otp() plugin (
|
|
2
|
+
* Fail-loud capability checks for the otp() plugin (provider / app mode).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ChannelDriver, SmsOtpTransport } from "../drivers/channel-types.ts";
|
|
@@ -8,12 +8,12 @@ import type { OtpChannel } from "./verification.ts";
|
|
|
8
8
|
/** Config snapshot stored on the otp plugin for boot-time assertion. */
|
|
9
9
|
export interface OtpPluginConfig {
|
|
10
10
|
readonly method: "otp";
|
|
11
|
-
readonly
|
|
11
|
+
readonly mode: "provider" | "app";
|
|
12
12
|
readonly channels?: readonly OtpChannel[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Whether an SMS transport exposes provider
|
|
16
|
+
* Whether an SMS transport exposes provider OTP via Verify (structural).
|
|
17
17
|
*
|
|
18
18
|
* @param t - Candidate transport
|
|
19
19
|
*/
|
|
@@ -58,38 +58,38 @@ export function driverCoversMedium(drivers: readonly ChannelDriver[], medium: Ot
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Assert
|
|
61
|
+
* Assert provider mode: a Verify-capable SMS driver must be bound.
|
|
62
62
|
*
|
|
63
63
|
* @param drivers - Bound channel drivers
|
|
64
64
|
*/
|
|
65
|
-
export function
|
|
65
|
+
export function assertOtpProviderModeCapability(drivers: readonly ChannelDriver[]): void {
|
|
66
66
|
if (findOtpSmsDriver(drivers)) return;
|
|
67
67
|
const sms = drivers.filter((d) => d.smsTransport);
|
|
68
68
|
if (sms.length === 0) {
|
|
69
69
|
throw new Error(
|
|
70
|
-
'otp({
|
|
70
|
+
'otp({ mode: "provider" }): no SMS driver with sendOtp/verifyOtp bound — set drivers.channel.sms to a Verify-capable driver (e.g. "taqnyat"), or switch to otp({ mode: "app", channels: [...] })',
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
const id = sms[0]?.id ?? "unknown";
|
|
74
74
|
throw new Error(
|
|
75
|
-
`otp({
|
|
75
|
+
`otp({ mode: "provider" }): SMS driver "${id}" does not support provider OTP — bind a Verify-capable driver (e.g. taqnyat), or switch to otp({ mode: "app", channels: [...] })`,
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Assert
|
|
80
|
+
* Assert app mode: every declared channel has a deliverable driver.
|
|
81
81
|
*
|
|
82
82
|
* @param drivers - Bound channel drivers
|
|
83
83
|
* @param channels - Declared channel order
|
|
84
84
|
*/
|
|
85
|
-
export function
|
|
85
|
+
export function assertOtpAppModeChannels(
|
|
86
86
|
drivers: readonly ChannelDriver[],
|
|
87
87
|
channels: readonly OtpChannel[],
|
|
88
88
|
): void {
|
|
89
89
|
for (const ch of channels) {
|
|
90
90
|
if (!driverCoversMedium(drivers, ch)) {
|
|
91
91
|
throw new Error(
|
|
92
|
-
`otp({
|
|
92
|
+
`otp({ mode: "app" }): no channel driver covers "${ch}" — configure drivers.channel.${ch === "email" ? "email" : ch === "sms" ? "sms" : "whatsapp"}`,
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -108,12 +108,12 @@ export function assertOtpPluginCapability(
|
|
|
108
108
|
if (!config || typeof config !== "object") return;
|
|
109
109
|
const c = config as Partial<OtpPluginConfig>;
|
|
110
110
|
if (c.method !== "otp") return;
|
|
111
|
-
if (c.
|
|
112
|
-
|
|
111
|
+
if (c.mode === "provider") {
|
|
112
|
+
assertOtpProviderModeCapability(drivers);
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
if (c.
|
|
115
|
+
if (c.mode === "app") {
|
|
116
116
|
const channels = c.channels ?? [];
|
|
117
|
-
|
|
117
|
+
assertOtpAppModeChannels(drivers, channels);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -13,13 +13,14 @@ import { parseAiSetupArgs } from "./index.ts";
|
|
|
13
13
|
|
|
14
14
|
describe("catalog", () => {
|
|
15
15
|
test("recommendChatModel respects RAM tiers", () => {
|
|
16
|
-
expect(recommendChatModel(8).
|
|
16
|
+
expect(recommendChatModel(8).ramGb).toBeLessThanOrEqual(8);
|
|
17
17
|
expect(recommendChatModel(16).ramGb).toBeLessThanOrEqual(16);
|
|
18
18
|
expect(recommendChatModel(null).recommended).toBe(true);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
test("cloud providers ship short curated chat lists + recommended", () => {
|
|
22
22
|
expect(cloudChatModels("openai").length).toBeGreaterThan(0);
|
|
23
|
+
expect(cloudChatModels("openai").length).toBeLessThanOrEqual(10);
|
|
23
24
|
expect(cloudChatModels("anthropic").some((m) => m.recommended)).toBe(true);
|
|
24
25
|
expect(recommendCloudChat("openai")).toBe("gpt-4o-mini");
|
|
25
26
|
expect(recommendCloudChat("anthropic")).toContain("claude");
|
|
@@ -58,11 +59,15 @@ describe("apply", () => {
|
|
|
58
59
|
});
|
|
59
60
|
`;
|
|
60
61
|
const next = upsertAiDrivers(src, "ollama");
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const body = next
|
|
63
|
+
.replace(/^import\s+[\s\S]*?from\s+["'][^"']+["'];?\s*/m, "")
|
|
64
|
+
.replace(/export\s+default\s+/, "return ");
|
|
65
|
+
const config = new Function("defineConfig", body)(<T>(c: T): T => c) as {
|
|
66
|
+
drivers?: { ai?: unknown; channel?: { ai?: unknown; email?: unknown } };
|
|
67
|
+
};
|
|
68
|
+
expect(config.drivers?.ai).toMatchObject({ local: "ollama", docker: "ollama" });
|
|
69
|
+
expect(config.drivers?.channel?.ai).toBeUndefined();
|
|
70
|
+
expect(config.drivers?.channel?.email).toBeDefined();
|
|
66
71
|
});
|
|
67
72
|
|
|
68
73
|
test("upsertEnv uncomment / set", () => {
|
|
@@ -70,6 +75,18 @@ describe("apply", () => {
|
|
|
70
75
|
expect(upsertEnv(env, "OKE_AI_DRIVER", "ollama")).toContain("OKE_AI_DRIVER=ollama");
|
|
71
76
|
});
|
|
72
77
|
|
|
78
|
+
test("upsertEnv comment keeps stack keys as overrides", () => {
|
|
79
|
+
const env = `# OKE_AI_DRIVER=mock\nOKE_AI_URL=http://x\n`;
|
|
80
|
+
const next = upsertEnv(env, "OKE_AI_URL", "http://127.0.0.1:8080/v1", { comment: true });
|
|
81
|
+
expect(next).toContain("# OKE_AI_URL=http://127.0.0.1:8080/v1");
|
|
82
|
+
expect(next).not.toMatch(/^OKE_AI_URL=/m);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("upsertEnv writes API token when provided", () => {
|
|
86
|
+
const env = `# OKE_AI_DRIVER=mock\n`;
|
|
87
|
+
expect(upsertEnv(env, "OPENAI_API_KEY", "sk-test")).toContain("OPENAI_API_KEY=sk-test");
|
|
88
|
+
});
|
|
89
|
+
|
|
73
90
|
test("renderAiTs includes vision + embed", () => {
|
|
74
91
|
const ts = renderAiTs({
|
|
75
92
|
driver: "ollama",
|
|
@@ -82,7 +99,7 @@ describe("apply", () => {
|
|
|
82
99
|
expect(ts).toContain("docsEmbed");
|
|
83
100
|
});
|
|
84
101
|
|
|
85
|
-
test("applyAiSetup writes config, env, ai.ts", () => {
|
|
102
|
+
test("applyAiSetup writes config, env, core/ai.ts", () => {
|
|
86
103
|
const dir = mkdtempSync(join(tmpdir(), "oke-ai-setup-"));
|
|
87
104
|
try {
|
|
88
105
|
writeFileSync(
|
|
@@ -119,14 +136,17 @@ export default defineConfig({
|
|
|
119
136
|
|
|
120
137
|
const config = readFileSync(join(dir, "oke.config.ts"), "utf8");
|
|
121
138
|
expect(config).toContain('local: "ollama"');
|
|
122
|
-
expect(config).toContain('ai: "ollama/ollama:
|
|
139
|
+
expect(config).toContain('ai: "ollama/ollama:0.32.6"');
|
|
123
140
|
const env = readFileSync(join(dir, ".env.local"), "utf8");
|
|
124
|
-
expect(env).toContain("OKE_AI_DRIVER=ollama");
|
|
125
|
-
expect(env).
|
|
126
|
-
|
|
141
|
+
expect(env).toContain("# OKE_AI_DRIVER=ollama");
|
|
142
|
+
expect(env).toMatch(/^OKE_AI_MODEL=gemma4:e4b$/m);
|
|
143
|
+
expect(env).not.toMatch(/^#\s*OKE_AI_MODEL=gemma4:e4b$/m);
|
|
144
|
+
expect(env).toContain("# OKE_AI_URL=http://127.0.0.1:11434");
|
|
145
|
+
expect(env).not.toMatch(/^OKE_AI_URL=/m);
|
|
146
|
+
const aiTs = readFileSync(join(dir, "src", "core", "ai.ts"), "utf8");
|
|
127
147
|
expect(aiTs).toContain("smart");
|
|
128
148
|
const app = readFileSync(join(dir, "src", "app.ts"), "utf8");
|
|
129
|
-
expect(app).toContain('import "./ai"');
|
|
149
|
+
expect(app).toContain('import "./core/ai"');
|
|
130
150
|
} finally {
|
|
131
151
|
rmSync(dir, { recursive: true, force: true });
|
|
132
152
|
}
|