create-oke 0.10.3 → 0.11.1
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 +2 -2
- package/package.json +2 -2
- package/src/ai-setup/apply.ts +113 -17
- package/src/ai-setup/catalog.ts +3 -3
- package/src/ai-setup/from-pref.ts +1 -1
- package/src/ai-setup/prompts.ts +1 -1
- package/src/cli.test.ts +183 -142
- package/src/cli.ts +190 -32
- package/src/create-defaults.test.ts +21 -15
- package/src/create-defaults.ts +33 -13
- package/src/customize-flow.test.ts +30 -35
- package/src/customize-flow.ts +68 -229
- package/src/drivers-catalog.ts +42 -48
- package/src/local-okengine.test.ts +50 -41
- package/src/local-okengine.ts +28 -14
- package/src/locales.test.ts +128 -0
- package/src/locales.ts +321 -0
- package/src/scaffold.ts +277 -33
- package/src/templates.ts +2 -8
- package/src/transform.test.ts +96 -36
- package/src/transform.ts +285 -110
- package/templates/advanced/.env.example +23 -3
- package/templates/advanced/README.md +1 -1
- package/templates/advanced/drizzle.config.ts +10 -13
- package/templates/advanced/oke.config.ts +12 -57
- package/templates/advanced/package.json +3 -1
- package/templates/advanced/src/app.ts +3 -10
- package/templates/advanced/src/core.ts +58 -0
- package/templates/advanced/src/db/schema.decl.ts +1 -1
- package/templates/advanced/src/db/seed/index.ts +2 -2
- package/templates/advanced/src/flows/notes/index.ts +1 -13
- package/templates/advanced/src/locales/index.ts +6 -0
- package/templates/advanced/tests/advanced.test.ts +1 -1
- package/templates/standard/.env.example +23 -3
- package/templates/standard/README.md +1 -1
- package/templates/standard/drizzle.config.ts +10 -13
- package/templates/standard/oke.config.ts +10 -56
- package/templates/standard/package.json +3 -1
- package/templates/standard/src/app.ts +3 -10
- package/templates/standard/src/core.ts +55 -0
- package/templates/standard/src/db/schema.decl.ts +1 -1
- package/templates/standard/src/db/seed/index.ts +2 -2
- package/templates/standard/src/flows/notes/index.ts +1 -10
- package/templates/standard/src/locales/index.ts +6 -0
- package/templates/standard/tests/standard.test.ts +1 -1
- package/templates/advanced/src/core/channels.ts +0 -13
- package/templates/advanced/src/core/gates.ts +0 -12
- package/templates/advanced/src/core/index.ts +0 -12
- package/templates/advanced/src/core/store.ts +0 -8
- package/templates/advanced/src/core/vault.ts +0 -7
- package/templates/advanced/src/locales/ar.ts +0 -18
- package/templates/standard/src/core/channels.ts +0 -13
- package/templates/standard/src/core/gates.ts +0 -12
- package/templates/standard/src/core/index.ts +0 -12
- package/templates/standard/src/core/store.ts +0 -5
- package/templates/standard/src/core/vault.ts +0 -7
- package/templates/standard/src/locales/ar.ts +0 -18
package/README.md
CHANGED
|
@@ -50,8 +50,8 @@ model selection runs in the wizard **before** `bun install` (writes `.env.local`
|
|
|
50
50
|
|
|
51
51
|
Store-bearing scaffolds ship `drizzle.config.ts` and `.env.example`. Local
|
|
52
52
|
`oke dev` auto-runs `oke db push` on schema change and asks once whether to
|
|
53
|
-
seed;
|
|
54
|
-
|
|
53
|
+
seed; compose stack passwords are generated and preserved per project
|
|
54
|
+
(built-in vault needs no extra container). Both
|
|
55
55
|
templates include `.github/workflows/ci.yml` (typecheck + `bun test` on push/PR).
|
|
56
56
|
|
|
57
57
|
Every scaffold writes an `AGENTS.md` so coding agents know the OKE contract
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-oke",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Scaffold an okengine app — bunx create-oke@latest <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"test": "bun test"
|
|
25
|
+
"test": "bun test src"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0"
|
package/src/ai-setup/apply.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Write AI driver config, env, and `src/core
|
|
2
|
+
* Write AI driver config, env, and AI models into `src/core.ts` for `oke ai setup`.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -70,13 +70,15 @@ export function applyAiSetup(
|
|
|
70
70
|
: existsSync(envExample)
|
|
71
71
|
? readFileSync(envExample, "utf8")
|
|
72
72
|
: "";
|
|
73
|
-
// Driver / URL stay commented so docker mode can hydrate
|
|
74
|
-
// `docker/.env.docker` without being shadowed. The
|
|
75
|
-
// written
|
|
76
|
-
//
|
|
73
|
+
// Driver / URL / model stay commented so docker mode can hydrate from
|
|
74
|
+
// `docker/.env.docker` without being shadowed. The chosen model is still
|
|
75
|
+
// written into `.env.local` as a hint (and into `.env.docker` when present);
|
|
76
|
+
// `loadExistingStackControls` seeds commented `OKE_AI_MODEL` on first boot.
|
|
77
77
|
env = upsertEnv(env, "OKE_AI_DRIVER", input.driver, { comment: true });
|
|
78
78
|
if (input.baseUrl) env = upsertEnv(env, "OKE_AI_URL", input.baseUrl, { comment: true });
|
|
79
|
-
if (input.chatModel)
|
|
79
|
+
if (input.chatModel) {
|
|
80
|
+
env = upsertEnv(env, "OKE_AI_MODEL", input.chatModel, { comment: true });
|
|
81
|
+
}
|
|
80
82
|
if (input.visionModel) {
|
|
81
83
|
env = upsertEnv(env, "OKE_AI_VISION_MODEL", input.visionModel, { comment: true });
|
|
82
84
|
}
|
|
@@ -92,10 +94,7 @@ export function applyAiSetup(
|
|
|
92
94
|
}
|
|
93
95
|
writeFileSync(envPath, env.endsWith("\n") ? env : `${env}\n`, "utf8");
|
|
94
96
|
|
|
95
|
-
const aiTsPath =
|
|
96
|
-
mkdirSync(dirname(aiTsPath), { recursive: true });
|
|
97
|
-
writeFileSync(aiTsPath, renderAiTs(input), "utf8");
|
|
98
|
-
ensureAiImported(cwd);
|
|
97
|
+
const aiTsPath = writeAiModels(cwd, input);
|
|
99
98
|
|
|
100
99
|
return { configPath, envPath, aiTsPath };
|
|
101
100
|
}
|
|
@@ -105,11 +104,11 @@ export function applyAiSetup(
|
|
|
105
104
|
* @param driver - AI driver id
|
|
106
105
|
*/
|
|
107
106
|
export function upsertAiDrivers(source: string, driver: string): string {
|
|
107
|
+
const runtime = driver === "mock" ? "mock" : driver;
|
|
108
108
|
const block = `{
|
|
109
|
-
|
|
110
|
-
docker: "${driver === "mock" ? "mock" : driver}",
|
|
109
|
+
dev: "${runtime}",
|
|
111
110
|
test: "mock",
|
|
112
|
-
prod: "${
|
|
111
|
+
prod: "${runtime}",
|
|
113
112
|
}`;
|
|
114
113
|
// Top-level drivers.ai only (4-space indent) — never nest under channel.email.
|
|
115
114
|
const aiRe = /^( {4})ai:\s*\{[\s\S]*?\n\1\}/m;
|
|
@@ -226,16 +225,113 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
/**
|
|
229
|
-
*
|
|
228
|
+
* Write AI model declarations into `src/core.ts` (preferred) or legacy `src/core/ai.ts`.
|
|
229
|
+
*
|
|
230
|
+
* @param cwd - Project root
|
|
231
|
+
* @param input - Setup choices
|
|
232
|
+
* @returns Path written
|
|
233
|
+
*/
|
|
234
|
+
function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
235
|
+
const rendered = renderAiTs(input);
|
|
236
|
+
const coreTsPath = join(cwd, "src", "core.ts");
|
|
237
|
+
const legacyIndex = join(cwd, "src", "core", "index.ts");
|
|
238
|
+
|
|
239
|
+
// Folder layout still in the wild — keep writing a sidecar.
|
|
240
|
+
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
241
|
+
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
242
|
+
mkdirSync(dirname(aiTsPath), { recursive: true });
|
|
243
|
+
writeFileSync(aiTsPath, rendered, "utf8");
|
|
244
|
+
ensureLegacyAiImported(cwd);
|
|
245
|
+
return aiTsPath;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
mkdirSync(dirname(coreTsPath), { recursive: true });
|
|
249
|
+
if (existsSync(coreTsPath)) {
|
|
250
|
+
const existing = readFileSync(coreTsPath, "utf8");
|
|
251
|
+
if (hasAiModels(existing)) {
|
|
252
|
+
return coreTsPath;
|
|
253
|
+
}
|
|
254
|
+
writeFileSync(coreTsPath, mergeAiIntoCore(existing, rendered), "utf8");
|
|
255
|
+
} else {
|
|
256
|
+
writeFileSync(coreTsPath, rendered, "utf8");
|
|
257
|
+
}
|
|
258
|
+
ensureCoreImported(cwd);
|
|
259
|
+
return coreTsPath;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param source - Existing TypeScript
|
|
264
|
+
*/
|
|
265
|
+
function hasAiModels(source: string): boolean {
|
|
266
|
+
return (
|
|
267
|
+
/\bai\.model\s*\(/.test(source) ||
|
|
268
|
+
/from\s+["']\.\/(?:core\/)?ai["']/.test(source) ||
|
|
269
|
+
/import\s+["']\.\/(?:core\/)?ai["']/.test(source)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Merge rendered AI module into an existing `src/core.ts`.
|
|
275
|
+
*
|
|
276
|
+
* @param existing - Current core.ts
|
|
277
|
+
* @param rendered - Output of {@link renderAiTs}
|
|
278
|
+
*/
|
|
279
|
+
export function mergeAiIntoCore(existing: string, rendered: string): string {
|
|
280
|
+
const body = rendered.replace(/^import\s*\{\s*ai\s*\}\s*from\s*["']okengine["'];\s*\n*/m, "");
|
|
281
|
+
const next = ensureNamedOkengineImport(existing, "ai");
|
|
282
|
+
return `${next.trimEnd()}\n\n${body.trimStart()}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Ensure a named binding is imported from `"okengine"`.
|
|
287
|
+
*
|
|
288
|
+
* @param source - Module source
|
|
289
|
+
* @param name - Binding to add
|
|
290
|
+
*/
|
|
291
|
+
export function ensureNamedOkengineImport(source: string, name: string): string {
|
|
292
|
+
const re = /import\s*\{([^}]*)\}\s*from\s*["']okengine["']\s*;/;
|
|
293
|
+
const m = re.exec(source);
|
|
294
|
+
if (!m) {
|
|
295
|
+
return `import { ${name} } from "okengine";\n${source}`;
|
|
296
|
+
}
|
|
297
|
+
const names = m[1]!
|
|
298
|
+
.split(",")
|
|
299
|
+
.map((s) => s.trim())
|
|
300
|
+
.filter(Boolean);
|
|
301
|
+
if (names.includes(name)) return source;
|
|
302
|
+
const sorted = [...names, name].sort((a, b) => a.localeCompare(b));
|
|
303
|
+
return source.replace(re, `import { ${sorted.join(", ")} } from "okengine";`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Ensure `src/app.ts` loads `@/core` / `./core` so merged AI registers.
|
|
308
|
+
*
|
|
309
|
+
* @param cwd - Project root
|
|
310
|
+
*/
|
|
311
|
+
function ensureCoreImported(cwd: string): void {
|
|
312
|
+
const appPath = join(cwd, "src", "app.ts");
|
|
313
|
+
if (!existsSync(appPath)) return;
|
|
314
|
+
const src = readFileSync(appPath, "utf8");
|
|
315
|
+
if (
|
|
316
|
+
/from\s+["']@\/core["']/.test(src) ||
|
|
317
|
+
/import\s+["']@\/core["']/.test(src) ||
|
|
318
|
+
/from\s+["']\.\/core(?:\.ts)?["']/.test(src) ||
|
|
319
|
+
/import\s+["']\.\/core(?:\.ts)?["']/.test(src)
|
|
320
|
+
) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
writeFileSync(appPath, `import "@/core";\n${src}`, "utf8");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Legacy `src/core/*` layout — side-effect import the AI sidecar.
|
|
230
328
|
*
|
|
231
329
|
* @param cwd - Project root
|
|
232
330
|
*/
|
|
233
|
-
function
|
|
331
|
+
function ensureLegacyAiImported(cwd: string): void {
|
|
234
332
|
const candidates: ReadonlyArray<{ readonly rel: string; readonly importLine: string }> = [
|
|
235
333
|
{ rel: "src/app.ts", importLine: `import "./core/ai";\n` },
|
|
236
334
|
{ rel: "src/core/index.ts", importLine: `import "./ai";\n` },
|
|
237
|
-
// Legacy layout
|
|
238
|
-
{ rel: "src/core.ts", importLine: `import "./ai";\n` },
|
|
239
335
|
];
|
|
240
336
|
for (const { rel, importLine } of candidates) {
|
|
241
337
|
const path = join(cwd, rel);
|
package/src/ai-setup/catalog.ts
CHANGED
|
@@ -788,9 +788,8 @@ export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
|
|
|
788
788
|
{
|
|
789
789
|
id: "smollm2",
|
|
790
790
|
label: "SmolLM2",
|
|
791
|
-
hint: "
|
|
791
|
+
hint: "Lightest",
|
|
792
792
|
role: "chat",
|
|
793
|
-
recommended: true,
|
|
794
793
|
ramGb: 1,
|
|
795
794
|
tier: "ultra-fast",
|
|
796
795
|
modalities: ["text"],
|
|
@@ -951,8 +950,9 @@ export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
|
|
|
951
950
|
{
|
|
952
951
|
id: "granite3.3:2b",
|
|
953
952
|
label: "Granite 3.3 2B",
|
|
954
|
-
hint: "IBM 2B",
|
|
953
|
+
hint: "Default · IBM 2B",
|
|
955
954
|
role: "chat",
|
|
955
|
+
recommended: true,
|
|
956
956
|
ramGb: 2,
|
|
957
957
|
tier: "ultra-fast",
|
|
958
958
|
modalities: ["text", "code"],
|
|
@@ -68,7 +68,7 @@ export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
|
|
|
68
68
|
return {
|
|
69
69
|
driver: "openai-compatible",
|
|
70
70
|
baseUrl: process.env.OKE_AI_URL ?? "http://127.0.0.1:8080/v1",
|
|
71
|
-
chatModel: "
|
|
71
|
+
chatModel: "granite3.3:2b",
|
|
72
72
|
visionModel: null,
|
|
73
73
|
embedModel: null,
|
|
74
74
|
image: LLAMA_CPP_IMAGE,
|
package/src/ai-setup/prompts.ts
CHANGED
|
@@ -154,7 +154,7 @@ async function askLlamaCppPath(options: {
|
|
|
154
154
|
if (mode === null) return null;
|
|
155
155
|
|
|
156
156
|
if (mode === "manual") {
|
|
157
|
-
const id = await askOtherModelId("
|
|
157
|
+
const id = await askOtherModelId("granite3.3:2b");
|
|
158
158
|
if (id === null) return null;
|
|
159
159
|
return finishLlamaCpp(id);
|
|
160
160
|
}
|