okengine 0.2.1 → 0.2.3

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 (59) hide show
  1. package/README.md +8 -8
  2. package/package.json +2 -1
  3. package/src/cli/dev-app-runner.ts +6 -2
  4. package/src/cli/dev.ts +84 -15
  5. package/src/cli/doctor.ts +3 -18
  6. package/src/cli/index.ts +4 -1
  7. package/src/cli/json-out.test.ts +1 -1
  8. package/src/cli/load-config.images.test.ts +75 -0
  9. package/src/cli/load-config.ts +67 -2
  10. package/src/cli/ports.test.ts +51 -0
  11. package/src/cli/ports.ts +81 -0
  12. package/src/cli/registry.help.test.ts +18 -0
  13. package/src/cli/registry.ts +0 -4
  14. package/src/cli/safe-defaults.test.ts +3 -3
  15. package/src/console/server/app.ts +2 -1
  16. package/src/console/server/claim.test.ts +7 -4
  17. package/src/console/server/claim.ts +2 -7
  18. package/src/console/server/flows.ts +1 -0
  19. package/src/console/server/index.ts +5 -0
  20. package/src/console/server/operator-db.test.ts +75 -0
  21. package/src/console/server/operator-db.ts +324 -0
  22. package/src/console/server/serve.ts +39 -0
  23. package/src/console/server/state.ts +13 -1
  24. package/src/console/ui/dist/assets/index-Bnf_3Hei.js +10 -0
  25. package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
  26. package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
  27. package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
  28. package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
  29. package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
  30. package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
  31. package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
  32. package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
  33. package/src/console/ui/dist/assets/panel-overview-Dt_AeXgd.js +1 -0
  34. package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
  35. package/src/console/ui/dist/assets/panel-runs-DGstFHeq.js +1 -0
  36. package/src/console/ui/dist/assets/panel-signals-DzEa2Fnt.js +1 -0
  37. package/src/console/ui/dist/assets/panel-store-BJkbNgxx.js +1 -0
  38. package/src/console/ui/dist/assets/panel-traces-BaRVO3gM.js +1 -0
  39. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
  40. package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
  41. package/src/console/ui/dist/assets/style-C8MxEWPd.css +3 -0
  42. package/src/console/ui/dist/favicon.svg +7 -0
  43. package/src/console/ui/dist/index.html +15 -0
  44. package/src/console/ui/shell/App.tsx +34 -2
  45. package/src/console/ui/shell/components/oke-logo.tsx +40 -0
  46. package/src/console/ui/shell/index.html +1 -0
  47. package/src/console/ui/shell/layout/Shell.tsx +2 -3
  48. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +8 -0
  49. package/src/console/ui/shell/public/favicon.svg +7 -0
  50. package/src/console/ui/shell/setup/Wizard.tsx +5 -2
  51. package/src/docker/derive.ts +3 -1
  52. package/src/docker/dockerfile.integration.test.ts +1 -1
  53. package/src/docker/stack.integration.test.ts +1 -1
  54. package/src/docs-origin.ts +19 -0
  55. package/src/kernel/errors.registry.test.ts +4 -2
  56. package/src/kernel/errors.ts +3 -1
  57. package/src/kernel/fx.test.ts +2 -2
  58. package/src/term.test.ts +51 -0
  59. package/src/term.ts +158 -0
