okengine 0.10.3 → 0.11.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/package.json +76 -10
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -7
- package/site/content/docs/elements/channel.mdx +9 -7
- package/site/content/docs/elements/clock.mdx +8 -8
- package/site/content/docs/elements/flow.mdx +46 -47
- package/site/content/docs/elements/gate.mdx +6 -5
- package/site/content/docs/elements/signal.mdx +2 -2
- package/site/content/docs/elements/store.mdx +71 -78
- package/site/content/docs/elements/vault.mdx +89 -70
- package/site/content/docs/get-started/basic-usage.mdx +8 -8
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +2 -2
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +11 -4
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +27 -15
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +1 -1
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +166 -121
- package/src/cli/dev.ts +138 -192
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +55 -15
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +16 -12
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +34 -28
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +28 -10
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +16 -23
- package/src/config/driver-defaults.ts +33 -63
- package/src/config/index.ts +169 -144
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +3 -8
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +3 -10
- package/src/kernel/boot-bind/clock.ts +2 -2
- package/src/kernel/boot-bind/gate.ts +5 -9
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +2 -2
- package/src/kernel/boot-bind/store.test.ts +43 -44
- package/src/kernel/boot-bind/store.ts +16 -41
- package/src/kernel/boot-bind/vault.ts +16 -1
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/` command palette — autofill over the full `oke` CLI + panel jumps.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text, useInput } from "ink";
|
|
6
|
+
import { useMemo, useState, type ReactElement } from "react";
|
|
7
|
+
import {
|
|
8
|
+
commonPrefix,
|
|
9
|
+
filterSlashActions,
|
|
10
|
+
parseSlashArgv,
|
|
11
|
+
type SlashAction,
|
|
12
|
+
} from "../slash-catalog.ts";
|
|
13
|
+
import { TUI_BRAND, TUI_HINT, TUI_MUTED, TUI_OK, TUI_WARN } from "../theme.ts";
|
|
14
|
+
|
|
15
|
+
/** Props for {@link SlashPalette}. */
|
|
16
|
+
export type SlashPaletteProps = {
|
|
17
|
+
readonly cwd: string;
|
|
18
|
+
readonly onClose: () => void;
|
|
19
|
+
readonly onJumpPanel: (panelId: string) => void;
|
|
20
|
+
readonly onRunCli: (argv: readonly string[]) => void | Promise<void>;
|
|
21
|
+
readonly busy?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Slash palette overlay — type to filter, Tab autofills, Enter runs.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Callbacks
|
|
28
|
+
*/
|
|
29
|
+
export function SlashPalette(props: SlashPaletteProps): ReactElement {
|
|
30
|
+
const [query, setQuery] = useState("");
|
|
31
|
+
const [selected, setSelected] = useState(0);
|
|
32
|
+
const [status, setStatus] = useState("");
|
|
33
|
+
|
|
34
|
+
const matches = useMemo(() => filterSlashActions(query, 14), [query]);
|
|
35
|
+
const sel = Math.min(selected, Math.max(0, matches.length - 1));
|
|
36
|
+
const active = matches[sel] ?? null;
|
|
37
|
+
|
|
38
|
+
useInput(
|
|
39
|
+
(input, key) => {
|
|
40
|
+
if (props.busy) return;
|
|
41
|
+
|
|
42
|
+
if (key.escape) {
|
|
43
|
+
props.onClose();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (key.upArrow) {
|
|
48
|
+
setSelected((i) => Math.max(0, i - 1));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (key.downArrow) {
|
|
52
|
+
setSelected((i) => Math.min(Math.max(0, matches.length - 1), i + 1));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (key.tab) {
|
|
57
|
+
if (active) {
|
|
58
|
+
const ids = matches.map((m) => m.id);
|
|
59
|
+
const prefix = commonPrefix(ids);
|
|
60
|
+
const next =
|
|
61
|
+
prefix.length > query.length && ids.every((id) => id.startsWith(prefix))
|
|
62
|
+
? prefix
|
|
63
|
+
: active.id;
|
|
64
|
+
setQuery(next);
|
|
65
|
+
setSelected(0);
|
|
66
|
+
setStatus(`autofill → /${next}`);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (key.return) {
|
|
72
|
+
void submit(active, query);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (key.backspace || key.delete) {
|
|
77
|
+
setQuery((q) => q.slice(0, -1));
|
|
78
|
+
setSelected(0);
|
|
79
|
+
setStatus("");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!input || key.ctrl || key.meta) return;
|
|
84
|
+
// Ignore a second leading `/`; otherwise append printable chars.
|
|
85
|
+
if (input === "/" && query.length === 0) return;
|
|
86
|
+
setQuery((q) => q + input);
|
|
87
|
+
setSelected(0);
|
|
88
|
+
setStatus("");
|
|
89
|
+
},
|
|
90
|
+
{ isActive: true },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const submit = async (action: SlashAction | null, raw: string): Promise<void> => {
|
|
94
|
+
if (action?.kind === "panel" && action.panel) {
|
|
95
|
+
props.onJumpPanel(action.panel);
|
|
96
|
+
props.onClose();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (action?.kind === "cli" && action.argv) {
|
|
101
|
+
const typed = parseSlashArgv(raw);
|
|
102
|
+
const base = action.argv;
|
|
103
|
+
// Append trailing free tokens the user typed beyond the suggestion.
|
|
104
|
+
let argv: readonly string[] = base;
|
|
105
|
+
if (typed.length > base.length && typed.slice(0, base.length).join(" ") === base.join(" ")) {
|
|
106
|
+
argv = typed;
|
|
107
|
+
}
|
|
108
|
+
setStatus(`running oke ${argv.join(" ")}…`);
|
|
109
|
+
await props.onRunCli(argv);
|
|
110
|
+
props.onClose();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const argv = parseSlashArgv(raw);
|
|
115
|
+
if (argv.length === 0) {
|
|
116
|
+
setStatus("type a command · Tab to autofill");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setStatus(`running oke ${argv.join(" ")}…`);
|
|
120
|
+
await props.onRunCli(argv);
|
|
121
|
+
props.onClose();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Box
|
|
126
|
+
flexDirection="column"
|
|
127
|
+
borderStyle="round"
|
|
128
|
+
borderColor={TUI_BRAND}
|
|
129
|
+
paddingX={1}
|
|
130
|
+
width="100%"
|
|
131
|
+
flexGrow={1}
|
|
132
|
+
>
|
|
133
|
+
<Box>
|
|
134
|
+
<Text bold color={TUI_BRAND}>
|
|
135
|
+
/
|
|
136
|
+
</Text>
|
|
137
|
+
<Text bold>{query}</Text>
|
|
138
|
+
<Text color={TUI_BRAND}>█</Text>
|
|
139
|
+
<Text color={TUI_MUTED}> Tab autofill · ↑↓ · Enter run · Esc</Text>
|
|
140
|
+
</Box>
|
|
141
|
+
|
|
142
|
+
<Box flexDirection="column" marginTop={1} flexGrow={1}>
|
|
143
|
+
{matches.length === 0 ? (
|
|
144
|
+
<Text color={TUI_WARN}>No matches — Enter runs free-form `oke …`</Text>
|
|
145
|
+
) : (
|
|
146
|
+
matches.map((m, i) => {
|
|
147
|
+
const on = i === sel;
|
|
148
|
+
return (
|
|
149
|
+
<Box key={m.id} gap={1}>
|
|
150
|
+
<Text bold={on} color={on ? TUI_BRAND : TUI_MUTED}>
|
|
151
|
+
{on ? "›" : " "}
|
|
152
|
+
</Text>
|
|
153
|
+
<Text bold={on} color={on ? TUI_BRAND : undefined}>
|
|
154
|
+
/{m.label}
|
|
155
|
+
</Text>
|
|
156
|
+
<Text color={m.kind === "panel" ? TUI_OK : TUI_MUTED}>
|
|
157
|
+
{m.kind === "panel" ? "panel" : "cli"}
|
|
158
|
+
</Text>
|
|
159
|
+
<Text color={TUI_MUTED} wrap="truncate">
|
|
160
|
+
{m.summary}
|
|
161
|
+
</Text>
|
|
162
|
+
</Box>
|
|
163
|
+
);
|
|
164
|
+
})
|
|
165
|
+
)}
|
|
166
|
+
</Box>
|
|
167
|
+
|
|
168
|
+
{status ? <Text color={TUI_HINT}>{status}</Text> : null}
|
|
169
|
+
{props.busy ? <Text color={TUI_WARN}>Running…</Text> : null}
|
|
170
|
+
</Box>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight terminal spinner (no extra deps).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Text } from "ink";
|
|
6
|
+
import { useEffect, useState, type ReactElement } from "react";
|
|
7
|
+
import { TUI_WARN } from "../theme.ts";
|
|
8
|
+
|
|
9
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
10
|
+
|
|
11
|
+
/** Props for {@link Spinner}. */
|
|
12
|
+
export type SpinnerProps = {
|
|
13
|
+
readonly label?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Animated spinner + optional label.
|
|
18
|
+
*
|
|
19
|
+
* @param props - Label
|
|
20
|
+
*/
|
|
21
|
+
export function Spinner(props: SpinnerProps): ReactElement {
|
|
22
|
+
const [i, setI] = useState(0);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const id = setInterval(() => setI((n) => (n + 1) % FRAMES.length), 80);
|
|
25
|
+
return () => clearInterval(id);
|
|
26
|
+
}, []);
|
|
27
|
+
return (
|
|
28
|
+
<Text color={TUI_WARN}>
|
|
29
|
+
{FRAMES[i]} {props.label ?? "Working…"}
|
|
30
|
+
</Text>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single global footer — shortcuts + transient action status (no own border;
|
|
3
|
+
* the app chrome already frames the shell).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
import type { ReactElement } from "react";
|
|
8
|
+
import { Spinner } from "./Spinner.tsx";
|
|
9
|
+
import { TUI_HINT, TUI_MUTED, TUI_OK } from "../theme.ts";
|
|
10
|
+
|
|
11
|
+
/** Footer mode. */
|
|
12
|
+
export type FooterStatus =
|
|
13
|
+
| { readonly kind: "idle" }
|
|
14
|
+
| { readonly kind: "running"; readonly label: string }
|
|
15
|
+
| { readonly kind: "done"; readonly label?: string };
|
|
16
|
+
|
|
17
|
+
/** Props for {@link StatusBar}. */
|
|
18
|
+
export type StatusBarProps = {
|
|
19
|
+
readonly status?: FooterStatus;
|
|
20
|
+
readonly hints?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Shortcut strip + optional running/done feedback.
|
|
25
|
+
*
|
|
26
|
+
* @param props - Status + hints
|
|
27
|
+
*/
|
|
28
|
+
export function StatusBar(props: StatusBarProps): ReactElement {
|
|
29
|
+
const hints =
|
|
30
|
+
props.hints ?? "/ command · Tab panels · ↑↓ navigate · Enter action · 1-5 jump · Esc";
|
|
31
|
+
const status = props.status ?? { kind: "idle" as const };
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Box paddingX={1} paddingY={0} marginTop={0} justifyContent="space-between">
|
|
35
|
+
<Box>
|
|
36
|
+
{status.kind === "running" ? (
|
|
37
|
+
<Spinner label={status.label} />
|
|
38
|
+
) : status.kind === "done" ? (
|
|
39
|
+
<Text color={TUI_OK}>✓ {status.label ?? "Done"}</Text>
|
|
40
|
+
) : (
|
|
41
|
+
<Text color={TUI_HINT}>{hints}</Text>
|
|
42
|
+
)}
|
|
43
|
+
</Box>
|
|
44
|
+
{status.kind !== "idle" ? (
|
|
45
|
+
<Text color={TUI_MUTED} dimColor>
|
|
46
|
+
{hints}
|
|
47
|
+
</Text>
|
|
48
|
+
) : null}
|
|
49
|
+
</Box>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ink readiness glyph for {@link DevStatus}.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Text } from "ink";
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
import type { DevStatus } from "../../../term.ts";
|
|
8
|
+
import { TUI_ERR, TUI_OK, TUI_WARN } from "../theme.ts";
|
|
9
|
+
|
|
10
|
+
/** Props for {@link StatusDot}. */
|
|
11
|
+
export type StatusDotProps = {
|
|
12
|
+
readonly status: DevStatus;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* ● online/pending/error · ○ offline (yellow/red, not gray).
|
|
17
|
+
*
|
|
18
|
+
* @param props - Status
|
|
19
|
+
*/
|
|
20
|
+
export function StatusDot(props: StatusDotProps): ReactElement {
|
|
21
|
+
if (props.status === "idle") {
|
|
22
|
+
return <Text color={TUI_WARN}>○</Text>;
|
|
23
|
+
}
|
|
24
|
+
const color = props.status === "ready" ? TUI_OK : props.status === "pending" ? TUI_WARN : TUI_ERR;
|
|
25
|
+
return <Text color={color}>●</Text>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Fixed-width status word. */
|
|
29
|
+
export function statusWord(status: DevStatus): {
|
|
30
|
+
readonly word: string;
|
|
31
|
+
readonly color: typeof TUI_OK | typeof TUI_WARN | typeof TUI_ERR;
|
|
32
|
+
} {
|
|
33
|
+
if (status === "ready") return { word: "online ", color: TUI_OK };
|
|
34
|
+
if (status === "pending") return { word: "start ", color: TUI_WARN };
|
|
35
|
+
if (status === "error") return { word: "error ", color: TUI_ERR };
|
|
36
|
+
return { word: "offline", color: TUI_WARN };
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level panel tabs — green active chip, no enclosing border.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
import { TUI_BRAND, TUI_MUTED } from "../theme.ts";
|
|
8
|
+
|
|
9
|
+
/** One tab descriptor. */
|
|
10
|
+
export type TabItem = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Props for {@link TabBar}. */
|
|
16
|
+
export type TabBarProps = {
|
|
17
|
+
readonly tabs: readonly TabItem[];
|
|
18
|
+
readonly activeIndex: number;
|
|
19
|
+
/** When true, tab strip owns focus (reserved for future emphasis). */
|
|
20
|
+
readonly focused?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Horizontal tab bar.
|
|
25
|
+
*
|
|
26
|
+
* @param props - Tabs + selection
|
|
27
|
+
*/
|
|
28
|
+
export function TabBar(props: TabBarProps): ReactElement {
|
|
29
|
+
return (
|
|
30
|
+
<Box paddingX={1} gap={0}>
|
|
31
|
+
{props.tabs.map((tab, i) => {
|
|
32
|
+
const active = i === props.activeIndex;
|
|
33
|
+
const label = ` ${i + 1} ${tab.label} `;
|
|
34
|
+
if (active) {
|
|
35
|
+
return (
|
|
36
|
+
<Text key={tab.id} backgroundColor={TUI_BRAND} color="black" bold>
|
|
37
|
+
{label}
|
|
38
|
+
</Text>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<Text key={tab.id} color={TUI_MUTED}>
|
|
43
|
+
{label}
|
|
44
|
+
</Text>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
</Box>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKE ASCII wordmark — same art as `oke dev` / {@link formatOkeWordmark}.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
import { TUI_BRAND, TUI_MUTED } from "../theme.ts";
|
|
8
|
+
|
|
9
|
+
/** Plain wordmark lines (no ANSI) — matches `formatOkeWordmark`. */
|
|
10
|
+
export const OKE_WORDMARK_LINES: readonly string[] = [
|
|
11
|
+
" ██████╗ ██╗ ██╗███████╗",
|
|
12
|
+
" ██╔═══██╗██║ ██╔╝██╔════╝",
|
|
13
|
+
" ██║ ██║█████╔╝ █████╗ ",
|
|
14
|
+
" ██║ ██║██╔═██╗ ██╔══╝ ",
|
|
15
|
+
" ╚██████╔╝██║ ██╗███████╗",
|
|
16
|
+
" ╚═════╝ ╚═╝ ╚═╝╚══════╝",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Props for {@link Wordmark}. */
|
|
20
|
+
export type WordmarkProps = {
|
|
21
|
+
readonly version?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Green block-letter OKE + version.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Optional pinned version
|
|
28
|
+
*/
|
|
29
|
+
export function Wordmark(props: WordmarkProps): ReactElement {
|
|
30
|
+
const version = props.version ?? "0.10.3";
|
|
31
|
+
return (
|
|
32
|
+
<Box flexDirection="column">
|
|
33
|
+
{OKE_WORDMARK_LINES.map((line) => (
|
|
34
|
+
<Text key={line} bold color={TUI_BRAND}>
|
|
35
|
+
{line}
|
|
36
|
+
</Text>
|
|
37
|
+
))}
|
|
38
|
+
<Text color={TUI_MUTED}>
|
|
39
|
+
{" "}v{version}
|
|
40
|
+
</Text>
|
|
41
|
+
</Box>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TUI format helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format ms as compact uptime (`4m 21s`).
|
|
7
|
+
*
|
|
8
|
+
* @param ms - Elapsed milliseconds
|
|
9
|
+
*/
|
|
10
|
+
export function formatUptime(ms: number): string {
|
|
11
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
12
|
+
const m = Math.floor(s / 60);
|
|
13
|
+
const h = Math.floor(m / 60);
|
|
14
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
15
|
+
if (m > 0) return `${m}m ${s % 60}s`;
|
|
16
|
+
return `${s}s`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Open a URL in the default browser (best-effort).
|
|
21
|
+
*
|
|
22
|
+
* @param url - http(s) URL
|
|
23
|
+
*/
|
|
24
|
+
export async function openUrl(url: string): Promise<void> {
|
|
25
|
+
const platform = process.platform;
|
|
26
|
+
const cmd =
|
|
27
|
+
platform === "darwin"
|
|
28
|
+
? ["open", url]
|
|
29
|
+
: platform === "win32"
|
|
30
|
+
? ["cmd", "/c", "start", "", url]
|
|
31
|
+
: ["xdg-open", url];
|
|
32
|
+
try {
|
|
33
|
+
const proc = Bun.spawn(cmd, { stdout: "ignore", stderr: "ignore" });
|
|
34
|
+
await proc.exited;
|
|
35
|
+
} catch {
|
|
36
|
+
// ignore — TUI still shows the link
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mapDevControlInput } from "./keys.ts";
|
|
3
|
+
|
|
4
|
+
describe("mapDevControlInput", () => {
|
|
5
|
+
test("maps ? r q u x and aliases", () => {
|
|
6
|
+
expect(mapDevControlInput("?", {})).toBe("?");
|
|
7
|
+
expect(mapDevControlInput("h", {})).toBe("?");
|
|
8
|
+
expect(mapDevControlInput("r", {})).toBe("r");
|
|
9
|
+
expect(mapDevControlInput("q", {})).toBe("q");
|
|
10
|
+
expect(mapDevControlInput("u", {})).toBe("u");
|
|
11
|
+
expect(mapDevControlInput("x", {})).toBe("x");
|
|
12
|
+
expect(mapDevControlInput("c", {})).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("maps Ctrl+C to quit", () => {
|
|
16
|
+
expect(mapDevControlInput("c", { ctrl: true })).toBe("q");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared keyboard mapping for Ink `useInput` and pure tests.
|
|
3
|
+
* Parity with legacy `parseDevControlKey` (`? r q u x`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Dev control action from a keypress. */
|
|
7
|
+
export type DevControlKey = "?" | "r" | "q" | "u" | "x";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Map Ink input (+ ctrl) to a control key.
|
|
11
|
+
*
|
|
12
|
+
* @param input - Character from useInput
|
|
13
|
+
* @param key - Ink key modifiers
|
|
14
|
+
*/
|
|
15
|
+
export function mapDevControlInput(
|
|
16
|
+
input: string,
|
|
17
|
+
key: { readonly ctrl?: boolean },
|
|
18
|
+
): DevControlKey | null {
|
|
19
|
+
if (key.ctrl && input === "c") return "q";
|
|
20
|
+
if (input === "?" || input === "h" || input === "H") return "?";
|
|
21
|
+
if (input === "q" || input === "Q") return "q";
|
|
22
|
+
if (input === "r" || input === "R") return "r";
|
|
23
|
+
if (input === "u" || input === "U") return "u";
|
|
24
|
+
if (input === "x" || input === "X") return "x";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { clearTerminalScreen } from "../../term.ts";
|
|
3
|
+
import { canRenderTui, clearTerminalViewport } from "./launch.ts";
|
|
4
|
+
|
|
5
|
+
describe("canRenderTui", () => {
|
|
6
|
+
test("requires stdout.isTTY", () => {
|
|
7
|
+
expect(canRenderTui({ isTTY: true })).toBe(true);
|
|
8
|
+
expect(canRenderTui({ isTTY: false })).toBe(false);
|
|
9
|
+
expect(canRenderTui({})).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("clearTerminalViewport", () => {
|
|
14
|
+
test("writes the same clear sequence as oke dev", () => {
|
|
15
|
+
expect(clearTerminalScreen()).toContain("\u001b[2J");
|
|
16
|
+
expect(typeof clearTerminalViewport).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch the interactive Ink TUI (dynamic-imported from the bin).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { clearTerminalScreen } from "../../term.ts";
|
|
6
|
+
import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "../exit.ts";
|
|
7
|
+
import { formatOkeHelp } from "../registry.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* True when stdout can host an interactive Ink app.
|
|
11
|
+
*
|
|
12
|
+
* @param stream - stdout-like
|
|
13
|
+
*/
|
|
14
|
+
export function canRenderTui(stream: { readonly isTTY?: boolean } = process.stdout): boolean {
|
|
15
|
+
return stream.isTTY === true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wipe the viewport so boot logs never sit above the Ink frame.
|
|
20
|
+
*/
|
|
21
|
+
export function clearTerminalViewport(): void {
|
|
22
|
+
process.stdout.write(clearTerminalScreen());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Start the TUI. Call only after {@link canRenderTui} is true.
|
|
27
|
+
*
|
|
28
|
+
* @param cwd - Project root
|
|
29
|
+
*/
|
|
30
|
+
export async function launchTui(cwd: string = process.cwd()): Promise<number> {
|
|
31
|
+
try {
|
|
32
|
+
clearTerminalViewport();
|
|
33
|
+
const React = await import("react");
|
|
34
|
+
const { render } = await import("ink");
|
|
35
|
+
const { OkeTuiApp } = await import("./App.tsx");
|
|
36
|
+
const instance = render(React.createElement(OkeTuiApp, { cwd }), {
|
|
37
|
+
exitOnCtrlC: false,
|
|
38
|
+
});
|
|
39
|
+
await instance.waitUntilExit();
|
|
40
|
+
return EXIT_OK;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43
|
+
const missing =
|
|
44
|
+
/Cannot find (module|package)|ERR_MODULE_NOT_FOUND|Cannot find package/i.test(msg) ||
|
|
45
|
+
msg.includes("'ink'") ||
|
|
46
|
+
msg.includes('"ink"');
|
|
47
|
+
if (missing) {
|
|
48
|
+
console.error("oke: interactive TUI requires optional deps `ink` and `react`.");
|
|
49
|
+
console.error(" bun add ink react");
|
|
50
|
+
console.error(" Or run `oke --help` / `oke <command>` without the TUI.");
|
|
51
|
+
console.log(formatOkeHelp());
|
|
52
|
+
return EXIT_USAGE;
|
|
53
|
+
}
|
|
54
|
+
console.error(`oke: TUI failed — ${msg}`);
|
|
55
|
+
return EXIT_RUNTIME;
|
|
56
|
+
}
|
|
57
|
+
}
|