create-oke 0.2.8 → 0.3.2

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 (142) hide show
  1. package/README.md +11 -32
  2. package/package.json +2 -4
  3. package/src/cli.test.ts +29 -117
  4. package/src/cli.ts +16 -139
  5. package/src/docs-origin.ts +2 -3
  6. package/src/index.ts +1 -2
  7. package/src/scaffold.ts +13 -31
  8. package/src/templates.ts +12 -90
  9. package/src/transform.ts +1 -1
  10. package/template/.env.example +56 -0
  11. package/{templates/standard → template}/README.md +25 -12
  12. package/template/bun.lock +845 -0
  13. package/template/drizzle.config.ts +25 -0
  14. package/{templates/full → template}/oke.config.ts +11 -7
  15. package/{templates/minimal → template}/src/core.ts +1 -1
  16. package/template/src/schema.decl.ts +12 -0
  17. package/examples/linkly/.env.example +0 -15
  18. package/examples/linkly/drizzle.config.ts +0 -11
  19. package/examples/linkly/oke.config.ts +0 -12
  20. package/examples/linkly/package.json +0 -18
  21. package/examples/linkly/src/app.ts +0 -21
  22. package/examples/linkly/src/core.ts +0 -4
  23. package/examples/linkly/src/flows/analytics/index.ts +0 -39
  24. package/examples/linkly/src/flows/links/index.ts +0 -82
  25. package/examples/linkly/src/flows/links/shapes.ts +0 -22
  26. package/examples/linkly/src/flows/links/signals.ts +0 -14
  27. package/examples/linkly/src/gates.ts +0 -10
  28. package/examples/linkly/src/schema.decl.ts +0 -39
  29. package/examples/linkly/src/schema.generated.ts +0 -30
  30. package/examples/linkly/src/schema.ts +0 -2
  31. package/examples/linkly/tests/linkly.test.ts +0 -19
  32. package/examples/notes/.env.example +0 -12
  33. package/examples/notes/drizzle.config.ts +0 -11
  34. package/examples/notes/oke.config.ts +0 -14
  35. package/examples/notes/package.json +0 -19
  36. package/examples/notes/src/app.ts +0 -13
  37. package/examples/notes/src/core.ts +0 -4
  38. package/examples/notes/src/flows/notes/index.ts +0 -58
  39. package/examples/notes/src/schema.ts +0 -9
  40. package/examples/notes/tests/notes.test.ts +0 -10
  41. package/examples/provisions/.env.example +0 -19
  42. package/examples/provisions/drizzle.config.ts +0 -11
  43. package/examples/provisions/oke.config.ts +0 -19
  44. package/examples/provisions/package.json +0 -18
  45. package/examples/provisions/src/app.ts +0 -60
  46. package/examples/provisions/src/channels.ts +0 -15
  47. package/examples/provisions/src/core.ts +0 -4
  48. package/examples/provisions/src/flows/notifications/index.ts +0 -25
  49. package/examples/provisions/src/flows/orders/index.ts +0 -97
  50. package/examples/provisions/src/flows/orders/shapes.ts +0 -21
  51. package/examples/provisions/src/flows/orders/signals.ts +0 -19
  52. package/examples/provisions/src/flows/payments/index.ts +0 -43
  53. package/examples/provisions/src/flows/payments/shapes.ts +0 -3
  54. package/examples/provisions/src/flows/payments/stripe.ts +0 -18
  55. package/examples/provisions/src/gates.ts +0 -4
  56. package/examples/provisions/src/locales/ar.ts +0 -1
  57. package/examples/provisions/src/locales/en.ts +0 -1
  58. package/examples/provisions/src/plugins/audit-schema.ts +0 -2
  59. package/examples/provisions/src/plugins/audit.ts +0 -14
  60. package/examples/provisions/src/schema.ts +0 -16
  61. package/examples/provisions/src/vault.ts +0 -16
  62. package/examples/provisions/tests/orders.test.ts +0 -19
  63. package/examples/skyport/.env.example +0 -34
  64. package/examples/skyport/drizzle.config.ts +0 -11
  65. package/examples/skyport/evals/triage.jsonl +0 -1
  66. package/examples/skyport/oke.config.ts +0 -47
  67. package/examples/skyport/oke.images.lock +0 -4
  68. package/examples/skyport/package.json +0 -19
  69. package/examples/skyport/src/ai.ts +0 -25
  70. package/examples/skyport/src/app.ts +0 -46
  71. package/examples/skyport/src/channels.ts +0 -12
  72. package/examples/skyport/src/core.ts +0 -4
  73. package/examples/skyport/src/flows/bookings/index.ts +0 -73
  74. package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
  75. package/examples/skyport/src/flows/bookings/signals.ts +0 -13
  76. package/examples/skyport/src/flows/notifications/index.ts +0 -16
  77. package/examples/skyport/src/flows/payments/index.ts +0 -14
  78. package/examples/skyport/src/flows/payments/shapes.ts +0 -5
  79. package/examples/skyport/src/flows/support/index.ts +0 -50
  80. package/examples/skyport/src/flows/users/elements.ts +0 -2
  81. package/examples/skyport/src/flows/users/index.ts +0 -12
  82. package/examples/skyport/src/flows/users/shapes.ts +0 -6
  83. package/examples/skyport/src/gates.ts +0 -10
  84. package/examples/skyport/src/journeys.ts +0 -9
  85. package/examples/skyport/src/locales/ar.ts +0 -4
  86. package/examples/skyport/src/locales/en.ts +0 -4
  87. package/examples/skyport/src/plugins/audit-schema.ts +0 -7
  88. package/examples/skyport/src/plugins/audit.ts +0 -14
  89. package/examples/skyport/src/plugins/panel.tsx +0 -4
  90. package/examples/skyport/src/schema.ts +0 -25
  91. package/examples/skyport/src/vault.ts +0 -24
  92. package/examples/skyport/tests/support.test.ts +0 -16
  93. package/src/sync-templates.ts +0 -77
  94. package/templates/full/.env.example +0 -24
  95. package/templates/full/README.md +0 -65
  96. package/templates/full/drizzle.config.ts +0 -14
  97. package/templates/full/package.json +0 -18
  98. package/templates/full/src/ai.ts +0 -11
  99. package/templates/full/src/app.ts +0 -34
  100. package/templates/full/src/channels.ts +0 -9
  101. package/templates/full/src/core.ts +0 -4
  102. package/templates/full/src/flows/main/index.ts +0 -45
  103. package/templates/full/src/flows/main/shapes.ts +0 -3
  104. package/templates/full/src/flows/main/signals.ts +0 -8
  105. package/templates/full/src/gates.ts +0 -4
  106. package/templates/full/src/locales/ar.ts +0 -3
  107. package/templates/full/src/locales/en.ts +0 -3
  108. package/templates/full/src/schema.ts +0 -8
  109. package/templates/full/src/vault.ts +0 -9
  110. package/templates/full/tests/full.test.ts +0 -11
  111. package/templates/hello/.env.example +0 -9
  112. package/templates/hello/README.md +0 -40
  113. package/templates/hello/oke.config.ts +0 -3
  114. package/templates/hello/package.json +0 -14
  115. package/templates/hello/src/app.ts +0 -10
  116. package/templates/hello/src/flows/hello/index.ts +0 -27
  117. package/templates/hello/tests/hello.test.ts +0 -10
  118. package/templates/minimal/.env.example +0 -13
  119. package/templates/minimal/README.md +0 -49
  120. package/templates/minimal/drizzle.config.ts +0 -14
  121. package/templates/minimal/oke.config.ts +0 -14
  122. package/templates/minimal/package.json +0 -18
  123. package/templates/minimal/src/app.ts +0 -13
  124. package/templates/minimal/src/flows/main/index.ts +0 -49
  125. package/templates/minimal/src/schema.ts +0 -8
  126. package/templates/minimal/tests/minimal.test.ts +0 -13
  127. package/templates/standard/.env.example +0 -21
  128. package/templates/standard/drizzle.config.ts +0 -14
  129. package/templates/standard/oke.config.ts +0 -51
  130. package/templates/standard/src/core.ts +0 -4
  131. package/templates/standard/src/schema.ts +0 -9
  132. /package/{templates/standard → template}/package.json +0 -0
  133. /package/{templates/standard → template}/src/app.ts +0 -0
  134. /package/{templates/standard → template}/src/channels.ts +0 -0
  135. /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
  136. /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
  137. /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
  138. /package/{templates/standard → template}/src/gates.ts +0 -0
  139. /package/{templates/standard → template}/src/locales/ar.ts +0 -0
  140. /package/{templates/standard → template}/src/locales/en.ts +0 -0
  141. /package/{templates/standard → template}/src/vault.ts +0 -0
  142. /package/{templates/standard → template}/tests/standard.test.ts +0 -0