@@ -188,6 +188,14 @@ export function OverviewPanel() {
188
188
  </header>
189
189
 
190
190
  <main id="overview-main" className="flex flex-col gap-8 px-6 py-6">
191
+ {!loading && !manifestQuery.data ? (
192
+ <p className="text-sm text-[var(--oke-muted)]">
193
+ No Manifest yet — save a Flow under{" "}
194
+ <code className="font-mono text-[var(--oke-fg)]">src/</code> and
195
+ the Console will refresh.
196
+ </p>
197
+ ) : null}
198
+
191
199
  {view.hasDeclaredSlos ? (
192
200
  <BudgetStrip
193
201
  label="Error budgets"
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
2
+ <rect width="96" height="96" rx="20" fill="#0D0D0D"/>
3
+ <path
4
+ d="M70.2 74.5C68.5 74.75 66.6 74.88 64.5 74.88H32.1C27 74.88 23 74.15 20.1 72.7C17.2 71.25 15.1 69.05 13.9 66.15C12.7 63.25 12.1 59.65 12.1 55.35V30.15C12.1 25.95 12.7 22.4 13.9 19.55C15.1 16.65 17.15 14.45 20 13C22.9 11.45 26.95 10.7 32.1 10.7H64.5C66.6 10.7 68.5 10.83 70.2 11.08V21.05H83.1C83.95 23.6 84.4 26.6 84.4 30.15V55.35C84.4 58.75 84 61.75 83.2 64.3H70.2V74.5ZM35.3 64.4H61C63.7 64.4 65.75 63.7 67.2 62.3C68.75 60.9 69.5 59 69.5 56.6V28.85C69.5 26.75 68.75 24.95 67.2 23.5C65.65 21.95 63.55 21.2 61 21.2H35.3C32.7 21.2 30.55 21.9 28.95 23.3C27.35 24.7 26.55 26.55 26.55 28.85V56.6C26.55 59.05 27.3 61 28.85 62.4C30.45 63.7 32.6 64.4 35.3 64.4Z"
5
+ fill="#FFFFFF"
6
+ />
7
+ </svg>
@@ -6,6 +6,7 @@
6
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7
7
  import { useState, type FormEvent } from "react";
8
8
  import { consoleCalls, setAccessToken } from "../client.ts";
9
+ import { OkeLogo } from "../components/oke-logo.tsx";
9
10
  import { Button, Field, Input } from "../components/ui.tsx";
10
11
 
11
12
  /**
@@ -55,7 +56,8 @@ export function SetupWizard() {
55
56
  if (status.data?.setupClosed) {
56
57
  return (
57
58
  <section className="mx-auto flex max-w-md flex-col gap-4 px-6 py-16">
58
- <h1 className="text-2xl font-semibold tracking-tight">oke Console</h1>
59
+ <OkeLogo className="h-7 w-auto text-[var(--oke-fg)]" />
60
+ <h1 className="text-2xl font-semibold tracking-tight">Console</h1>
59
61
  <p className="text-[var(--oke-muted)]">
60
62
  Setup is closed. Sign in with an existing operator account.
61
63
  </p>
@@ -73,10 +75,11 @@ export function SetupWizard() {
73
75
  return (
74
76
  <section className="mx-auto flex max-w-md flex-col gap-6 px-6 py-16">
75
77
  <header className="flex flex-col gap-2">
78
+ <OkeLogo className="h-7 w-auto text-[var(--oke-fg)]" />
76
79
  <p className="text-xs uppercase tracking-[0.2em] text-[var(--oke-muted)]">
77
80
  First admin
78
81
  </p>
79
- <h1 className="text-3xl font-semibold tracking-tight">oke Console</h1>
82
+ <h1 className="text-3xl font-semibold tracking-tight">Console</h1>
80
83
  <p className="text-[var(--oke-muted)]">
81
84
  Enter the claim code printed once to the boot log. This wizard closes
82
85
  permanently after the first operator.
@@ -23,7 +23,9 @@ import type { DeriveOptions, DeriveResult, GeneratedFile } from "./types.ts";
23
23
  */
24
24
  export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
25
25
  if (!options.images || Object.keys(options.images).length === 0) {
26
- throw new Error("oke docker: no images configured — set `images` in oke.config.ts");
26
+ throw new Error(
27
+ "oke docker: no images configured — set `images` in oke.config.ts (or prod drivers postgres/redis for defaults)",
28
+ );
27
29
  }
28
30
 
29
31
  const specs = buildSpecs(options);
@@ -73,7 +73,7 @@ console.log("listening", server.port);
73
73
 
74
74
  // No lockfile — adjust Dockerfile install for the fixture.
75
75
  const derived = deriveInfrastructure({
76
- images: { "store.sql": "postgres:16-alpine" },
76
+ images: { "store.sql": "postgres:18-alpine" },
77
77
  credentials: {
78
78
  "store.sql": {
79
79
  user: "oke",
@@ -35,7 +35,7 @@ describe("oke dev --stack postgres integration", () => {
35
35
  const project = `oke-pg-${Date.now()}`;
36
36
  try {
37
37
  const derived = deriveInfrastructure({
38
- images: { "store.sql": "postgres:16-alpine" },
38
+ images: { "store.sql": "postgres:18-alpine" },
39
39
  credentials: {
40
40
  "store.sql": {
41
41
  user: "oke",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Canonical public docs origin for error links, README, and the site.
3
+ *
4
+ * Flip this one constant when the custom domain is attached:
5
+ * `https://oke.omqkhafi.dev`
6
+ */
7
+
8
+ /** Live docs site (Vercel). Planned cutover: `https://oke.omqkhafi.dev`. */
9
+ export const DOCS_ORIGIN = "https://okengine.vercel.app" as const;
10
+
11
+ /**
12
+ * Absolute docs URL for a path under the handbook / site.
13
+ *
14
+ * @param path - Path beginning with `/` (e.g. `/docs`, `/e/1001`)
15
+ */
16
+ export function docsUrl(path: string): string {
17
+ const normalized = path.startsWith("/") ? path : `/${path}`;
18
+ return `${DOCS_ORIGIN}${normalized}`;
19
+ }
@@ -27,10 +27,12 @@ describe("OKE error-code registry", () => {
27
27
  }
28
28
  });
29
29
 
30
- test("OkeError docsUrl matches https://oke.dev/e/{code}", () => {
30
+ test("OkeError docsUrl matches docs origin /e/{code}", () => {
31
31
  for (const def of Object.values(OKE_ERRORS) as OkeErrorDefinition[]) {
32
32
  const err = new OkeError(def);
33
- expect(err.docsUrl).toBe(`https://oke.dev/e/${def.code}`);
33
+ expect(err.docsUrl).toBe(
34
+ `https://okengine.vercel.app/e/${def.code}`,
35
+ );
34
36
  expect(err.message).toContain(err.docsUrl);
35
37
  expect(err.message).toContain(`OKE${def.code}`);
36
38
  expect(err.message).toContain("→");
@@ -6,6 +6,8 @@
6
6
  * permanent from the moment they enter this registry.
7
7
  */
8
8
 
9
+ import { docsUrl as absoluteDocsUrl } from "../docs-origin.ts";
10
+
9
11
  /** Numeric OKE error code (permanent once published). */
10
12
  export type OkeErrorCode = number;
11
13
 
@@ -45,7 +47,7 @@ export class OkeError extends Error {
45
47
  constructor(definition: OkeErrorDefinition, params: OkeErrorParams = {}) {
46
48
  const causeText = interpolate(definition.cause, params);
47
49
  const fix = interpolate(definition.fix, params);
48
- const docsUrl = `https://oke.dev/e/${definition.code}`;
50
+ const docsUrl = absoluteDocsUrl(`/e/${definition.code}`);
49
51
  const message = formatOkeMessage(definition.code, causeText, fix, docsUrl);
50
52
  super(message);
51
53
  this.name = "OkeError";
@@ -43,7 +43,7 @@ describe("fx — capability enforcement", () => {
43
43
  expect(oke.fix).toBe(
44
44
  'Add "sql:users" to this flow\'s effects.reads.',
45
45
  );
46
- expect(oke.docsUrl).toBe("https://oke.dev/e/1001");
46
+ expect(oke.docsUrl).toBe("https://okengine.vercel.app/e/1001");
47
47
  expect(oke.message).toBe(
48
48
  formatOkeMessage(1001, oke.causeText, oke.fix, oke.docsUrl),
49
49
  );
@@ -218,7 +218,7 @@ describe("errors — registry", () => {
218
218
  });
219
219
  expect(err.message).toContain("OKE1042");
220
220
  expect(err.message).toContain("no subscriber");
221
- expect(err.message).toContain("https://oke.dev/e/1042");
221
+ expect(err.message).toContain("https://okengine.vercel.app/e/1042");
222
222
  expect(err.message).toContain("→");
223
223
  });
224
224
 
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Terminal chrome — color off stays plain; claim/banner keep structure.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import {
7
+ formatAppReadyLine,
8
+ formatClaimNote,
9
+ formatDevBanner,
10
+ formatServiceLine,
11
+ formatStatusLine,
12
+ termStyle,
13
+ } from "./term.ts";
14
+
15
+ describe("term", () => {
16
+ test("termStyle(false) strips ANSI", () => {
17
+ const s = termStyle(false);
18
+ expect(s.cyan).toBe("");
19
+ expect(s.bold).toBe("");
20
+ });
21
+
22
+ test("formatDevBanner is readable without color", () => {
23
+ const out = formatDevBanner({ color: false });
24
+ expect(out).toContain("oke dev");
25
+ expect(out).toContain("Starting");
26
+ expect(out).toContain("watching");
27
+ expect(out).not.toMatch(/\u001b\[/);
28
+ });
29
+
30
+ test("formatServiceLine / formatAppReadyLine include URL", () => {
31
+ expect(formatServiceLine("Console", "http://127.0.0.1:6533", false)).toContain(
32
+ "http://127.0.0.1:6533",
33
+ );
34
+ expect(formatAppReadyLine("http://127.0.0.1:6530", false)).toContain(
35
+ "http://127.0.0.1:6530",
36
+ );
37
+ });
38
+
39
+ test("formatClaimNote embeds code and ownership line", () => {
40
+ const code = "aabbccddeeff00112233445566778899";
41
+ const out = formatClaimNote(code, false);
42
+ expect(out).toContain(code);
43
+ expect(out).toContain("Claim code");
44
+ expect(out).toContain("owns the server");
45
+ expect(out).not.toMatch(/\u001b\[/);
46
+ });
47
+
48
+ test("formatStatusLine keeps message", () => {
49
+ expect(formatStatusLine("stack up (sql)", false)).toContain("stack up");
50
+ });
51
+ });
package/src/term.ts ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Clack-inspired terminal styling for `oke` CLI / boot logs.
3
+ *
4
+ * Human TTY gets color + unicode chrome; CI / pipes stay plain.
5
+ * Flip to monochrome with `NO_COLOR=1` or non-TTY stdout.
6
+ */
7
+
8
+ /** Whether ANSI color should be applied. */
9
+ export function termColorEnabled(
10
+ stream: { readonly isTTY?: boolean } = process.stdout,
11
+ ): boolean {
12
+ if (process.env["NO_COLOR"] !== undefined) return false;
13
+ if (process.env["FORCE_COLOR"] === "0") return false;
14
+ if (process.env["FORCE_COLOR"]) return true;
15
+ return stream.isTTY === true;
16
+ }
17
+
18
+ const ESC = "\u001b[";
19
+
20
+ /** ANSI helpers — empty strings when color is off. */
21
+ export type TermStyle = {
22
+ readonly reset: string;
23
+ readonly bold: string;
24
+ readonly dim: string;
25
+ readonly cyan: string;
26
+ readonly green: string;
27
+ readonly magenta: string;
28
+ readonly yellow: string;
29
+ readonly white: string;
30
+ };
31
+
32
+ /**
33
+ * Build a style palette for the current environment.
34
+ *
35
+ * @param color - Force on/off; default from {@link termColorEnabled}
36
+ */
37
+ export function termStyle(color: boolean = termColorEnabled()): TermStyle {
38
+ if (!color) {
39
+ return {
40
+ reset: "",
41
+ bold: "",
42
+ dim: "",
43
+ cyan: "",
44
+ green: "",
45
+ magenta: "",
46
+ yellow: "",
47
+ white: "",
48
+ };
49
+ }
50
+ return {
51
+ reset: `${ESC}0m`,
52
+ bold: `${ESC}1m`,
53
+ dim: `${ESC}2m`,
54
+ cyan: `${ESC}36m`,
55
+ green: `${ESC}32m`,
56
+ magenta: `${ESC}35m`,
57
+ yellow: `${ESC}33m`,
58
+ white: `${ESC}37m`,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * `oke dev` intro — clack chrome; service URLs print as each surface binds.
64
+ *
65
+ * @param options - Color / watch hint
66
+ */
67
+ export function formatDevBanner(
68
+ options: { readonly color?: boolean; readonly watching?: boolean } = {},
69
+ ): string {
70
+ const s = termStyle(options.color ?? termColorEnabled());
71
+ const bar = `${s.dim}│${s.reset}`;
72
+ const lines = [
73
+ "",
74
+ `${s.cyan}${s.bold}┌${s.reset} ${s.bold}oke dev${s.reset}`,
75
+ bar,
76
+ `${s.green}◇${s.reset} Starting`,
77
+ ];
78
+ if (options.watching !== false) {
79
+ lines.push(
80
+ `${bar} ${s.dim}watching — client types regenerate on save${s.reset}`,
81
+ );
82
+ }
83
+ lines.push(bar);
84
+ return `${lines.join("\n")}\n`;
85
+ }
86
+
87
+ /**
88
+ * One service URL line (Console / MCP after bind).
89
+ *
90
+ * @param label - Surface name
91
+ * @param url - Absolute URL
92
+ * @param color - Color on/off
93
+ */
94
+ export function formatServiceLine(
95
+ label: string,
96
+ url: string,
97
+ color: boolean = termColorEnabled(),
98
+ ): string {
99
+ const s = termStyle(color);
100
+ const pad = label.padEnd(7);
101
+ return `${s.dim}│${s.reset} ${s.dim}${pad}${s.reset} ${s.cyan}${url}${s.reset}\n`;
102
+ }
103
+
104
+ /**
105
+ * App child ready line (`bun --hot` soft reload).
106
+ *
107
+ * @param url - App base URL
108
+ * @param color - Color on/off
109
+ */
110
+ export function formatAppReadyLine(
111
+ url: string,
112
+ color: boolean = termColorEnabled(),
113
+ ): string {
114
+ const s = termStyle(color);
115
+ return `${s.green}●${s.reset} ${s.dim}App${s.reset} ${s.cyan}${url}${s.reset}\n`;
116
+ }
117
+
118
+ /**
119
+ * Quiet status line (regen, stack up, …).
120
+ *
121
+ * @param message - Status text
122
+ * @param color - Color on/off
123
+ */
124
+ export function formatStatusLine(
125
+ message: string,
126
+ color: boolean = termColorEnabled(),
127
+ ): string {
128
+ const s = termStyle(color);
129
+ return `${s.dim}│${s.reset} ${s.dim}${message}${s.reset}\n`;
130
+ }
131
+
132
+ /**
133
+ * First-admin claim code note (clack `note`-style box).
134
+ *
135
+ * @param code - Hex claim code
136
+ * @param color - Color on/off
137
+ */
138
+ export function formatClaimNote(
139
+ code: string,
140
+ color: boolean = termColorEnabled(),
141
+ ): string {
142
+ const s = termStyle(color);
143
+ const bar = `${s.dim}│${s.reset}`;
144
+ const title = "Claim code";
145
+ const hint = "expires in 30 min";
146
+ const ownership = "Whoever can read this log already owns the server.";
147
+ const lines = [
148
+ "",
149
+ `${s.magenta}◆${s.reset} ${s.bold}${title}${s.reset} ${s.dim}${hint}${s.reset}`,
150
+ bar,
151
+ `${bar} ${s.bold}${code}${s.reset}`,
152
+ bar,
153
+ `${bar} ${s.dim}${ownership}${s.reset}`,
154
+ `${s.dim}└${s.reset}`,
155
+ "",
156
+ ];
157
+ return `${lines.join("\n")}\n`;
158
+ }