create-oke 0.6.1 → 0.8.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/README.md +23 -8
- package/package.json +3 -3
- package/src/ai-setup/apply.ts +214 -0
- package/src/ai-setup/catalog.ts +263 -0
- package/src/ai-setup/detect-ollama.ts +180 -0
- package/src/ai-setup/from-pref.ts +99 -0
- package/src/ai-setup/prompts.ts +649 -0
- package/src/ai-setup/recommend.ts +203 -0
- package/src/cli.test.ts +385 -28
- package/src/cli.ts +332 -29
- package/src/create-defaults.test.ts +92 -0
- package/src/create-defaults.ts +246 -0
- package/src/customize-flow.test.ts +82 -0
- package/src/customize-flow.ts +432 -0
- package/src/drivers-catalog.ts +226 -0
- package/src/scaffold.ts +97 -10
- package/src/templates.ts +14 -9
- package/src/transform.test.ts +105 -0
- package/src/transform.ts +227 -0
- package/src/wizard-select.ts +50 -0
- package/{template → templates/advanced}/.env.example +1 -0
- package/templates/advanced/README.md +39 -0
- package/templates/advanced/oke.config.ts +81 -0
- package/templates/advanced/package.json +18 -0
- package/templates/advanced/src/app.ts +30 -0
- package/templates/advanced/src/channels.ts +13 -0
- package/templates/advanced/src/core.ts +8 -0
- package/{template → templates/advanced}/src/flows/main/index.ts +9 -6
- package/templates/advanced/src/flows/main/shapes.ts +1 -0
- package/templates/advanced/src/flows/main/signals.ts +1 -0
- package/templates/advanced/src/flows/notes/index.ts +224 -0
- package/templates/advanced/src/flows/notes/shapes.ts +52 -0
- package/templates/advanced/src/flows/notes/signals.ts +13 -0
- package/templates/advanced/src/gates.ts +12 -0
- package/templates/advanced/src/locales/ar.ts +18 -0
- package/templates/advanced/src/locales/en.ts +22 -0
- package/templates/advanced/src/schema.decl.ts +13 -0
- package/templates/advanced/src/seed/index.ts +67 -0
- package/templates/advanced/src/vault.ts +7 -0
- package/templates/advanced/tests/advanced.test.ts +42 -0
- package/templates/standard/.env.example +94 -0
- package/templates/standard/README.md +48 -0
- package/templates/standard/drizzle.config.ts +25 -0
- package/{template → templates/standard}/oke.config.ts +14 -7
- package/{template → templates/standard}/package.json +1 -1
- package/templates/standard/src/app.ts +30 -0
- package/templates/standard/src/channels.ts +13 -0
- package/{template → templates/standard}/src/core.ts +1 -0
- package/templates/standard/src/flows/main/index.ts +34 -0
- package/templates/standard/src/flows/main/shapes.ts +1 -0
- package/templates/standard/src/flows/main/signals.ts +1 -0
- package/templates/standard/src/flows/notes/index.ts +143 -0
- package/templates/standard/src/flows/notes/shapes.ts +26 -0
- package/templates/standard/src/flows/notes/signals.ts +13 -0
- package/templates/standard/src/gates.ts +12 -0
- package/templates/standard/src/locales/ar.ts +18 -0
- package/templates/standard/src/locales/en.ts +22 -0
- package/templates/standard/src/schema.decl.ts +13 -0
- package/templates/standard/src/seed/index.ts +56 -0
- package/templates/standard/src/vault.ts +7 -0
- package/templates/standard/tests/standard.test.ts +51 -0
- package/template/README.md +0 -77
- package/template/bun.lock +0 -845
- package/template/src/app.ts +0 -18
- package/template/src/channels.ts +0 -4
- package/template/src/flows/main/shapes.ts +0 -2
- package/template/src/flows/main/signals.ts +0 -4
- package/template/src/gates.ts +0 -4
- package/template/src/locales/ar.ts +0 -20
- package/template/src/locales/en.ts +0 -26
- package/template/src/schema.decl.ts +0 -12
- package/template/src/vault.ts +0 -4
- package/template/tests/standard.test.ts +0 -18
- /package/{template → templates/advanced}/drizzle.config.ts +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect local Ollama — CLI (`ollama list` / `ps`) and HTTP `/api/tags`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ALL_CURATED, type CatalogModel } from "./catalog.ts";
|
|
6
|
+
|
|
7
|
+
/** Default Ollama HTTP origin (create-oke copy — no okengine driver import). */
|
|
8
|
+
const OLLAMA_DEFAULT_BASE_URL = "http://127.0.0.1:11434";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalize an Ollama base URL (strip trailing slash / accidental `/v1`).
|
|
12
|
+
*
|
|
13
|
+
* @param raw - User or env value
|
|
14
|
+
*/
|
|
15
|
+
function normalizeOllamaBaseUrl(raw: string): string {
|
|
16
|
+
const trimmed = raw.trim().replace(/\/$/, "");
|
|
17
|
+
if (!trimmed) return OLLAMA_DEFAULT_BASE_URL;
|
|
18
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/v1\/?$/i, "");
|
|
19
|
+
return `http://${trimmed}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Result of probing the local Ollama install. */
|
|
23
|
+
export type OllamaDetectResult = {
|
|
24
|
+
readonly available: boolean;
|
|
25
|
+
readonly baseUrl: string;
|
|
26
|
+
readonly installed: readonly string[];
|
|
27
|
+
readonly running: readonly string[];
|
|
28
|
+
readonly curatedInstalled: readonly CatalogModel[];
|
|
29
|
+
readonly curatedMissing: readonly CatalogModel[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse `ollama list` tabular output into model names.
|
|
34
|
+
*
|
|
35
|
+
* @param stdout - CLI stdout
|
|
36
|
+
*/
|
|
37
|
+
export function parseOllamaList(stdout: string): string[] {
|
|
38
|
+
const lines = stdout
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map((l) => l.trim())
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
const out: string[] = [];
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (/^NAME\b/i.test(line)) continue;
|
|
45
|
+
const name = line.split(/\s+/)[0];
|
|
46
|
+
if (name) out.push(name);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse `ollama ps` tabular output into running model names.
|
|
53
|
+
*
|
|
54
|
+
* @param stdout - CLI stdout
|
|
55
|
+
*/
|
|
56
|
+
export function parseOllamaPs(stdout: string): string[] {
|
|
57
|
+
return parseOllamaList(stdout);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Normalize installed tag for comparison (`qwen3.5:9b` vs `qwen3.5:9b-mlx`).
|
|
62
|
+
*
|
|
63
|
+
* @param id - Catalog or installed id
|
|
64
|
+
* @param installed - Installed names
|
|
65
|
+
*/
|
|
66
|
+
export function isInstalled(id: string, installed: readonly string[]): boolean {
|
|
67
|
+
const base = id.split(":")[0] ?? id;
|
|
68
|
+
return installed.some((name) => {
|
|
69
|
+
if (name === id) return true;
|
|
70
|
+
if (name.startsWith(`${id}-`)) return true;
|
|
71
|
+
if (name.startsWith(`${base}:`) && id.startsWith(`${base}:`)) {
|
|
72
|
+
// treat mlx / quant suffixes as matching the same family:tag when prefix matches
|
|
73
|
+
const instTag = name.slice(base.length + 1);
|
|
74
|
+
const wantTag = id.slice(base.length + 1);
|
|
75
|
+
return (
|
|
76
|
+
instTag === wantTag || instTag.startsWith(`${wantTag}-`) || wantTag.startsWith(instTag)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Detect Ollama via CLI then HTTP fallback.
|
|
85
|
+
*
|
|
86
|
+
* @param options - Base URL / spawn / fetch seams
|
|
87
|
+
*/
|
|
88
|
+
export async function detectOllama(
|
|
89
|
+
options: {
|
|
90
|
+
readonly baseUrl?: string;
|
|
91
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
92
|
+
readonly run?: (cmd: readonly string[]) => Promise<{ code: number; stdout: string }>;
|
|
93
|
+
} = {},
|
|
94
|
+
): Promise<OllamaDetectResult> {
|
|
95
|
+
const baseUrl = normalizeOllamaBaseUrl(options.baseUrl ?? OLLAMA_DEFAULT_BASE_URL);
|
|
96
|
+
const run =
|
|
97
|
+
options.run ??
|
|
98
|
+
(async (cmd) => {
|
|
99
|
+
const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "pipe" });
|
|
100
|
+
const stdout = await new Response(proc.stdout).text();
|
|
101
|
+
const code = await proc.exited;
|
|
102
|
+
return { code, stdout };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let installed: string[] = [];
|
|
106
|
+
let running: string[] = [];
|
|
107
|
+
let available = false;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const list = await run(["ollama", "list"]);
|
|
111
|
+
if (list.code === 0) {
|
|
112
|
+
available = true;
|
|
113
|
+
installed = parseOllamaList(list.stdout);
|
|
114
|
+
}
|
|
115
|
+
const ps = await run(["ollama", "ps"]);
|
|
116
|
+
if (ps.code === 0) {
|
|
117
|
+
running = parseOllamaPs(ps.stdout);
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// CLI missing — try HTTP
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!available) {
|
|
124
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetchFn(`${baseUrl}/api/tags`, {
|
|
127
|
+
method: "GET",
|
|
128
|
+
signal: AbortSignal.timeout(2_000),
|
|
129
|
+
});
|
|
130
|
+
if (res.ok) {
|
|
131
|
+
available = true;
|
|
132
|
+
const json = (await res.json()) as { models?: readonly { name?: string }[] };
|
|
133
|
+
installed = (json.models ?? [])
|
|
134
|
+
.map((m) => m.name)
|
|
135
|
+
.filter((n): n is string => typeof n === "string");
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// unavailable
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const curatedInstalled = ALL_CURATED.filter((m) => isInstalled(m.id, installed));
|
|
143
|
+
const curatedMissing = ALL_CURATED.filter((m) => !isInstalled(m.id, installed));
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
available,
|
|
147
|
+
baseUrl,
|
|
148
|
+
installed,
|
|
149
|
+
running,
|
|
150
|
+
curatedInstalled,
|
|
151
|
+
curatedMissing,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Best-effort total system RAM in GB.
|
|
157
|
+
*/
|
|
158
|
+
export function detectTotalRamGb(): number | null {
|
|
159
|
+
try {
|
|
160
|
+
if (process.platform === "darwin") {
|
|
161
|
+
const proc = Bun.spawnSync(["sysctl", "-n", "hw.memsize"], { stdout: "pipe" });
|
|
162
|
+
if (proc.exitCode === 0) {
|
|
163
|
+
const bytes = Number(proc.stdout.toString().trim());
|
|
164
|
+
if (Number.isFinite(bytes) && bytes > 0) return Math.round(bytes / 1024 ** 3);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (process.platform === "linux") {
|
|
168
|
+
const proc = Bun.spawnSync(["awk", "/MemTotal/ {print $2}", "/proc/meminfo"], {
|
|
169
|
+
stdout: "pipe",
|
|
170
|
+
});
|
|
171
|
+
if (proc.exitCode === 0) {
|
|
172
|
+
const kb = Number(proc.stdout.toString().trim());
|
|
173
|
+
if (Number.isFinite(kb) && kb > 0) return Math.round(kb / 1024 ** 2);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// ignore
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert persisted create-defaults AI prefs ↔ apply input.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CreateAiPref } from "../create-defaults.ts";
|
|
6
|
+
import type { AiSetupApplyInput } from "./apply.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build apply input from saved create-defaults (reuse path).
|
|
10
|
+
*
|
|
11
|
+
* @param pref - Saved AI preference
|
|
12
|
+
*/
|
|
13
|
+
export function applyInputFromAiPref(pref: CreateAiPref): AiSetupApplyInput | null {
|
|
14
|
+
if (!pref.enabled || !pref.driver) return null;
|
|
15
|
+
const driver = pref.driver as AiSetupApplyInput["driver"];
|
|
16
|
+
if (
|
|
17
|
+
driver !== "ollama" &&
|
|
18
|
+
driver !== "anthropic" &&
|
|
19
|
+
driver !== "openai-compatible" &&
|
|
20
|
+
driver !== "mock"
|
|
21
|
+
) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
driver,
|
|
26
|
+
...(pref.baseUrl ? { baseUrl: pref.baseUrl } : {}),
|
|
27
|
+
...(pref.chatModel ? { chatModel: pref.chatModel } : {}),
|
|
28
|
+
...(pref.visionModel !== undefined && pref.visionModel !== null
|
|
29
|
+
? { visionModel: pref.visionModel }
|
|
30
|
+
: {}),
|
|
31
|
+
...(pref.embedModel !== undefined && pref.embedModel !== null
|
|
32
|
+
? { embedModel: pref.embedModel }
|
|
33
|
+
: {}),
|
|
34
|
+
...(pref.apiKeyEnv ? { apiKeyEnv: pref.apiKeyEnv } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Merge model choices into a CreateAiPref for persistence.
|
|
40
|
+
*
|
|
41
|
+
* @param base - Provider / driver prefs
|
|
42
|
+
* @param apply - Model apply input
|
|
43
|
+
*/
|
|
44
|
+
export function aiPrefWithModels(
|
|
45
|
+
base: CreateAiPref,
|
|
46
|
+
apply: AiSetupApplyInput | null,
|
|
47
|
+
): CreateAiPref {
|
|
48
|
+
if (!apply) return base;
|
|
49
|
+
return {
|
|
50
|
+
...base,
|
|
51
|
+
driver: apply.driver,
|
|
52
|
+
chatModel: apply.chatModel ?? null,
|
|
53
|
+
visionModel: apply.visionModel ?? null,
|
|
54
|
+
embedModel: apply.embedModel ?? null,
|
|
55
|
+
baseUrl: apply.baseUrl ?? null,
|
|
56
|
+
apiKeyEnv: apply.apiKeyEnv ?? null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Non-interactive defaults for `--yes --ai` (mirrors `oke ai setup --yes`).
|
|
62
|
+
*
|
|
63
|
+
* @param provider - Menu id
|
|
64
|
+
*/
|
|
65
|
+
export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
|
|
66
|
+
if (provider === "ollama") {
|
|
67
|
+
return {
|
|
68
|
+
driver: "ollama",
|
|
69
|
+
baseUrl: process.env.OKE_AI_URL ?? "http://127.0.0.1:11434",
|
|
70
|
+
chatModel: "gemma4:e4b",
|
|
71
|
+
visionModel: "qwen3-vl:4b",
|
|
72
|
+
embedModel: "nomic-embed-text",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (provider === "anthropic") {
|
|
76
|
+
return {
|
|
77
|
+
driver: "anthropic",
|
|
78
|
+
chatModel: "claude-sonnet-4-20250514",
|
|
79
|
+
visionModel: null,
|
|
80
|
+
embedModel: null,
|
|
81
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const baseUrls: Record<string, string | undefined> = {
|
|
85
|
+
openai: "https://api.openai.com/v1",
|
|
86
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
87
|
+
lmstudio: "http://127.0.0.1:1234/v1",
|
|
88
|
+
gemini: undefined,
|
|
89
|
+
custom: undefined,
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
driver: "openai-compatible",
|
|
93
|
+
...(baseUrls[provider] !== undefined ? { baseUrl: baseUrls[provider] } : {}),
|
|
94
|
+
chatModel: "gpt-4o-mini",
|
|
95
|
+
visionModel: null,
|
|
96
|
+
embedModel: null,
|
|
97
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
98
|
+
};
|
|
99
|
+
}
|