@@ -1,73 +0,0 @@
1
- import { on, flow, gate, http } from "okengine";
2
- import { eq } from "drizzle-orm";
3
- import { db } from "../../core";
4
- import { member, fair } from "../../gates";
5
- import { orderPlaced, seatFeed } from "./signals";
6
- import { NewBooking, BookingId, BookingRow, FlightFull } from "./shapes";
7
- import { bookings, flights } from "../../schema";
8
-
9
- export const canBook = gate.policy("booking:create", ({ auth }) =>
10
- auth.scopes.has("booking:create"),
11
- );
12
-
13
- export const create = on(
14
- http.post("/bookings").gate(member, canBook, fair),
15
- flow({
16
- slo: { availability: "99.9%", latency: { p99: "200ms" } },
17
- in: NewBooking,
18
- out: BookingId,
19
- errors: { FlightFull },
20
- do: async ({ flightId, seats }, fx) => {
21
- const [flight] = await fx
22
- .store(db)
23
- .select()
24
- .from(flights)
25
- .where(eq(flights.id, flightId))
26
- .limit(1);
27
- if (!flight || flight.seatsAvailable < seats)
28
- return fx.fail("FlightFull", { seatsLeft: flight?.seatsAvailable ?? 0 });
29
-
30
- const id = fx.id();
31
- await fx.store(db).insert(bookings).values({
32
- id,
33
- userId: fx.auth.userId,
34
- flightId,
35
- seats,
36
- status: "pending",
37
- createdAt: Date.now(),
38
- });
39
- await fx.emit(orderPlaced, { orderId: id });
40
- await fx.emit(seatFeed, { flightId, left: flight.seatsAvailable - seats });
41
- return { id };
42
- },
43
- }),
44
- );
45
-
46
- export const mine = on(
47
- http.get("/bookings").gate(member).live(),
48
- flow({
49
- out: BookingRow.array(),
50
- do: (_, fx) => fx.store(db).select().from(bookings).where(eq(bookings.userId, fx.auth.userId)),
51
- }),
52
- );
53
-
54
- export const getBooking = flow({
55
- in: BookingId,
56
- out: BookingRow,
57
- do: async ({ id }, fx) => {
58
- const [b] = await fx.store(db).select().from(bookings).where(eq(bookings.id, id)).limit(1);
59
- return b;
60
- },
61
- });
62
-
63
- // The agent's second tool — refunding is a distinct, gated capability, never the same
64
- // permission as reading a booking, since the agent's tool list is exactly its authority.
65
- export const refundBooking = flow({
66
- in: BookingId,
67
- out: BookingRow,
68
- do: async ({ id }, fx) => {
69
- await fx.store(db).update(bookings).set({ status: "refunded" }).where(eq(bookings.id, id));
70
- const [b] = await fx.store(db).select().from(bookings).where(eq(bookings.id, id)).limit(1);
71
- return b;
72
- },
73
- });
@@ -1,6 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const NewBooking = z.object({ flightId: z.string(), seats: z.number().min(1).max(9) });
4
- export const BookingId = z.object({ id: z.string() });
5
- export const BookingRow = z.object({ id: z.string(), status: z.string(), seats: z.number() });
6
- export const FlightFull = z.object({ seatsLeft: z.number() });
@@ -1,13 +0,0 @@
1
- import { signal } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const orderPlaced = signal("order-placed", {
5
- schema: z.object({ orderId: z.string() }),
6
- delivery: "once",
7
- retries: 5,
8
- deadLetter: true,
9
- });
10
- export const seatFeed = signal("seat-feed", {
11
- schema: z.object({ flightId: z.string(), left: z.number() }),
12
- delivery: "live",
13
- });
@@ -1,16 +0,0 @@
1
- import { on, flow } from "okengine";
2
- import { bookingConfirmed } from "../../channels";
3
- import { orderPlaced } from "../bookings/signals";
4
-
5
- export const send = on(
6
- orderPlaced,
7
- flow({
8
- name: "notifications.send",
9
- do: async ({ id }, fx) => {
10
- await fx.send(bookingConfirmed, {
11
- to: fx.auth.userId ?? "guest",
12
- data: { name: "Traveler", bookingId: id },
13
- });
14
- },
15
- }),
16
- );
@@ -1,14 +0,0 @@
1
- import { flow } from "okengine";
2
- import { stripeKey } from "../../vault";
3
- import { ChargeIn } from "./shapes";
4
-
5
- export const chargeBooking = flow({
6
- name: "payments.chargeBooking",
7
- durable: true,
8
- in: ChargeIn,
9
- do: async ({ orderId }, fx) => {
10
- const intent = await fx.step("create-intent", () => fx.vault(stripeKey));
11
- await fx.clock.sleep("verify-window", "2m");
12
- return fx.step("confirm", () => ({ orderId, intent }));
13
- },
14
- });
@@ -1,5 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const ChargeIn = z.object({
4
- orderId: z.string(),
5
- });
@@ -1,50 +0,0 @@
1
- import { on, flow, http } from "okengine";
2
- import { z } from "zod";
3
- import { triage, support, embed, smart, fast } from "../../ai";
4
- import { member } from "../../gates";
5
- import { db } from "../../core";
6
- import { tickets } from "../../schema";
7
-
8
- // ① A prompt call with a provider fallback chain and a validated result
9
- export const createTicket = on(
10
- http.post("/tickets").gate(member),
11
- flow({
12
- in: z.object({ subject: z.string(), body: z.string() }),
13
- out: z.object({ id: z.string(), urgency: z.string() }),
14
- do: async (input, fx) => {
15
- const t = await fx.ask(triage, input, { via: [smart, fast] });
16
- const id = fx.id();
17
- await fx
18
- .store(db)
19
- .insert(tickets)
20
- .values({ id, ...input, ...t });
21
- return { id, urgency: t.urgency };
22
- },
23
- }),
24
- );
25
- // effects → writes[sql:tickets] asks[ticket-triage v3] cost[~$0.01] nondeterministic
26
-
27
- // ② RAG — retrieve, then answer with streaming tokens
28
- export const askDocs = on(
29
- http.post("/ask").gate(member).live(),
30
- flow({
31
- in: z.object({ question: z.string() }),
32
- do: async ({ question }, fx) => {
33
- const context = await fx.search(embed, question, { topK: 5 });
34
- return fx.stream(smart, { prompt: "answer-with-context", data: { question, context } });
35
- // streaming reaches the client through the Signal element — no separate socket layer
36
- },
37
- }),
38
- );
39
-
40
- // ③ A durable, bounded agent
41
- export const supportAgent = on(
42
- http.post("/support").gate(member),
43
- flow({
44
- durable: true, // nondeterministic calls are ALWAYS journaled
45
- in: z.object({ message: z.string() }),
46
- do: ({ message }, fx) => fx.run(support, { message }),
47
- // the agent can only call getBooking and refundBooking, and only within THIS user's
48
- // gates and tenant scope — it cannot exceed what the code declares
49
- }),
50
- );
@@ -1,2 +0,0 @@
1
- /** User-unit element stubs (Skyport tree). */
2
- export const usersUnit = "users";
@@ -1,12 +0,0 @@
1
- import { on, flow, http } from "okengine";
2
- import { member } from "../../gates";
3
- import { UserProfile } from "./shapes";
4
-
5
- export const me = on(
6
- http.get("/me").gate(member),
7
- flow({
8
- name: "users.me",
9
- out: UserProfile,
10
- do: (_input, fx) => ({ id: fx.auth.userId ?? "anon" }),
11
- }),
12
- );
@@ -1,6 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const UserProfile = z.object({
4
- id: z.string(),
5
- email: z.string().email().optional(),
6
- });
@@ -1,10 +0,0 @@
1
- import { gate } from "okengine";
2
-
3
- export const member = gate.policy("member", ({ auth }) => !!auth?.verified);
4
-
5
- export const fair = gate.rate({
6
- strategy: "sliding-window-counter",
7
- max: 300,
8
- per: "1m",
9
- keyBy: "user",
10
- });
@@ -1,9 +0,0 @@
1
- import { journey } from "okengine";
2
- import { create } from "./flows/bookings";
3
- import { chargeBooking } from "./flows/payments";
4
- import { send } from "./flows/notifications";
5
-
6
- journey("book-a-flight", {
7
- path: [create, chargeBooking, send],
8
- slo: { availability: "99.5%" },
9
- });
@@ -1,4 +0,0 @@
1
- export default {
2
- "booking.confirmed": "تم تأكيد حجزك",
3
- "support.ticket": "تم استلام تذكرة الدعم",
4
- } as const;
@@ -1,4 +0,0 @@
1
- export default {
2
- "booking.confirmed": "Your booking is confirmed",
3
- "support.ticket": "Support ticket received",
4
- } as const;
@@ -1,7 +0,0 @@
1
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
2
-
3
- export const auditLog = sqliteTable("audit_log", {
4
- id: text("id").primaryKey(),
5
- at: integer("at").notNull(),
6
- payload: text("payload"),
7
- });
@@ -1,14 +0,0 @@
1
- import { plugin, store } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const audit = plugin("audit", { version: "1.0.0" })
5
- .config(z.object({ retain: z.string().default("2y") }))
6
- .element(store.sql("audit", { schema: () => import("./audit-schema") }))
7
- .needs("store.kv")
8
- .decorate("audit", { enabled: true })
9
- .hook("afterHandle", async (ctx, fx) => {
10
- if (ctx.trigger.meta?.audit) await fx.store("audit").log(ctx);
11
- })
12
- .errors({ AuditWriteFailed: z.object({ reason: z.string() }) })
13
- .consolePanel({ id: "audit", title: "Audit Trail", entry: "./panel.tsx" })
14
- .cli("audit:export", ({ fx }) => fx.store("audit").exportCsv());
@@ -1,4 +0,0 @@
1
- /** Audit Console panel stub (Skyport tree). */
2
- export default function AuditPanel() {
3
- return null;
4
- }
@@ -1,25 +0,0 @@
1
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
2
- import { id } from "okengine/store";
3
-
4
- export const bookings = sqliteTable("bookings", {
5
- id: text("id").primaryKey().$defaultFn(id),
6
- userId: text("user_id").notNull(),
7
- flightId: text("flight_id").notNull(),
8
- seats: integer("seats").notNull(),
9
- status: text("status").notNull().default("pending"),
10
- createdAt: integer("created_at").notNull(),
11
- });
12
-
13
- export const flights = sqliteTable("flights", {
14
- id: text("id").primaryKey(),
15
- seatsAvailable: integer("seats_available").notNull(),
16
- });
17
-
18
- export const tickets = sqliteTable("tickets", {
19
- id: text("id").primaryKey(),
20
- subject: text("subject").notNull(),
21
- body: text("body").notNull(),
22
- urgency: text("urgency"),
23
- team: text("team"),
24
- summary: text("summary"),
25
- });
@@ -1,24 +0,0 @@
1
- import { vault } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const dbUrl = vault.secret("DATABASE_URL", {
5
- schema: z.string().url(),
6
- dev: vault.fromDocker("store.sql"),
7
- });
8
-
9
- export const dbReplica1 = vault.secret("DATABASE_REPLICA_1", {
10
- schema: z.string().url(),
11
- dev: "postgres://localhost/oke-replica",
12
- });
13
-
14
- export const anthropicKey = vault.secret("ANTHROPIC_KEY", {
15
- schema: z.string().min(1),
16
- dev: "sk-ant-test",
17
- });
18
-
19
- export const stripeKey = vault.secret("STRIPE_KEY", {
20
- schema: z.string().startsWith("sk_"),
21
- description: "Payments gateway key",
22
- rotate: "90d",
23
- dev: "sk_test_local",
24
- });
@@ -1,16 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { createTestApp } from "okengine/test";
3
- import { app } from "../src/app";
4
- import { triage } from "../src/ai";
5
-
6
- test("AI triage is mockable and cost-bounded", async () => {
7
- const t = await createTestApp(app);
8
- t.ai.mock(triage, { urgency: "high", team: "ops", summary: "seat dispute" });
9
-
10
- const u = await t.auth.loginAs({});
11
- const { data } = await t.api.support.createTicket({ subject: "…", body: "…" }, { as: u });
12
-
13
- expect(data!.urgency).toBe("high");
14
- expect(t.ai.cost()).toBeLessThan(0.02); // budgets are assertable
15
- await t.close();
16
- });
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * prepack: copy repo-root `templates/` and `examples/` into this package so the
4
- * published npm package can scaffold without the monorepo checkout.
5
- *
6
- * Applies the same skip rules as runtime scaffold (no node_modules, no
7
- * monorepo-only docker.test.ts).
8
- */
9
-
10
- import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
11
- import { join, relative, resolve } from "node:path";
12
- import { EXAMPLES, packageRoot, TEMPLATES } from "./templates.ts";
13
- import { shouldSkipTemplatePath } from "./transform.ts";
14
-
15
- const pkg = packageRoot();
16
- const templatesRoot = resolve(pkg, "../../templates");
17
- const examplesRoot = resolve(pkg, "../../examples");
18
- const outTemplates = join(pkg, "templates");
19
- const outExamples = join(pkg, "examples");
20
-
21
- if (!existsSync(templatesRoot)) {
22
- console.error(`create-oke prepack: templates/ not found at ${templatesRoot}`);
23
- process.exit(1);
24
- }
25
- if (!existsSync(examplesRoot)) {
26
- console.error(`create-oke prepack: examples/ not found at ${examplesRoot}`);
27
- process.exit(1);
28
- }
29
-
30
- rmSync(outTemplates, { recursive: true, force: true });
31
- rmSync(outExamples, { recursive: true, force: true });
32
- mkdirSync(outTemplates, { recursive: true });
33
- mkdirSync(outExamples, { recursive: true });
34
-
35
- for (const id of TEMPLATES) {
36
- const src = join(templatesRoot, id);
37
- const dst = join(outTemplates, id);
38
- if (!existsSync(src)) {
39
- console.error(`create-oke prepack: missing template ${src}`);
40
- process.exit(1);
41
- }
42
- copyFiltered(src, dst, src);
43
- console.log(`create-oke prepack: template ${id}`);
44
- }
45
-
46
- for (const id of EXAMPLES) {
47
- const src = join(examplesRoot, id);
48
- const dst = join(outExamples, id);
49
- if (!existsSync(src)) {
50
- console.error(`create-oke prepack: missing example ${src}`);
51
- process.exit(1);
52
- }
53
- copyFiltered(src, dst, src);
54
- console.log(`create-oke prepack: example ${id}`);
55
- }
56
-
57
- /**
58
- * @param src - Source file or directory
59
- * @param dst - Destination path
60
- * @param root - Template root for relative skip paths
61
- */
62
- function copyFiltered(src: string, dst: string, root: string): void {
63
- const st = statSync(src);
64
- const rel = relative(root, src).split(/[/\\]/).join("/");
65
- if (rel && shouldSkipTemplatePath(rel)) return;
66
- if (st.isDirectory()) {
67
- mkdirSync(dst, { recursive: true });
68
- for (const entry of readdirSync(src)) {
69
- copyFiltered(join(src, entry), join(dst, entry), root);
70
- }
71
- return;
72
- }
73
- if (st.isFile()) {
74
- mkdirSync(join(dst, ".."), { recursive: true });
75
- cpSync(src, dst);
76
- }
77
- }
@@ -1,24 +0,0 @@
1
- # Environment template — copy to `.env.local` for local overrides.
2
- # create-oke copies this file to `.env.local` when you scaffold a new project.
3
- #
4
- # Resolution order (first hit wins):
5
- # process.env → .env.local → docker/.env.docker → vault driver → dev fallback
6
- #
7
- # --- Vault (app secrets) ---
8
-
9
- # APP_SECRET — signs sessions and internal tokens; generate with `openssl rand -hex 32`
10
- # Local scaffold default (also set as vault `dev:` fallback in src/vault.ts).
11
- APP_SECRET=dev-only-secret
12
-
13
- # --- Infrastructure (optional) ---
14
- # Commented — this app pins postgres, redis, and smtp (Mailpit) in oke.config.ts.
15
- # Run `oke dev --docker` to write role-accurate keys into `docker/.env.docker`.
16
-
17
- # DATABASE_URL — primary Postgres connection; from your host or `docker/.env.docker` after `oke dev --docker`
18
- # DATABASE_URL=postgres://user:password@127.0.0.1:5432/oke
19
-
20
- # REDIS_URL — Redis/Valkey connection for store.kv; from your host or `docker/.env.docker` after `oke dev --docker`
21
- # REDIS_URL=redis://:password@127.0.0.1:6379
22
-
23
- # SMTP_URL — outbound email (Mailpit in docker dev); from your provider or `docker/.env.docker` after `oke dev --docker`
24
- # SMTP_URL=smtp://127.0.0.1:1025
@@ -1,65 +0,0 @@
1
- # full
2
-
3
- **Every element present and wired** — no business logic. Use when you want the
4
- complete surface ready to fill in.
5
-
6
- ## Run
7
-
8
- ```bash
9
- bun install
10
- oke dev # app :6530 · Console :6533 · MCP :6535
11
- # or: oke mode docker && oke dev # postgres · redis · Mailpit · RustFS · sops
12
- ```
13
-
14
- Local `oke dev` auto-runs `oke db push` when `schema.ts` changes (opt out:
15
- `--no-db-push`). Docker/prod never auto-apply DDL — use `oke db migrate`.
16
-
17
- | Port | Try |
18
- | ------- | ------------------------- |
19
- | `:6530` | `GET /` · `GET /health` |
20
- | `:6533` | Console |
21
- | `:6535` | MCP |
22
-
23
- ## What’s in this template
24
-
25
- ```text
26
- full/
27
- ├── .env.example # copied to .env.local by create-oke
28
- ├── drizzle.config.ts # oke db push|generate|migrate
29
- ├── oke.config.ts # local: sqlite · docker/prod: postgres
30
- ├── src/
31
- │ ├── app.ts # gates, secrets, signals, stores, channel, ai
32
- │ ├── core.ts / schema.ts # Store
33
- │ ├── gates.ts # open gate (used on HTTP)
34
- │ ├── vault.ts # appSecret
35
- │ ├── channels.ts # welcome template
36
- │ ├── ai.ts # stub model + echo prompt
37
- │ ├── locales/en.ts · ar.ts
38
- │ └── flows/main/
39
- │ ├── index.ts # GET / · GET /health · every("1d")
40
- │ ├── shapes.ts
41
- │ └── signals.ts # pinged
42
- └── tests/full.test.ts
43
- ```
44
-
45
- - **Elements wired:** Flow · Store · Signal · Clock (`every`) · Gate · Vault · Channel · AI
46
- - **Unit:** `main`
47
- - **App `$options`:** `gates`, `secrets`, `signals`, `stores`, `channel`, `ai`
48
- - **Flows:** `root`, `health` (emits `pinged`), plus a trivial daily cron
49
-
50
- Replace stubs with real models, prompts, channels, and domain flows. Prefer
51
- `standard` if you do not need AI / clock / vault on day one.
52
-
53
- ## Docker mode (`oke dev --docker`)
54
-
55
- Closest to production protocols: Postgres, Redis, SMTP (Mailpit), S3 (RustFS),
56
- vault `sops`/age. Compose credentials land in `docker/.env.docker`.
57
-
58
- | Surface | URL |
59
- | -------------- | ------------------------------------------------ |
60
- | Mailpit UI | [http://127.0.0.1:8025](http://127.0.0.1:8025) |
61
- | RustFS console | [http://127.0.0.1:9001](http://127.0.0.1:9001) |
62
-
63
- ## Agent contract
64
-
65
- See [`AGENTS.md`](./AGENTS.md). Handbook: [okengine.vercel.app/docs](https://okengine.vercel.app/docs)
@@ -1,14 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- /**
4
- * Domain schema sync for `oke db push|generate|migrate`.
5
- * Versioned prod migrations land in `./drizzle` — never mix with `.oke/`.
6
- */
7
- export default defineConfig({
8
- dialect: "sqlite",
9
- schema: "./src/schema.ts",
10
- out: "./drizzle",
11
- dbCredentials: {
12
- url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
13
- },
14
- });
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@oke/template-full",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "dependencies": {
7
- "okengine": "file:../..",
8
- "drizzle-orm": "^1.0.0-rc.4",
9
- "zod": "^4.4.3"
10
- },
11
- "devDependencies": {
12
- "drizzle-kit": "^1.0.0-rc.4"
13
- },
14
- "scripts": {
15
- "test": "bun test",
16
- "dev": "oke dev"
17
- }
18
- }
@@ -1,11 +0,0 @@
1
- import { ai } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const stub = ai.model("stub", { provider: "anthropic", tier: "haiku" });
5
-
6
- /** Stub prompt — replace with real versioned prompts. */
7
- export const echo = stub.prompt("echo", {
8
- in: z.object({ text: z.string() }),
9
- out: z.object({ text: z.string() }),
10
- version: 1,
11
- });
@@ -1,34 +0,0 @@
1
- import { db } from "./core";
2
- import { open } from "./gates";
3
- import { appSecret } from "./vault";
4
- import { welcome } from "./channels";
5
- import { stub, echo } from "./ai";
6
- import { pinged } from "./flows/main/signals";
7
- import "./channels";
8
- import "./gates";
9
- import "./ai";
10
- import "./locales/en";
11
- import "./locales/ar";
12
-
13
- import { oke } from "okengine";
14
- import * as main from "./flows/main";
15
-
16
- export const app = oke({ name: "full" }).adopt({ main });
17
-
18
- export type App = typeof app;
19
-
20
- Object.assign(app.$options, {
21
- env: "test",
22
- gates: [open],
23
- secrets: [appSecret],
24
- signals: [pinged],
25
- stores: [db],
26
- channel: {
27
- templates: [welcome],
28
- defaultLocale: "en",
29
- },
30
- ai: {
31
- models: [stub],
32
- prompts: [echo],
33
- },
34
- });
@@ -1,9 +0,0 @@
1
- import { channel } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const mail = channel.email({ from: "App <no-reply@example.com>" });
5
-
6
- /** Stub channel template — replace with real copy. */
7
- export const welcome = mail.template("welcome", {
8
- schema: z.object({ name: z.string() }),
9
- });
@@ -1,4 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "./schema";
3
-
4
- export const db = store.sql("app", { schema });
@@ -1,45 +0,0 @@
1
- import { on, flow, http, every } from "okengine";
2
- import { z } from "zod";
3
- import { open } from "../../gates";
4
- import { Health } from "./shapes";
5
- import { pinged } from "./signals";
6
-
7
- /** First-run welcome — visit :6530/ after `oke dev`. */
8
- export const root = on(
9
- http.get("/").gate(open),
10
- flow({
11
- out: z.object({
12
- ok: z.literal(true),
13
- console: z.string(),
14
- try: z.string(),
15
- }),
16
- do: () => ({
17
- ok: true as const,
18
- console: "http://127.0.0.1:6533",
19
- try: "/health",
20
- }),
21
- }),
22
- );
23
-
24
- export const health = on(
25
- http.get("/health").gate(open),
26
- flow({
27
- out: Health,
28
- do: async (_input, fx) => {
29
- await fx.emit(pinged, { at: Date.now() });
30
- return { ok: true as const };
31
- },
32
- }),
33
- );
34
-
35
- on(pinged, flow({
36
- do: async () => {},
37
- }));
38
-
39
- /** Trivial cron — replace with real schedules. */
40
- on(
41
- every("1d"),
42
- flow({
43
- do: async () => {},
44
- }),
45
- );
@@ -1,3 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const Health = z.object({ ok: z.literal(true) });