okengine 0.6.1 → 0.7.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 (130) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +73 -181
  3. package/package.json +2 -2
  4. package/site/content/docs/console/clock.mdx +25 -5
  5. package/site/content/docs/console/signals.mdx +13 -4
  6. package/site/content/docs/elements/ai.mdx +29 -2
  7. package/site/content/docs/elements/channel.mdx +19 -13
  8. package/site/content/docs/elements/clock.mdx +56 -24
  9. package/site/content/docs/elements/flow.mdx +2 -0
  10. package/site/content/docs/elements/gate.mdx +8 -1
  11. package/site/content/docs/elements/signal.mdx +173 -29
  12. package/site/content/docs/elements/store.mdx +464 -73
  13. package/site/content/docs/elements/vault.mdx +15 -5
  14. package/site/content/docs/get-started/basic-usage.mdx +13 -6
  15. package/site/content/docs/get-started/installation.mdx +49 -13
  16. package/site/content/docs/get-started/introduction.mdx +1 -1
  17. package/site/content/docs/reference/cli.md +30 -2
  18. package/site/content/docs/reference/configuration.mdx +13 -13
  19. package/site/content/docs/reference/environment-variables.mdx +12 -10
  20. package/site/content/docs/reference/errors.mdx +12 -11
  21. package/site/content/docs/reference/fx.mdx +15 -13
  22. package/site/content/docs/reference/security.md +3 -1
  23. package/src/cli/ai-setup/ai-setup.test.ts +144 -0
  24. package/src/cli/ai-setup/apply.ts +214 -0
  25. package/src/cli/ai-setup/catalog.ts +263 -0
  26. package/src/cli/ai-setup/detect-ollama.ts +166 -0
  27. package/src/cli/ai-setup/index.ts +228 -0
  28. package/src/cli/ai-setup/prompts.ts +649 -0
  29. package/src/cli/ai-setup/recommend.test.ts +100 -0
  30. package/src/cli/ai-setup/recommend.ts +203 -0
  31. package/src/cli/ai.ts +38 -0
  32. package/src/cli/db-auto-push.test.ts +6 -1
  33. package/src/cli/db-auto-push.ts +8 -3
  34. package/src/cli/dev-db-push.test.ts +59 -0
  35. package/src/cli/dev.test.ts +40 -0
  36. package/src/cli/dev.ts +55 -1
  37. package/src/cli/docker-clean.test.ts +179 -0
  38. package/src/cli/docker-clean.ts +406 -0
  39. package/src/cli/docker.ts +16 -4
  40. package/src/cli/ensure-drizzle-config.ts +1 -1
  41. package/src/cli/hero-meta.test.ts +6 -5
  42. package/src/cli/hero-meta.ts +7 -1
  43. package/src/cli/index.ts +5 -0
  44. package/src/cli/load-config.ts +4 -4
  45. package/src/cli/openbao-bootstrap.test.ts +1 -0
  46. package/src/cli/openbao-bootstrap.ts +9 -2
  47. package/src/cli/registry.ts +79 -1
  48. package/src/client/live-gap.test.ts +35 -0
  49. package/src/client/transport.test.ts +21 -0
  50. package/src/client/transport.ts +27 -5
  51. package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
  52. package/src/config/define-config.test.ts +6 -6
  53. package/src/config/index.ts +1 -1
  54. package/src/console/server/console.test.ts +45 -0
  55. package/src/console/server/flows.ts +39 -9
  56. package/src/console/server/i18n.ts +41 -0
  57. package/src/console/server/vault.ts +12 -38
  58. package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
  59. package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
  60. package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
  61. package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
  62. package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
  63. package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
  64. package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
  65. package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
  66. package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
  67. package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
  68. package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
  69. package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
  70. package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
  71. package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
  72. package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
  73. package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
  74. package/src/console/ui/dist/index.html +1 -1
  75. package/src/console/ui/shell/components/ui.tsx +5 -1
  76. package/src/console/ui/shell/setup/Wizard.tsx +26 -5
  77. package/src/docker/cleanup.test.ts +193 -0
  78. package/src/docker/cleanup.ts +355 -0
  79. package/src/docker/docker.test.ts +4 -0
  80. package/src/docker/index.ts +19 -0
  81. package/src/docker/recipes/ollama.ts +10 -5
  82. package/src/drivers/ai-ollama.ts +45 -2
  83. package/src/drivers/ai-providers.test.ts +31 -0
  84. package/src/drivers/index.ts +3 -0
  85. package/src/drivers/signal-engine.ts +98 -25
  86. package/src/drivers/signal-nats.ts +4 -4
  87. package/src/drivers/signal-postgres.ts +158 -64
  88. package/src/drivers/signal-redis.ts +4 -4
  89. package/src/drivers/signal-types.ts +61 -2
  90. package/src/drivers/vault-driver-removal.test.ts +5 -1
  91. package/src/elements/clock/chaos-child.ts +150 -0
  92. package/src/elements/clock/chaos.test.ts +204 -0
  93. package/src/elements/clock/reconcile.ts +110 -0
  94. package/src/elements/clock/runtime.ts +11 -0
  95. package/src/elements/clock.test.ts +164 -2
  96. package/src/elements/clock.ts +1 -0
  97. package/src/elements/gate/runtime.ts +6 -0
  98. package/src/elements/signal/chaos-child.ts +41 -4
  99. package/src/elements/signal/declare.ts +4 -1
  100. package/src/elements/signal/delivery-modes.test.ts +179 -0
  101. package/src/elements/signal/key-ordering.test.ts +306 -0
  102. package/src/elements/signal/lease-reclaim.test.ts +123 -0
  103. package/src/elements/signal/optional-emit.test.ts +76 -0
  104. package/src/elements/signal/order-lifecycle.test.ts +130 -0
  105. package/src/elements/signal/orphan-messages.test.ts +152 -0
  106. package/src/elements/signal/runtime.ts +11 -4
  107. package/src/elements/signal/schema-emit.test.ts +107 -0
  108. package/src/elements/signal.test.ts +89 -5
  109. package/src/elements/store/files-fx.ts +104 -0
  110. package/src/elements/store/files-image.test.ts +251 -0
  111. package/src/elements/store/files-image.ts +494 -0
  112. package/src/elements/store/runtime.ts +60 -0
  113. package/src/elements/store.ts +24 -0
  114. package/src/elements/vault/boot-chain.ts +150 -0
  115. package/src/elements/vault/runtime.ts +8 -0
  116. package/src/kernel/app.ts +3 -1
  117. package/src/kernel/boot-bind/clock.ts +52 -7
  118. package/src/kernel/boot-bind/gate.ts +71 -3
  119. package/src/kernel/boot-bind/honor-config.test.ts +247 -0
  120. package/src/kernel/boot-bind/signal.ts +64 -7
  121. package/src/kernel/boot-bind/vault.ts +27 -8
  122. package/src/kernel/boot.ts +10 -2
  123. package/src/kernel/errors.ts +8 -2
  124. package/src/kernel/fx.test.ts +13 -0
  125. package/src/kernel/fx.ts +27 -4
  126. package/src/runtime/dev-request-log.test.ts +20 -1
  127. package/src/runtime/dev-request-log.ts +38 -0
  128. package/src/term.test.ts +15 -0
  129. package/src/term.ts +8 -3
  130. package/src/console/ui/dist/assets/index-CjxwRGVv.js +0 -10
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Detect local Ollama — CLI (`ollama list` / `ps`) and HTTP `/api/tags`.
3
+ */
4
+
5
+ import { OLLAMA_DEFAULT_BASE_URL, normalizeOllamaBaseUrl } from "../../drivers/ai-ollama.ts";
6
+ import { ALL_CURATED, type CatalogModel } from "./catalog.ts";
7
+
8
+ /** Result of probing the local Ollama install. */
9
+ export type OllamaDetectResult = {
10
+ readonly available: boolean;
11
+ readonly baseUrl: string;
12
+ readonly installed: readonly string[];
13
+ readonly running: readonly string[];
14
+ readonly curatedInstalled: readonly CatalogModel[];
15
+ readonly curatedMissing: readonly CatalogModel[];
16
+ };
17
+
18
+ /**
19
+ * Parse `ollama list` tabular output into model names.
20
+ *
21
+ * @param stdout - CLI stdout
22
+ */
23
+ export function parseOllamaList(stdout: string): string[] {
24
+ const lines = stdout
25
+ .split("\n")
26
+ .map((l) => l.trim())
27
+ .filter(Boolean);
28
+ const out: string[] = [];
29
+ for (const line of lines) {
30
+ if (/^NAME\b/i.test(line)) continue;
31
+ const name = line.split(/\s+/)[0];
32
+ if (name) out.push(name);
33
+ }
34
+ return out;
35
+ }
36
+
37
+ /**
38
+ * Parse `ollama ps` tabular output into running model names.
39
+ *
40
+ * @param stdout - CLI stdout
41
+ */
42
+ export function parseOllamaPs(stdout: string): string[] {
43
+ return parseOllamaList(stdout);
44
+ }
45
+
46
+ /**
47
+ * Normalize installed tag for comparison (`qwen3.5:9b` vs `qwen3.5:9b-mlx`).
48
+ *
49
+ * @param id - Catalog or installed id
50
+ * @param installed - Installed names
51
+ */
52
+ export function isInstalled(id: string, installed: readonly string[]): boolean {
53
+ const base = id.split(":")[0] ?? id;
54
+ return installed.some((name) => {
55
+ if (name === id) return true;
56
+ if (name.startsWith(`${id}-`)) return true;
57
+ if (name.startsWith(`${base}:`) && id.startsWith(`${base}:`)) {
58
+ // treat mlx / quant suffixes as matching the same family:tag when prefix matches
59
+ const instTag = name.slice(base.length + 1);
60
+ const wantTag = id.slice(base.length + 1);
61
+ return (
62
+ instTag === wantTag || instTag.startsWith(`${wantTag}-`) || wantTag.startsWith(instTag)
63
+ );
64
+ }
65
+ return false;
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Detect Ollama via CLI then HTTP fallback.
71
+ *
72
+ * @param options - Base URL / spawn / fetch seams
73
+ */
74
+ export async function detectOllama(
75
+ options: {
76
+ readonly baseUrl?: string;
77
+ readonly fetch?: typeof globalThis.fetch;
78
+ readonly run?: (cmd: readonly string[]) => Promise<{ code: number; stdout: string }>;
79
+ } = {},
80
+ ): Promise<OllamaDetectResult> {
81
+ const baseUrl = normalizeOllamaBaseUrl(options.baseUrl ?? OLLAMA_DEFAULT_BASE_URL);
82
+ const run =
83
+ options.run ??
84
+ (async (cmd) => {
85
+ const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "pipe" });
86
+ const stdout = await new Response(proc.stdout).text();
87
+ const code = await proc.exited;
88
+ return { code, stdout };
89
+ });
90
+
91
+ let installed: string[] = [];
92
+ let running: string[] = [];
93
+ let available = false;
94
+
95
+ try {
96
+ const list = await run(["ollama", "list"]);
97
+ if (list.code === 0) {
98
+ available = true;
99
+ installed = parseOllamaList(list.stdout);
100
+ }
101
+ const ps = await run(["ollama", "ps"]);
102
+ if (ps.code === 0) {
103
+ running = parseOllamaPs(ps.stdout);
104
+ }
105
+ } catch {
106
+ // CLI missing — try HTTP
107
+ }
108
+
109
+ if (!available) {
110
+ const fetchFn = options.fetch ?? globalThis.fetch;
111
+ try {
112
+ const res = await fetchFn(`${baseUrl}/api/tags`, {
113
+ method: "GET",
114
+ signal: AbortSignal.timeout(2_000),
115
+ });
116
+ if (res.ok) {
117
+ available = true;
118
+ const json = (await res.json()) as { models?: readonly { name?: string }[] };
119
+ installed = (json.models ?? [])
120
+ .map((m) => m.name)
121
+ .filter((n): n is string => typeof n === "string");
122
+ }
123
+ } catch {
124
+ // unavailable
125
+ }
126
+ }
127
+
128
+ const curatedInstalled = ALL_CURATED.filter((m) => isInstalled(m.id, installed));
129
+ const curatedMissing = ALL_CURATED.filter((m) => !isInstalled(m.id, installed));
130
+
131
+ return {
132
+ available,
133
+ baseUrl,
134
+ installed,
135
+ running,
136
+ curatedInstalled,
137
+ curatedMissing,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Best-effort total system RAM in GB.
143
+ */
144
+ export function detectTotalRamGb(): number | null {
145
+ try {
146
+ if (process.platform === "darwin") {
147
+ const proc = Bun.spawnSync(["sysctl", "-n", "hw.memsize"], { stdout: "pipe" });
148
+ if (proc.exitCode === 0) {
149
+ const bytes = Number(proc.stdout.toString().trim());
150
+ if (Number.isFinite(bytes) && bytes > 0) return Math.round(bytes / 1024 ** 3);
151
+ }
152
+ }
153
+ if (process.platform === "linux") {
154
+ const proc = Bun.spawnSync(["awk", "/MemTotal/ {print $2}", "/proc/meminfo"], {
155
+ stdout: "pipe",
156
+ });
157
+ if (proc.exitCode === 0) {
158
+ const kb = Number(proc.stdout.toString().trim());
159
+ if (Number.isFinite(kb) && kb > 0) return Math.round(kb / 1024 ** 2);
160
+ }
161
+ }
162
+ } catch {
163
+ // ignore
164
+ }
165
+ return null;
166
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * `oke ai setup` — interactive AI provider + model configuration.
3
+ */
4
+
5
+ import { cancel, intro, outro, spinner } from "@clack/prompts";
6
+ import { applyAiSetup, type AiSetupApplyInput } from "./apply.ts";
7
+ import { askAiSetup } from "./prompts.ts";
8
+
9
+ /** Parsed flags for AI setup. */
10
+ export type AiSetupCliArgs = {
11
+ readonly help: boolean;
12
+ readonly yes: boolean;
13
+ readonly provider?: string;
14
+ readonly chat?: string;
15
+ readonly vision?: string;
16
+ readonly embed?: string;
17
+ readonly pull: boolean;
18
+ readonly cwd: string;
19
+ };
20
+
21
+ /**
22
+ * Parse argv after `ai setup`.
23
+ *
24
+ * @param argv - Remaining args
25
+ * @param cwd - Project root
26
+ */
27
+ export function parseAiSetupArgs(
28
+ argv: readonly string[],
29
+ cwd: string = process.cwd(),
30
+ ): AiSetupCliArgs {
31
+ let help = false;
32
+ let yes = false;
33
+ let provider: string | undefined;
34
+ let chat: string | undefined;
35
+ let vision: string | undefined;
36
+ let embed: string | undefined;
37
+ let pull = true;
38
+
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i]!;
41
+ if (a === "--help" || a === "-h") {
42
+ help = true;
43
+ continue;
44
+ }
45
+ if (a === "--yes" || a === "-y") {
46
+ yes = true;
47
+ continue;
48
+ }
49
+ if (a === "--no-pull") {
50
+ pull = false;
51
+ continue;
52
+ }
53
+ if (a === "--pull") {
54
+ pull = true;
55
+ continue;
56
+ }
57
+ if (a === "--provider") {
58
+ provider = argv[++i];
59
+ continue;
60
+ }
61
+ if (a.startsWith("--provider=")) {
62
+ provider = a.slice("--provider=".length);
63
+ continue;
64
+ }
65
+ if (a === "--chat") {
66
+ chat = argv[++i];
67
+ continue;
68
+ }
69
+ if (a.startsWith("--chat=")) {
70
+ chat = a.slice("--chat=".length);
71
+ continue;
72
+ }
73
+ if (a === "--vision") {
74
+ vision = argv[++i];
75
+ continue;
76
+ }
77
+ if (a.startsWith("--vision=")) {
78
+ vision = a.slice("--vision=".length);
79
+ continue;
80
+ }
81
+ if (a === "--embed") {
82
+ embed = argv[++i];
83
+ continue;
84
+ }
85
+ if (a.startsWith("--embed=")) {
86
+ embed = a.slice("--embed=".length);
87
+ continue;
88
+ }
89
+ throw new Error(`oke ai setup: unknown option ${a}`);
90
+ }
91
+
92
+ return { help, yes, provider, chat, vision, embed, pull, cwd };
93
+ }
94
+
95
+ /**
96
+ * Help text for `oke ai setup`.
97
+ */
98
+ export function aiSetupHelp(): string {
99
+ return `oke ai setup — configure AI driver + models
100
+
101
+ Usage:
102
+ oke ai setup
103
+ oke ai setup --provider ollama --yes
104
+ oke ai setup --provider anthropic --chat claude-sonnet-4-20250514 --yes
105
+
106
+ Options:
107
+ --provider <id> ollama | openai | anthropic | gemini | lmstudio | openrouter | custom
108
+ --chat <model> Chat model id
109
+ --vision <model> Vision model id (ollama)
110
+ --embed <model> Embedding model id (ollama)
111
+ --pull / --no-pull Pull missing Ollama models (default: pull)
112
+ -y, --yes Non-interactive (requires --provider)
113
+ -h, --help Show this help
114
+ `;
115
+ }
116
+
117
+ /**
118
+ * Run AI setup (interactive or flags).
119
+ *
120
+ * @param args - Parsed args
121
+ * @returns Exit code
122
+ */
123
+ export async function runAiSetup(args: AiSetupCliArgs): Promise<number> {
124
+ if (args.help) {
125
+ console.log(aiSetupHelp());
126
+ return 0;
127
+ }
128
+
129
+ const tty = Boolean(process.stdin.isTTY);
130
+ let input: AiSetupApplyInput | null = null;
131
+
132
+ if (args.yes || !tty) {
133
+ if (!args.provider) {
134
+ console.error("oke ai setup: --provider is required in non-interactive mode");
135
+ return 1;
136
+ }
137
+ input = nonInteractiveInput(args);
138
+ } else {
139
+ intro("oke ai setup");
140
+ input = await askAiSetup({ provider: args.provider });
141
+ if (input === null) {
142
+ cancel("Cancelled.");
143
+ return 1;
144
+ }
145
+ // Flag overrides for models when provided alongside interactive provider
146
+ if (args.chat) input = { ...input, chatModel: args.chat };
147
+ if (args.vision !== undefined) input = { ...input, visionModel: args.vision || null };
148
+ if (args.embed) input = { ...input, embedModel: args.embed };
149
+ }
150
+
151
+ if (input === null) return 1;
152
+
153
+ if (args.pull && input.driver === "ollama" && (args.yes || !tty) && args.chat) {
154
+ // Non-interactive pull of requested models
155
+ const toPull = [input.chatModel, input.visionModel, input.embedModel].filter(
156
+ (m): m is string => typeof m === "string" && m.length > 0,
157
+ );
158
+ for (const id of toPull) {
159
+ const proc = Bun.spawn(["ollama", "pull", id], {
160
+ stdout: "inherit",
161
+ stderr: "inherit",
162
+ });
163
+ await proc.exited;
164
+ }
165
+ }
166
+
167
+ const spun = tty ? spinner() : undefined;
168
+ spun?.start("Writing AI config…");
169
+ try {
170
+ const result = applyAiSetup(args.cwd, input);
171
+ spun?.stop("AI configured.");
172
+ if (tty) {
173
+ outro(
174
+ `Wrote ${result.aiTsPath}\nDriver ${input.driver}` +
175
+ (input.chatModel ? ` · chat ${input.chatModel}` : ""),
176
+ );
177
+ } else {
178
+ console.log(`oke ai setup: driver=${input.driver} chat=${input.chatModel ?? "—"}`);
179
+ }
180
+ return 0;
181
+ } catch (e) {
182
+ spun?.stop("Failed.");
183
+ console.error(e instanceof Error ? e.message : e);
184
+ return 1;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Build apply input from flags (non-interactive).
190
+ *
191
+ * @param args - Parsed args
192
+ */
193
+ function nonInteractiveInput(args: AiSetupCliArgs): AiSetupApplyInput {
194
+ const provider = args.provider!;
195
+ if (provider === "ollama") {
196
+ return {
197
+ driver: "ollama",
198
+ baseUrl: process.env.OKE_AI_URL ?? "http://127.0.0.1:11434",
199
+ chatModel: args.chat ?? "gemma4:e4b",
200
+ visionModel: args.vision === undefined ? "qwen3-vl:4b" : args.vision || null,
201
+ embedModel: args.embed ?? "nomic-embed-text",
202
+ };
203
+ }
204
+ if (provider === "anthropic") {
205
+ return {
206
+ driver: "anthropic",
207
+ chatModel: args.chat ?? "claude-sonnet-4-20250514",
208
+ visionModel: null,
209
+ embedModel: null,
210
+ apiKeyEnv: "ANTHROPIC_API_KEY",
211
+ };
212
+ }
213
+ const baseUrls: Record<string, string | undefined> = {
214
+ openai: "https://api.openai.com/v1",
215
+ openrouter: "https://openrouter.ai/api/v1",
216
+ lmstudio: "http://127.0.0.1:1234/v1",
217
+ gemini: undefined,
218
+ custom: undefined,
219
+ };
220
+ return {
221
+ driver: "openai-compatible",
222
+ ...(baseUrls[provider] !== undefined ? { baseUrl: baseUrls[provider] } : {}),
223
+ chatModel: args.chat ?? "gpt-4o-mini",
224
+ visionModel: null,
225
+ embedModel: null,
226
+ apiKeyEnv: "OPENAI_API_KEY",
227
+ };
228
+ }