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.
- package/AGENTS.md +2 -2
- package/README.md +73 -181
- package/package.json +2 -2
- package/site/content/docs/console/clock.mdx +25 -5
- package/site/content/docs/console/signals.mdx +13 -4
- package/site/content/docs/elements/ai.mdx +29 -2
- package/site/content/docs/elements/channel.mdx +19 -13
- package/site/content/docs/elements/clock.mdx +56 -24
- package/site/content/docs/elements/flow.mdx +2 -0
- package/site/content/docs/elements/gate.mdx +8 -1
- package/site/content/docs/elements/signal.mdx +173 -29
- package/site/content/docs/elements/store.mdx +464 -73
- package/site/content/docs/elements/vault.mdx +15 -5
- package/site/content/docs/get-started/basic-usage.mdx +13 -6
- package/site/content/docs/get-started/installation.mdx +49 -13
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/reference/cli.md +30 -2
- package/site/content/docs/reference/configuration.mdx +13 -13
- package/site/content/docs/reference/environment-variables.mdx +12 -10
- package/site/content/docs/reference/errors.mdx +12 -11
- package/site/content/docs/reference/fx.mdx +15 -13
- package/site/content/docs/reference/security.md +3 -1
- package/src/cli/ai-setup/ai-setup.test.ts +144 -0
- package/src/cli/ai-setup/apply.ts +214 -0
- package/src/cli/ai-setup/catalog.ts +263 -0
- package/src/cli/ai-setup/detect-ollama.ts +166 -0
- package/src/cli/ai-setup/index.ts +228 -0
- package/src/cli/ai-setup/prompts.ts +649 -0
- package/src/cli/ai-setup/recommend.test.ts +100 -0
- package/src/cli/ai-setup/recommend.ts +203 -0
- package/src/cli/ai.ts +38 -0
- package/src/cli/db-auto-push.test.ts +6 -1
- package/src/cli/db-auto-push.ts +8 -3
- package/src/cli/dev-db-push.test.ts +59 -0
- package/src/cli/dev.test.ts +40 -0
- package/src/cli/dev.ts +55 -1
- package/src/cli/docker-clean.test.ts +179 -0
- package/src/cli/docker-clean.ts +406 -0
- package/src/cli/docker.ts +16 -4
- package/src/cli/ensure-drizzle-config.ts +1 -1
- package/src/cli/hero-meta.test.ts +6 -5
- package/src/cli/hero-meta.ts +7 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.ts +4 -4
- package/src/cli/openbao-bootstrap.test.ts +1 -0
- package/src/cli/openbao-bootstrap.ts +9 -2
- package/src/cli/registry.ts +79 -1
- package/src/client/live-gap.test.ts +35 -0
- package/src/client/transport.test.ts +21 -0
- package/src/client/transport.ts +27 -5
- package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
- package/src/config/define-config.test.ts +6 -6
- package/src/config/index.ts +1 -1
- package/src/console/server/console.test.ts +45 -0
- package/src/console/server/flows.ts +39 -9
- package/src/console/server/i18n.ts +41 -0
- package/src/console/server/vault.ts +12 -38
- package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
- package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
- package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
- package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
- package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
- package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
- package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
- package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
- package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
- package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
- package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
- package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
- package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
- package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/shell/components/ui.tsx +5 -1
- package/src/console/ui/shell/setup/Wizard.tsx +26 -5
- package/src/docker/cleanup.test.ts +193 -0
- package/src/docker/cleanup.ts +355 -0
- package/src/docker/docker.test.ts +4 -0
- package/src/docker/index.ts +19 -0
- package/src/docker/recipes/ollama.ts +10 -5
- package/src/drivers/ai-ollama.ts +45 -2
- package/src/drivers/ai-providers.test.ts +31 -0
- package/src/drivers/index.ts +3 -0
- package/src/drivers/signal-engine.ts +98 -25
- package/src/drivers/signal-nats.ts +4 -4
- package/src/drivers/signal-postgres.ts +158 -64
- package/src/drivers/signal-redis.ts +4 -4
- package/src/drivers/signal-types.ts +61 -2
- package/src/drivers/vault-driver-removal.test.ts +5 -1
- package/src/elements/clock/chaos-child.ts +150 -0
- package/src/elements/clock/chaos.test.ts +204 -0
- package/src/elements/clock/reconcile.ts +110 -0
- package/src/elements/clock/runtime.ts +11 -0
- package/src/elements/clock.test.ts +164 -2
- package/src/elements/clock.ts +1 -0
- package/src/elements/gate/runtime.ts +6 -0
- package/src/elements/signal/chaos-child.ts +41 -4
- package/src/elements/signal/declare.ts +4 -1
- package/src/elements/signal/delivery-modes.test.ts +179 -0
- package/src/elements/signal/key-ordering.test.ts +306 -0
- package/src/elements/signal/lease-reclaim.test.ts +123 -0
- package/src/elements/signal/optional-emit.test.ts +76 -0
- package/src/elements/signal/order-lifecycle.test.ts +130 -0
- package/src/elements/signal/orphan-messages.test.ts +152 -0
- package/src/elements/signal/runtime.ts +11 -4
- package/src/elements/signal/schema-emit.test.ts +107 -0
- package/src/elements/signal.test.ts +89 -5
- package/src/elements/store/files-fx.ts +104 -0
- package/src/elements/store/files-image.test.ts +251 -0
- package/src/elements/store/files-image.ts +494 -0
- package/src/elements/store/runtime.ts +60 -0
- package/src/elements/store.ts +24 -0
- package/src/elements/vault/boot-chain.ts +150 -0
- package/src/elements/vault/runtime.ts +8 -0
- package/src/kernel/app.ts +3 -1
- package/src/kernel/boot-bind/clock.ts +52 -7
- package/src/kernel/boot-bind/gate.ts +71 -3
- package/src/kernel/boot-bind/honor-config.test.ts +247 -0
- package/src/kernel/boot-bind/signal.ts +64 -7
- package/src/kernel/boot-bind/vault.ts +27 -8
- package/src/kernel/boot.ts +10 -2
- package/src/kernel/errors.ts +8 -2
- package/src/kernel/fx.test.ts +13 -0
- package/src/kernel/fx.ts +27 -4
- package/src/runtime/dev-request-log.test.ts +20 -1
- package/src/runtime/dev-request-log.ts +38 -0
- package/src/term.test.ts +15 -0
- package/src/term.ts +8 -3
- 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
|
+
}
|