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.
- package/README.md +8 -8
- package/package.json +2 -1
- package/src/cli/dev-app-runner.ts +6 -2
- package/src/cli/dev.ts +84 -15
- package/src/cli/doctor.ts +3 -18
- package/src/cli/index.ts +4 -1
- package/src/cli/json-out.test.ts +1 -1
- package/src/cli/load-config.images.test.ts +75 -0
- package/src/cli/load-config.ts +67 -2
- package/src/cli/ports.test.ts +51 -0
- package/src/cli/ports.ts +81 -0
- package/src/cli/registry.help.test.ts +18 -0
- package/src/cli/registry.ts +0 -4
- package/src/cli/safe-defaults.test.ts +3 -3
- package/src/console/server/app.ts +2 -1
- package/src/console/server/claim.test.ts +7 -4
- package/src/console/server/claim.ts +2 -7
- package/src/console/server/flows.ts +1 -0
- package/src/console/server/index.ts +5 -0
- package/src/console/server/operator-db.test.ts +75 -0
- package/src/console/server/operator-db.ts +324 -0
- package/src/console/server/serve.ts +39 -0
- package/src/console/server/state.ts +13 -1
- package/src/console/ui/dist/assets/index-Bnf_3Hei.js +10 -0
- package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
- package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
- package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
- package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
- package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
- package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
- package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
- package/src/console/ui/dist/assets/panel-overview-Dt_AeXgd.js +1 -0
- package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-DGstFHeq.js +1 -0
- package/src/console/ui/dist/assets/panel-signals-DzEa2Fnt.js +1 -0
- package/src/console/ui/dist/assets/panel-store-BJkbNgxx.js +1 -0
- package/src/console/ui/dist/assets/panel-traces-BaRVO3gM.js +1 -0
- package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
- package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +3 -0
- package/src/console/ui/dist/favicon.svg +7 -0
- package/src/console/ui/dist/index.html +15 -0
- package/src/console/ui/shell/App.tsx +34 -2
- package/src/console/ui/shell/components/oke-logo.tsx +40 -0
- package/src/console/ui/shell/index.html +1 -0
- package/src/console/ui/shell/layout/Shell.tsx +2 -3
- package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +8 -0
- package/src/console/ui/shell/public/favicon.svg +7 -0
- package/src/console/ui/shell/setup/Wizard.tsx +5 -2
- package/src/docker/derive.ts +3 -1
- package/src/docker/dockerfile.integration.test.ts +1 -1
- package/src/docker/stack.integration.test.ts +1 -1
- package/src/docs-origin.ts +19 -0
- package/src/kernel/errors.registry.test.ts +4 -2
- package/src/kernel/errors.ts +3 -1
- package/src/kernel/fx.test.ts +2 -2
- package/src/term.test.ts +51 -0
- 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
|
-
<
|
|
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">
|
|
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.
|
package/src/docker/derive.ts
CHANGED
|
@@ -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(
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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("→");
|
package/src/kernel/errors.ts
CHANGED
|
@@ -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 =
|
|
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";
|
package/src/kernel/fx.test.ts
CHANGED
|
@@ -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://
|
|
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://
|
|
221
|
+
expect(err.message).toContain("https://okengine.vercel.app/e/1042");
|
|
222
222
|
expect(err.message).toContain("→");
|
|
223
223
|
});
|
|
224
224
|
|
package/src/term.test.ts
ADDED
|
@@ -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
|
+
}
|