mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
feat(tui): run on Node 26 (one runtime), finalize copy UX, rename to ui-opentui
Ports the engine off the second JS runtime onto Node 26.3 (node:ffi) so the repo ships a single JavaScript runtime: child_process for the gateway, vitest for tests, an esbuild + Solid build step. Mouse selection copies the rendered text you highlight, and the clipboard path is crash-proofed (a broken copy pipe no longer quits the UI). Renames the engine dir ui-tui-opentui-v2/ -> ui-opentui/ and updates the launcher/installer/Docker references.
This commit is contained in:
parent
25567919ea
commit
ae11a636dc
85 changed files with 5613 additions and 900 deletions
31
Dockerfile
31
Dockerfile
|
|
@ -1,12 +1,14 @@
|
|||
FROM ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie@sha256:b3c543b6c4f23a5f2df22866bd7857e5d304b67a564f4feab6ac22044dde719b AS uv_source
|
||||
# Node 22 LTS source stage. Debian trixie's bundled nodejs is pinned to 20.x
|
||||
# which reached EOL in April 2026 — we copy node + npm + corepack from the
|
||||
# upstream node:22 image instead so we can stay on a supported LTS without
|
||||
# waiting for Debian 14 (forky, ~mid-2027). Bookworm-based slim image used
|
||||
# so the produced binary links against glibc 2.36, which runs cleanly on
|
||||
# our Debian 13 (trixie, glibc 2.41) runtime. Bumping to a new Node major
|
||||
# is a one-line ARG change; see #4977.
|
||||
FROM node:22-bookworm-slim@sha256:7af03b14a13c8cdd38e45058fd957bf00a72bbe17feac43b1c15a689c029c732 AS node_source
|
||||
# Node 26 source stage. Debian trixie's bundled nodejs is pinned to 20.x
|
||||
# (EOL April 2026), so we copy node + npm + corepack from the upstream node:26
|
||||
# image instead. Node 26 (Current; LTS promotion ~Oct 2026) is REQUIRED by the
|
||||
# native OpenTUI TUI engine, which loads its renderer via the experimental
|
||||
# `node:ffi` API that only exists on Node 26.3+ (the Ink engine + web build run
|
||||
# on it too). Bookworm-based slim image used so the produced binary links
|
||||
# against glibc 2.36, which runs cleanly on our Debian 13 (trixie, glibc 2.41)
|
||||
# runtime. The pinned tag ships v26.3.0. Bumping Node is a one-line change here.
|
||||
# NOTE: verify the full image build + Ink/web/Playwright on Node 26 in CI.
|
||||
FROM node:26-bookworm-slim@sha256:79723b41edbedf595f62e943a9f8b0ba9af5b1e61045c5f8f59c2c02c1212a16 AS node_source
|
||||
FROM debian:13.4
|
||||
|
||||
# Disable Python stdout buffering to ensure logs are printed immediately
|
||||
|
|
@ -90,7 +92,7 @@ RUN useradd -u 10000 -m -d /opt/data hermes
|
|||
|
||||
COPY --chmod=0755 --from=uv_source /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/
|
||||
|
||||
# Node 22 LTS: copy the node binary plus the bundled npm + corepack JS
|
||||
# Node 26: copy the node binary plus the bundled npm + corepack JS
|
||||
# installs from the upstream image. npm and npx are recreated as symlinks
|
||||
# because they're symlinks in the source image (and need to live on PATH).
|
||||
# See node_source stage at the top of the file for the version-bump
|
||||
|
|
@ -119,7 +121,7 @@ COPY ui-tui/packages/hermes-ink/ ui-tui/packages/hermes-ink/
|
|||
|
||||
# `npm_config_install_links=false` forces npm to install `file:` deps as
|
||||
# symlinks instead of copies. This is the default since npm 10+, which is
|
||||
# what the image ships now (via the node:22 source stage). We set it
|
||||
# what the image ships now (via the node:26 source stage). We set it
|
||||
# explicitly anyway as defense-in-depth: the previous Debian-bundled npm
|
||||
# 9.x defaulted to install-as-copy, which produced a hidden
|
||||
# node_modules/.package-lock.json that permanently disagreed with the root
|
||||
|
|
@ -174,8 +176,15 @@ RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra
|
|||
COPY --chown=hermes:hermes . .
|
||||
|
||||
# Build browser dashboard and terminal UI assets.
|
||||
# ui-opentui is the opt-in native OpenTUI engine (HERMES_TUI_ENGINE=opentui;
|
||||
# default stays Ink). .dockerignore strips its node_modules/dist, so install +
|
||||
# esbuild-build it here → dist/main.js, then prune devDeps (esbuild/babel/vitest);
|
||||
# the runtime only needs the prod deps (the external @opentui/core + its native
|
||||
# blob — the bundle inlines solid/effect). Build needs Node 26.3 (node:ffi floor),
|
||||
# which this image now ships. (CI must verify the full image build on Node 26.)
|
||||
RUN cd web && npm run build && \
|
||||
cd ../ui-tui && npm run build
|
||||
cd ../ui-tui && npm run build && \
|
||||
cd ../ui-opentui && npm install --no-audit --no-fund && npm run build && npm prune --omit=dev
|
||||
|
||||
# ---------- Permissions ----------
|
||||
# Make install dir world-readable so any HERMES_UID can read it at runtime.
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ You can still bring your own keys per-tool whenever you want — the gateway is
|
|||
|
||||
Hermes has two entry points: start the terminal UI with `hermes`, or run the gateway and talk to it from Telegram, Discord, Slack, WhatsApp, Signal, or Email. Once you're in a conversation, many slash commands are shared across both interfaces.
|
||||
|
||||
> **TUI engine:** On supported hosts (Linux/macOS with [Bun](https://bun.sh)), the terminal UI defaults to the native **OpenTUI** engine, which the installer provisions for you. The legacy **Ink** engine remains the fallback — it's used automatically on Windows, Termux, or when Bun isn't available, and you can select it explicitly with `HERMES_TUI_ENGINE=ink hermes`. Ink is not going away; it's the kept fallback.
|
||||
> **TUI engine:** On supported hosts (Linux/macOS with Node 26.3+), the terminal UI defaults to the native **OpenTUI** engine, which the installer provisions for you. The legacy **Ink** engine remains the fallback — it's used automatically on Windows, Termux, or when the native engine can't run, and you can select it explicitly with `HERMES_TUI_ENGINE=ink hermes`. Ink is not going away; it's the kept fallback.
|
||||
|
||||
| Action | CLI | Messaging platforms |
|
||||
| ------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
|
|
|
|||
|
|
@ -1560,14 +1560,15 @@ def _resolve_tui_engine() -> str:
|
|||
"""Which TUI engine to launch: "ink" (default) or "opentui".
|
||||
|
||||
Precedence: ``HERMES_TUI_ENGINE`` env > ``display.tui_engine`` config >
|
||||
(OpenTUI when this host can run it — Bun + the installed package — else Ink).
|
||||
The OpenTUI engine runs on Bun, which is not reliably available on Windows
|
||||
or Termux — request "opentui" there falls back to "ink" with a notice so a
|
||||
stale flag never strands the user on an engine that can't start.
|
||||
(OpenTUI when this host can run it — Node >= 26.3 + the built package — else Ink).
|
||||
The OpenTUI engine runs on Node 26.3+ via the experimental ``node:ffi`` renderer,
|
||||
which is not validated on Windows or Termux — a request for "opentui" there falls
|
||||
back to "ink" with a notice so a stale flag never strands the user on an engine
|
||||
that can't start.
|
||||
"""
|
||||
env = (os.environ.get("HERMES_TUI_ENGINE") or "").strip().lower()
|
||||
# Explicit choice (env > config) wins; otherwise default to OpenTUI when this
|
||||
# host is genuinely set up for it (Bun + the installed package), else Ink.
|
||||
# host is genuinely set up for it (Node >= 26.3 + the built bundle), else Ink.
|
||||
engine = env or _config_tui_engine_early() or ("opentui" if _opentui_available() else "ink")
|
||||
if engine != "opentui":
|
||||
return "ink"
|
||||
|
|
@ -1579,124 +1580,172 @@ def _resolve_tui_engine() -> str:
|
|||
where = "Windows" if sys.platform.startswith("win") else "Termux"
|
||||
print(
|
||||
f"HERMES_TUI_ENGINE=opentui is not supported on {where} "
|
||||
f"(needs Bun) — falling back to the Ink engine.",
|
||||
f"(needs Node 26.3+ with experimental FFI) — falling back to the Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return "ink"
|
||||
return "opentui"
|
||||
|
||||
|
||||
def _bun_bin_or_none() -> str | None:
|
||||
"""Resolve the Bun binary, or ``None`` if not found (no exit — a probe).
|
||||
NODE26_MIN_VERSION = (26, 3, 0)
|
||||
|
||||
``HERMES_BUN`` override > ``bun`` on PATH > common install dirs.
|
||||
|
||||
def _node_version_tuple(node_bin: str) -> tuple[int, int, int] | None:
|
||||
"""Return (major, minor, patch) for a node binary, or ``None`` if unreadable."""
|
||||
try:
|
||||
out = subprocess.run([node_bin, "--version"], capture_output=True, text=True, timeout=5)
|
||||
except Exception:
|
||||
return None
|
||||
if out.returncode != 0:
|
||||
return None
|
||||
raw = (out.stdout or "").strip().lstrip("v").split("-", 1)[0]
|
||||
parts = raw.split(".")
|
||||
try:
|
||||
return (int(parts[0]), int(parts[1]), int(parts[2]))
|
||||
except (IndexError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _node26_bin_or_none() -> str | None:
|
||||
"""Resolve a Node >= 26.3.0 binary (no exit — a probe), or ``None``.
|
||||
|
||||
``HERMES_NODE`` override > ``node`` on PATH, each gated on version >= 26.3.0.
|
||||
OpenTUI's native renderer loads via the experimental ``node:ffi`` API that only
|
||||
exists on Node 26.3+, so an older Node is treated as "not available".
|
||||
"""
|
||||
env_bun = os.environ.get("HERMES_BUN")
|
||||
if env_bun and os.path.isfile(env_bun) and os.access(env_bun, os.X_OK):
|
||||
return env_bun
|
||||
path = shutil.which("bun")
|
||||
candidates: list[str] = []
|
||||
env_node = os.environ.get("HERMES_NODE")
|
||||
if env_node and os.path.isfile(env_node) and os.access(env_node, os.X_OK):
|
||||
candidates.append(env_node)
|
||||
path = shutil.which("node")
|
||||
if path:
|
||||
return path
|
||||
for cand in (
|
||||
Path.home() / ".bun" / "bin" / "bun",
|
||||
Path("/usr/local/bin/bun"),
|
||||
Path("/opt/homebrew/bin/bun"),
|
||||
):
|
||||
if cand.is_file() and os.access(cand, os.X_OK):
|
||||
return str(cand)
|
||||
candidates.append(path)
|
||||
for cand in candidates:
|
||||
ver = _node_version_tuple(cand)
|
||||
if ver is not None and ver >= NODE26_MIN_VERSION:
|
||||
return cand
|
||||
return None
|
||||
|
||||
|
||||
def _bun_bin() -> str:
|
||||
"""Resolve the Bun binary for the OpenTUI engine, or exit with a clear message.
|
||||
def _node26_bin() -> str:
|
||||
"""Resolve Node >= 26.3.0 for the OpenTUI engine, or exit with a clear message.
|
||||
|
||||
The OpenTUI engine cannot run without Bun, so the launch path exits(1) when
|
||||
it's missing. Use :func:`_bun_bin_or_none` for a non-fatal availability probe.
|
||||
Use :func:`_node26_bin_or_none` for a non-fatal availability probe.
|
||||
"""
|
||||
bun = _bun_bin_or_none()
|
||||
if bun is not None:
|
||||
return bun
|
||||
node = _node26_bin_or_none()
|
||||
if node is not None:
|
||||
return node
|
||||
print(
|
||||
"bun not found — the OpenTUI TUI engine requires Bun.\n"
|
||||
"Install it (https://bun.sh) or set HERMES_BUN=/path/to/bun, "
|
||||
"Node.js >= 26.3.0 not found — the OpenTUI TUI engine needs it for the "
|
||||
"experimental node:ffi renderer.\n"
|
||||
"Install Node 26.3+ (e.g. via fnm/nvm) or set HERMES_NODE=/path/to/node, "
|
||||
"or unset HERMES_TUI_ENGINE to use the default Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _opentui_npm() -> str:
|
||||
"""Resolve npm (ships with Node) to build the OpenTUI bundle, or exit."""
|
||||
npm = shutil.which("npm")
|
||||
if npm:
|
||||
return npm
|
||||
print(
|
||||
"npm not found — needed to build the OpenTUI engine bundle.\n"
|
||||
"Install Node 26.3+ (it ships npm), or unset HERMES_TUI_ENGINE for Ink.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _opentui_available() -> bool:
|
||||
"""Whether the OpenTUI engine can actually launch on this host.
|
||||
|
||||
True only when the platform supports Bun (not Windows/Termux), Bun resolves,
|
||||
AND the v2 package's entry + its installed ``node_modules`` are present. This
|
||||
gates the DEFAULT engine: a host that's genuinely set up for OpenTUI defaults
|
||||
to it; everyone else stays on Ink. An explicit ``HERMES_TUI_ENGINE`` env or
|
||||
``display.tui_engine`` config choice bypasses this probe entirely.
|
||||
True only when the platform is supported (not Windows/Termux), a Node >= 26.3
|
||||
binary resolves (the node:ffi floor), AND the v2 package is BUILT
|
||||
(``dist/main.js``) with its ``node_modules`` installed. This gates the DEFAULT
|
||||
engine: a host genuinely set up for OpenTUI defaults to it; everyone else stays
|
||||
on Ink. An explicit ``HERMES_TUI_ENGINE`` env or ``display.tui_engine`` config
|
||||
choice bypasses this probe (and triggers an on-demand build).
|
||||
"""
|
||||
if sys.platform.startswith("win") or _is_termux_startup_environment():
|
||||
return False
|
||||
if _bun_bin_or_none() is None:
|
||||
if _node26_bin_or_none() is None:
|
||||
return False
|
||||
pkg = PROJECT_ROOT / "ui-tui-opentui-v2"
|
||||
entry = pkg / "src" / "entry" / "main.tsx"
|
||||
return entry.is_file() and (pkg / "node_modules" / "@opentui").is_dir()
|
||||
pkg = PROJECT_ROOT / "ui-opentui"
|
||||
built = pkg / "dist" / "main.js"
|
||||
return built.is_file() and (pkg / "node_modules" / "@opentui").is_dir()
|
||||
|
||||
|
||||
def _make_opentui_argv(tui_dev: bool) -> tuple[list[str], Path]:
|
||||
"""Argv for the native OpenTUI engine (Bun runs the TypeScript entry directly).
|
||||
"""Argv for the native OpenTUI engine under Node 26 (no Bun).
|
||||
|
||||
Prefers the v4 Solid + Effect-at-boundary engine
|
||||
(``ui-tui-opentui-v2/src/entry/main.tsx``); falls back to the superseded React
|
||||
build (``ui-tui-opentui/src/entry.real.tsx``) if the v2 package isn't present.
|
||||
Returns the argv and the package cwd. ``tui_dev`` adds ``--watch`` for hot reload.
|
||||
Builds the Solid + Effect-at-boundary engine (``ui-opentui``) with esbuild
|
||||
(``npm run build`` → ``dist/main.js``) when the bundle is missing (or always, in
|
||||
``--dev``), then launches it on Node with the experimental FFI flag:
|
||||
|
||||
The spawned ``tui_gateway`` resolves its Python from this checkout
|
||||
(``HERMES_PYTHON_SRC_ROOT`` env → ``<root>/.venv`` …); since the package lives at
|
||||
``<root>/ui-tui-opentui-v2`` the gateway's default source-root resolution lands on
|
||||
``PROJECT_ROOT`` without extra plumbing.
|
||||
node --experimental-ffi --no-warnings dist/main.js
|
||||
|
||||
``--no-warnings`` keeps the ExperimentalWarning off the TUI's stderr. Returns the
|
||||
argv and the package cwd.
|
||||
|
||||
The spawned ``tui_gateway`` resolves its Python from ``HERMES_PYTHON_SRC_ROOT``
|
||||
(the caller sets it to ``PROJECT_ROOT``); the built bundle's own fallback also
|
||||
walks up to the checkout root, so the gateway resolves correctly either way.
|
||||
"""
|
||||
candidates = (
|
||||
(PROJECT_ROOT / "ui-tui-opentui-v2", Path("src") / "entry" / "main.tsx"),
|
||||
(PROJECT_ROOT / "ui-tui-opentui", Path("src") / "entry.real.tsx"),
|
||||
)
|
||||
for app_dir, rel in candidates:
|
||||
entry = app_dir / rel
|
||||
if entry.is_file():
|
||||
# Preflight: Bun runs the TS entry directly (no build step), so the
|
||||
# package's node_modules must already be installed or the first
|
||||
# `import '@opentui/...'` dies with a cryptic resolve error and a blank
|
||||
# UI. Fail loudly with the fix instead.
|
||||
if not (app_dir / "node_modules" / "@opentui").is_dir():
|
||||
print(
|
||||
f"OpenTUI engine dependencies are not installed in {app_dir}.\n"
|
||||
f"Run: (cd {app_dir} && bun install)\n"
|
||||
f"Or unset HERMES_TUI_ENGINE to use the default Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
bun = _bun_bin()
|
||||
args = [bun]
|
||||
if tui_dev:
|
||||
args.append("--watch")
|
||||
args.append(str(entry))
|
||||
return args, app_dir
|
||||
app_dir = PROJECT_ROOT / "ui-opentui"
|
||||
entry_src = app_dir / "src" / "entry" / "main.tsx"
|
||||
if not entry_src.is_file():
|
||||
print(
|
||||
f"OpenTUI v2 engine entry not found at {entry_src}.\n"
|
||||
f"Unset HERMES_TUI_ENGINE to use the default Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
tried = ", ".join(str(app_dir / rel) for app_dir, rel in candidates)
|
||||
print(
|
||||
f"OpenTUI engine entry not found (tried: {tried}).\n"
|
||||
f"Unset HERMES_TUI_ENGINE to use the default Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
node = _node26_bin()
|
||||
|
||||
# The esbuild build needs the package's node_modules (esbuild + the @opentui
|
||||
# packages + the native blob). Without them the build/launch dies cryptically.
|
||||
if not (app_dir / "node_modules" / "@opentui").is_dir():
|
||||
print(
|
||||
f"OpenTUI engine dependencies are not installed in {app_dir}.\n"
|
||||
f"Run: (cd {app_dir} && npm install)\n"
|
||||
f"Or unset HERMES_TUI_ENGINE to use the default Ink engine.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
built = app_dir / "dist" / "main.js"
|
||||
if tui_dev or not built.is_file():
|
||||
npm = _opentui_npm()
|
||||
if not os.environ.get("HERMES_QUIET"):
|
||||
print("Building the OpenTUI engine…", file=sys.stderr)
|
||||
result = subprocess.run(
|
||||
[npm, "run", "build"],
|
||||
cwd=str(app_dir),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
combined = f"{result.stdout or ''}{result.stderr or ''}".strip()
|
||||
preview = "\n".join(combined.splitlines()[-30:])
|
||||
print("OpenTUI engine build failed.", file=sys.stderr)
|
||||
if preview:
|
||||
print(preview, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return [node, "--experimental-ffi", "--no-warnings", str(built)], app_dir
|
||||
|
||||
|
||||
def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
|
||||
"""TUI: --dev → tsx src; else node dist (HERMES_TUI_DIR prebuilt or esbuild).
|
||||
|
||||
Dual-engine: when ``HERMES_TUI_ENGINE``/``display.tui_engine`` selects the
|
||||
native OpenTUI engine, dispatch to ``_make_opentui_argv`` (Bun) BEFORE any
|
||||
Node bootstrap — a Bun-only host must not be forced through ``_ensure_tui_node``.
|
||||
native OpenTUI engine, dispatch to ``_make_opentui_argv`` (Node 26 + its own
|
||||
esbuild build) BEFORE the Ink Node bootstrap — the OpenTUI engine resolves its
|
||||
own Node >= 26.3 and builds its own bundle, so it must not be routed through
|
||||
``_ensure_tui_node`` / the Ink prebuilt-dir logic.
|
||||
"""
|
||||
if _resolve_tui_engine() == "opentui":
|
||||
return _make_opentui_argv(tui_dev)
|
||||
|
|
@ -2060,14 +2109,14 @@ def _launch_tui(
|
|||
# ("--expose-gc is not allowed in NODE_OPTIONS") and refuses to start.
|
||||
# It is passed as a direct argv flag in _make_tui_argv() instead.
|
||||
#
|
||||
# ENGINE-GATED: --max-old-space-size is a V8/Node flag. The OpenTUI engine
|
||||
# runs on Bun (JavaScriptCore), which has no such flag and would error/ignore
|
||||
# it — so only apply the Node heap sizing for the Ink engine.
|
||||
if _resolve_tui_engine() == "ink":
|
||||
_tokens = env.get("NODE_OPTIONS", "").split()
|
||||
if not any(t.startswith("--max-old-space-size=") for t in _tokens):
|
||||
_tokens.append(f"--max-old-space-size={_resolve_tui_heap_mb()}")
|
||||
env["NODE_OPTIONS"] = " ".join(_tokens)
|
||||
# Both TUI engines run on Node/V8 now — Ink, and the native OpenTUI engine
|
||||
# (Node 26 + node:ffi). So --max-old-space-size (a V8/Node flag) applies to
|
||||
# both. (Pre-Node-26 the OpenTUI engine ran on Bun/JavaScriptCore, which has
|
||||
# no such flag; that gate is gone now that the engine is Node.)
|
||||
_tokens = env.get("NODE_OPTIONS", "").split()
|
||||
if not any(t.startswith("--max-old-space-size=") for t in _tokens):
|
||||
_tokens.append(f"--max-old-space-size={_resolve_tui_heap_mb()}")
|
||||
env["NODE_OPTIONS"] = " ".join(_tokens)
|
||||
# HERMES_TUI_RESUME is an internal hand-off from the Python wrapper to the
|
||||
# Ink app. Because we start from os.environ.copy(), an exported/stale value
|
||||
# in the user's shell would otherwise make a plain `hermes --tui` try to
|
||||
|
|
|
|||
|
|
@ -1924,80 +1924,73 @@ install_node_deps() {
|
|||
restore_dirty_lockfiles "$INSTALL_DIR"
|
||||
}
|
||||
|
||||
# Provision the native OpenTUI engine (Bun runtime + `bun install` in
|
||||
# ui-tui-opentui-v2). The launcher (hermes_cli/main.py:_opentui_available)
|
||||
# defaults the TUI to OpenTUI only when Bun resolves AND the v2 package's
|
||||
# node_modules/@opentui is present, otherwise it transparently falls back to
|
||||
# the legacy Ink engine. So this stage is STRICTLY best-effort: any failure
|
||||
# (unsupported platform, no network, Bun install fails, `bun install` fails)
|
||||
# logs a warning and returns 0. A skipped OpenTUI setup just means the user
|
||||
# gets Ink — breaking the install would be far worse than skipping OpenTUI.
|
||||
# Every sub-step is guarded with `|| { log_warn ...; return 0; }`; this
|
||||
# function never `exit`s and never returns non-zero.
|
||||
# Provision the native OpenTUI engine on NODE 26.3+ (no Bun): `npm install` +
|
||||
# `npm run build` (esbuild → dist/main.js) in ui-opentui. The engine's
|
||||
# renderer loads via the experimental `node:ffi` API that only exists on Node
|
||||
# 26.3+. The launcher (hermes_cli/main.py:_opentui_available) only uses OpenTUI
|
||||
# when a Node >= 26.3 resolves AND the v2 package is built; otherwise it falls
|
||||
# back to the Ink engine. So this stage is STRICTLY best-effort: any failure
|
||||
# (unsupported platform, Node < 26.3, no network, install/build fails) logs a
|
||||
# warning and returns 0. A skipped OpenTUI setup just means the user gets Ink —
|
||||
# breaking the install would be far worse than skipping OpenTUI. Every sub-step
|
||||
# is guarded; this function never `exit`s and never returns non-zero.
|
||||
install_opentui() {
|
||||
# Bun is unreliable on Windows and Termux — keep those hosts on Ink.
|
||||
# (detect_os already aborts the bash installer on Windows, but guard
|
||||
# defensively in case this is ever reached via a stage runner.)
|
||||
# node:ffi isn't validated on Windows/Termux — keep those hosts on Ink.
|
||||
if [ "$OS" = "windows" ] || [ "$DISTRO" = "termux" ] || [ "$OS" = "android" ]; then
|
||||
log_info "Skipping OpenTUI engine (unsupported platform) — using Ink."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Only meaningful if the v2 package is present in this checkout.
|
||||
if [ ! -f "$INSTALL_DIR/ui-tui-opentui-v2/package.json" ]; then
|
||||
log_info "Skipping OpenTUI engine (ui-tui-opentui-v2 not present) — using Ink."
|
||||
if [ ! -f "$INSTALL_DIR/ui-opentui/package.json" ]; then
|
||||
log_info "Skipping OpenTUI engine (ui-opentui not present) — using Ink."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Setting up OpenTUI engine (native TUI)..."
|
||||
log_info "Setting up OpenTUI engine (native TUI, Node 26.3+ / node:ffi)..."
|
||||
|
||||
# Resolve Bun: PATH, then the common install dirs the launcher also probes
|
||||
# (HERMES_BUN > bun on PATH > ~/.bun/bin/bun > /usr/local/bin > Homebrew).
|
||||
local bun_bin=""
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
bun_bin="$(command -v bun)"
|
||||
else
|
||||
for cand in "$HOME/.bun/bin/bun" "/usr/local/bin/bun" "/opt/homebrew/bin/bun"; do
|
||||
if [ -x "$cand" ]; then
|
||||
bun_bin="$cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Install Bun if it isn't already resolvable (idempotent: skipped when found).
|
||||
if [ -z "$bun_bin" ]; then
|
||||
log_info "Installing Bun (https://bun.sh)..."
|
||||
# Best-effort: bun.sh's installer drops the binary at ~/.bun/bin/bun.
|
||||
# On any failure (no network, install error) warn and fall back to Ink.
|
||||
if ! curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1; then
|
||||
log_warn "OpenTUI engine setup skipped (Bun install failed) — the Ink engine will be used; see https://bun.sh"
|
||||
return 0
|
||||
fi
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
bun_bin="$(command -v bun)"
|
||||
elif [ -x "$HOME/.bun/bin/bun" ]; then
|
||||
bun_bin="$HOME/.bun/bin/bun"
|
||||
# Resolve a Node >= 26.3.0 (the node:ffi floor): HERMES_NODE > node on PATH,
|
||||
# version-checked. We do NOT install Node here — if one new enough isn't
|
||||
# available the launcher cleanly falls back to Ink.
|
||||
local node_bin=""
|
||||
for cand in "${HERMES_NODE:-}" "$(command -v node 2>/dev/null || true)"; do
|
||||
[ -n "$cand" ] && [ -x "$cand" ] || continue
|
||||
if "$cand" -e 'const p=process.versions.node.split(".").map(Number); process.exit(p[0]>26||(p[0]===26&&p[1]>=3)?0:1)' 2>/dev/null; then
|
||||
node_bin="$cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$node_bin" ]; then
|
||||
log_warn "OpenTUI engine setup skipped (needs Node >= 26.3.0; none found) — using the Ink engine. Install Node 26.3+ or set HERMES_NODE."
|
||||
return 0
|
||||
fi
|
||||
log_success "Node found ($("$node_bin" --version 2>/dev/null || echo "unknown"))"
|
||||
|
||||
if [ -z "$bun_bin" ]; then
|
||||
log_warn "OpenTUI engine setup skipped (Bun not available after install) — the Ink engine will be used; see https://bun.sh"
|
||||
# npm ships with Node; the build (`node scripts/build.mjs`) runs fine on any
|
||||
# recent Node — only the runtime needs 26.3, which the launcher re-checks.
|
||||
local npm_bin
|
||||
npm_bin="$(command -v npm 2>/dev/null || true)"
|
||||
if [ -z "$npm_bin" ]; then
|
||||
log_warn "OpenTUI engine setup skipped (npm not found) — using the Ink engine."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_success "Bun found ($("$bun_bin" --version 2>/dev/null || echo "unknown"))"
|
||||
cd "$INSTALL_DIR/ui-opentui" || { log_warn "OpenTUI engine setup skipped (cd failed) — using Ink."; return 0; }
|
||||
|
||||
# Pull the v2 package's dependencies — this fetches the per-arch
|
||||
# @opentui/core-<arch> native lib automatically. Idempotent.
|
||||
log_info "Installing OpenTUI dependencies (bun install)..."
|
||||
cd "$INSTALL_DIR/ui-tui-opentui-v2"
|
||||
if ! "$bun_bin" install >/dev/null 2>&1; then
|
||||
log_warn "OpenTUI engine setup skipped (bun install failed) — the Ink engine will be used; see https://bun.sh"
|
||||
# Pull deps (fetches the per-arch @opentui/core-<arch> native lib) then build
|
||||
# the Node bundle (dist/main.js). Both idempotent.
|
||||
log_info "Installing OpenTUI dependencies (npm install)..."
|
||||
if ! "$npm_bin" install --no-audit --no-fund >/dev/null 2>&1; then
|
||||
log_warn "OpenTUI engine setup skipped (npm install failed) — the Ink engine will be used."
|
||||
return 0
|
||||
fi
|
||||
log_info "Building OpenTUI engine (npm run build)..."
|
||||
if ! "$npm_bin" run build >/dev/null 2>&1; then
|
||||
log_warn "OpenTUI engine setup skipped (build failed) — the Ink engine will be used."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_success "OpenTUI engine ready (now the default; set HERMES_TUI_ENGINE=ink for the legacy Ink engine)."
|
||||
log_success "OpenTUI engine ready (opt-in: HERMES_TUI_ENGINE=opentui; default is Ink)."
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,9 +87,15 @@ export default tseslint.config(
|
|||
},
|
||||
},
|
||||
{
|
||||
// The eslint flat config is JS-only; the typed parser project service does not
|
||||
// cover it, so disable type-checking there to avoid parser errors.
|
||||
files: ["eslint.config.mjs"],
|
||||
// Build/config scripts (the eslint flat config, the esbuild build.mjs, the
|
||||
// vitest config) are not part of the typed TS program, so the project service
|
||||
// can't type them — disable type-aware linting there to avoid parser errors,
|
||||
// and declare the Node globals they use (process, console, URL).
|
||||
files: ["**/*.mjs", "*.config.ts"],
|
||||
...tseslint.configs.disableTypeChecked,
|
||||
languageOptions: {
|
||||
...tseslint.configs.disableTypeChecked.languageOptions,
|
||||
globals: { process: "readonly", console: "readonly", URL: "readonly", URLSearchParams: "readonly" },
|
||||
},
|
||||
},
|
||||
)
|
||||
4916
ui-opentui/package-lock.json
generated
Normal file
4916
ui-opentui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@hermes/ui-tui-opentui-v2",
|
||||
"name": "@hermes/ui-opentui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
@ -10,24 +10,32 @@
|
|||
"lint:fix": "eslint . --fix",
|
||||
"fmt": "prettier --write src",
|
||||
"fix": "prettier --write src && eslint . --fix",
|
||||
"test": "bun test",
|
||||
"build": "node scripts/build.mjs",
|
||||
"start": "node --experimental-ffi --no-warnings dist/main.js",
|
||||
"test": "vitest run",
|
||||
"check": "bash scripts/check.sh",
|
||||
"dev": "bun src/entry/main.tsx"
|
||||
"dev": "node scripts/build.mjs && node --experimental-ffi --no-warnings dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentui/core": "0.3.2",
|
||||
"@opentui/keymap": "0.3.2",
|
||||
"@opentui/solid": "0.3.2",
|
||||
"@opentui/core": "0.4.0",
|
||||
"@opentui/keymap": "0.4.0",
|
||||
"@opentui/solid": "0.4.0",
|
||||
"effect": "4.0.0-beta.78",
|
||||
"solid-js": "1.9.10"
|
||||
"solid-js": "1.9.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
"@babel/preset-typescript": "^7.29.7",
|
||||
"@effect/vitest": "^4.0.0-beta.78",
|
||||
"@eslint/js": "^9",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^24",
|
||||
"babel-preset-solid": "^1.9.12",
|
||||
"esbuild": "^0.28.0",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-unused-imports": "^4",
|
||||
"prettier": "^3",
|
||||
"typescript": "^5",
|
||||
"typescript-eslint": "^8"
|
||||
"typescript-eslint": "^8",
|
||||
"vitest": "^4.1.8"
|
||||
}
|
||||
}
|
||||
75
ui-opentui/scripts/acceptance.sh
Normal file
75
ui-opentui/scripts/acceptance.sh
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env bash
|
||||
# Single acceptance command for the Bun→Node-26 switchover (see
|
||||
# docs/plans/opentui-node26-build-spec.md). Proves, on a Node 26.3 host, that the
|
||||
# OpenTUI v2 engine runs WITHOUT Bun and at parity:
|
||||
#
|
||||
# 1. Node >= 26.3 present (the node:ffi floor); reports whether bun is on PATH
|
||||
# (the engine must NOT need it).
|
||||
# 2. `npm run check` — prettier + tsc + eslint + vitest (151+), all on Node.
|
||||
# 3. live-gateway transport smoke — spawns the real Python tui_gateway via the
|
||||
# node:child_process client, asserts gateway.ready + session.create.
|
||||
# (Skipped if no Hermes venv resolves — CI parity.)
|
||||
# 4. selection/markdown smoke in a real tmux TTY — asserts the native <markdown>
|
||||
# (Tree-sitter) PAINTS under node --experimental-ffi and that a selection
|
||||
# copies the RAW markdown source. (Skipped if tmux is unavailable.)
|
||||
#
|
||||
# Run: cd ui-opentui && HERMES_PYTHON_SRC_ROOT=<checkout-root> bash scripts/acceptance.sh
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Absolute node, so a fresh tmux pane (which won't inherit our PATH / fnm shim)
|
||||
# runs the SAME Node 26.3, not the shell's default.
|
||||
NODE_BIN="$(command -v node || echo node)"
|
||||
|
||||
pass=0; fail=0; skip=0
|
||||
ok() { echo " ✅ $1"; pass=$((pass+1)); }
|
||||
bad() { echo " ❌ $1"; fail=$((fail+1)); }
|
||||
note() { echo " ⏭ $1"; skip=$((skip+1)); }
|
||||
|
||||
echo "== [1/4] runtime: Node >= 26.3, Bun-free =="
|
||||
NODE_V="$(node -p 'process.versions.node' 2>/dev/null || echo 0.0.0)"
|
||||
node -e 'const [a,b]=process.versions.node.split(".").map(Number); process.exit(a>26||(a===26&&b>=3)?0:1)' \
|
||||
&& ok "node $NODE_V (>= 26.3)" || bad "node $NODE_V is below the 26.3 node:ffi floor"
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
note "bun is on PATH ($(command -v bun)) — fine; the engine does not use it (proven below)"
|
||||
else
|
||||
ok "no bun on PATH — single-runtime host"
|
||||
fi
|
||||
|
||||
echo "== [2/4] check: prettier + tsc + eslint + vitest =="
|
||||
if bash scripts/check.sh >/tmp/accept-check.log 2>&1; then ok "check green ($(grep -c 'passed' /tmp/accept-check.log >/dev/null 2>&1; grep -oE '[0-9]+ passed' /tmp/accept-check.log | tail -1))"
|
||||
else bad "check failed — see /tmp/accept-check.log"; tail -20 /tmp/accept-check.log; fi
|
||||
|
||||
echo "== [3/4] live-gateway transport smoke (real Python gateway, no Bun) =="
|
||||
if [ -n "${HERMES_PYTHON_SRC_ROOT:-}" ] || [ -x "../.venv/bin/python" ]; then
|
||||
rm -rf .accept && node scripts/build.mjs src/test/liveGateway.smoke.ts .accept >/dev/null 2>&1
|
||||
OUT="$(node --experimental-ffi --no-warnings .accept/liveGateway.smoke.js 2>&1)"
|
||||
echo "$OUT" | grep -q "^PASS" && ok "$(echo "$OUT" | grep '^PASS')" || { echo "$OUT" | grep -qE "TRANSPORT ERROR|SKIP" && note "gateway smoke skipped (no python/model)" || bad "gateway smoke: $(echo "$OUT" | head -1)"; }
|
||||
rm -rf .accept
|
||||
else
|
||||
note "no HERMES_PYTHON_SRC_ROOT / venv — gateway smoke skipped"
|
||||
fi
|
||||
|
||||
echo "== [4/4] selection/markdown smoke in a real tmux TTY (tree-sitter under FFI) =="
|
||||
if command -v tmux >/dev/null 2>&1; then
|
||||
rm -rf .accept && node scripts/build.mjs src/test/selectionCopy.smoke.tsx .accept >/dev/null 2>&1
|
||||
rm -f /tmp/accept-sel.json
|
||||
S="accept-$$"
|
||||
tmux kill-session -t "$S" 2>/dev/null
|
||||
tmux new-session -d -s "$S" -x 120 -y 40
|
||||
tmux send-keys -t "$S" "SEL_SMOKE_OUT=/tmp/accept-sel.json $NODE_BIN --experimental-ffi --no-warnings $PWD/.accept/selectionCopy.smoke.js; tmux wait-for -S $S" Enter
|
||||
tmux wait-for "$S" 2>/dev/null || sleep 6
|
||||
tmux kill-session -t "$S" 2>/dev/null
|
||||
if node -e 'process.exit(require("/tmp/accept-sel.json").pass===true?0:1)' 2>/dev/null; then
|
||||
ok "markdown painted + selection copied source (tree-sitter under node FFI)"
|
||||
else
|
||||
bad "selection/markdown smoke failed — see /tmp/accept-sel.json"; cat /tmp/accept-sel.json 2>/dev/null
|
||||
fi
|
||||
rm -rf .accept
|
||||
else
|
||||
note "tmux not available — markdown smoke skipped (run it on a TTY host)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== acceptance: $pass passed, $fail failed, $skip skipped =="
|
||||
[ "$fail" -eq 0 ] && { echo "ACCEPTANCE: PASS"; exit 0; } || { echo "ACCEPTANCE: FAIL"; exit 1; }
|
||||
75
ui-opentui/scripts/build.mjs
Normal file
75
ui-opentui/scripts/build.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Build the OpenTUI v2 Solid app for Node 26 (no Bun).
|
||||
*
|
||||
* Mirrors OpenTUI's own Node recipe (`~/github/opentui/.../run-node26.mjs` +
|
||||
* `packages/solid/scripts/solid-transform.ts`): apply babel-preset-solid in
|
||||
* `generate:"universal"` mode with `moduleName:"@opentui/solid"` to every app
|
||||
* .tsx/.jsx, and force solid-js to its CLIENT/universal build (the package's
|
||||
* `node` export condition points at the SSR `server.js`, which lacks the
|
||||
* reactive primitives the universal renderer needs).
|
||||
*
|
||||
* `@opentui/core` stays EXTERNAL: it resolves its per-arch native `libopentui.so`
|
||||
* (and the tree-sitter worker) from its own package dir via `import.meta.url`;
|
||||
* bundling it would break those paths.
|
||||
*
|
||||
* Run with the Node that will launch the app:
|
||||
* node scripts/build.mjs # → dist/main.js (app entry)
|
||||
* node scripts/build.mjs <entry.tsx> <outdir> # build an arbitrary entry (smokes/spikes)
|
||||
* Launch:
|
||||
* node --experimental-ffi --no-warnings dist/main.js
|
||||
*/
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { transformAsync } from '@babel/core'
|
||||
import tsPreset from '@babel/preset-typescript'
|
||||
import solidPreset from 'babel-preset-solid'
|
||||
import * as esbuild from 'esbuild'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
/** esbuild plugin that reproduces @opentui/solid's transform + solid-js resolution. */
|
||||
const opentuiSolid = {
|
||||
name: 'opentui-solid',
|
||||
setup(build) {
|
||||
// App JSX (.tsx/.jsx, never node_modules) → babel-preset-solid (universal).
|
||||
build.onLoad({ filter: /\.[cm]?[jt]sx$/ }, async args => {
|
||||
if (args.path.includes('/node_modules/')) return null
|
||||
const code = await readFile(args.path, 'utf8')
|
||||
const out = await transformAsync(code, {
|
||||
filename: args.path,
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [[solidPreset, { moduleName: '@opentui/solid', generate: 'universal' }], [tsPreset]]
|
||||
})
|
||||
return { contents: out?.code ?? '', loader: 'js' }
|
||||
})
|
||||
|
||||
// Force the universal/client solid-js build (node condition → server.js otherwise).
|
||||
build.onResolve({ filter: /^solid-js$/ }, () => ({ path: require.resolve('solid-js/dist/solid.js') }))
|
||||
build.onResolve({ filter: /^solid-js\/store$/ }, () => ({ path: require.resolve('solid-js/store/dist/store.js') }))
|
||||
}
|
||||
}
|
||||
|
||||
const [, , entryArg, outdirArg] = process.argv
|
||||
const entry = entryArg ? resolve(process.cwd(), entryArg) : resolve(root, 'src/entry/main.tsx')
|
||||
const outdir = outdirArg ? resolve(process.cwd(), outdirArg) : resolve(root, 'dist')
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: [entry],
|
||||
outdir,
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
target: 'node26',
|
||||
splitting: true,
|
||||
sourcemap: true,
|
||||
logLevel: 'info',
|
||||
// Native blob + tree-sitter worker resolve from @opentui/core's own dir at runtime.
|
||||
external: ['@opentui/core', '@opentui/core/*'],
|
||||
plugins: [opentuiSolid],
|
||||
define: { 'process.env.OPENTUI_BUN_ONLY_EXAMPLES': '"false"' }
|
||||
})
|
||||
26
ui-opentui/scripts/check.sh
Executable file
26
ui-opentui/scripts/check.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Phase gate for the native OpenTUI engine (spec v4 §5). Runs the full headless
|
||||
# suite: format + type-check + lint + vitest (which includes the headless frame
|
||||
# gate via captureCharFrame). The agentic smoke (docs/plans/opentui-smoke.md) is
|
||||
# the live complement — run BOTH every phase.
|
||||
#
|
||||
# Runs entirely on Node 26.3 (no Bun). The OpenTUI native core loads via node:ffi
|
||||
# under --experimental-ffi; vitest passes that flag to its test forks (see
|
||||
# vitest.config.ts). Requires `node -v` == v26.3.x on PATH.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "== [1/4] format (prettier --check) =="
|
||||
npx prettier --check src
|
||||
|
||||
echo "== [2/4] type-check =="
|
||||
npm run --silent type-check
|
||||
|
||||
echo "== [3/4] lint =="
|
||||
npm run --silent lint
|
||||
|
||||
echo "== [4/4] vitest (incl. headless frame gate) =="
|
||||
npm test
|
||||
|
||||
echo "== check OK =="
|
||||
|
|
@ -5,7 +5,9 @@
|
|||
* No gateway is spawned (purely the fixture seeded into the store via the resume
|
||||
* path), so typing won't reach a backend — it's for viewing/scrolling.
|
||||
*
|
||||
* Run: bun scripts/demo.tsx
|
||||
* Run (Node 26 — needs the esbuild/Solid transform, then --experimental-ffi):
|
||||
* node scripts/build.mjs scripts/demo.tsx .demo
|
||||
* node --experimental-ffi --no-warnings .demo/demo.js # inside tmux (needs a TTY)
|
||||
* DEMO_TOTAL=200 fixture messages to seed (default 200)
|
||||
* HERMES_TUI_MAX_MESSAGES=80 cap → the "⤒ N earlier messages" notice fires
|
||||
* Quit: Ctrl+C.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* DEV BENCH FIXTURE — NOT a test, NOT production code. A deterministic generator
|
||||
* for a REALISTIC heavy session, consumed by `scripts/mem-bench.tsx`. Excluded
|
||||
* from `bun test` (not a *.test.ts) and lint-clean.
|
||||
* from the vitest run (not a *.test.ts) and lint-clean.
|
||||
*
|
||||
* The old synthetic bench pushed tiny 3-delta turns (~5.5 mounted nodes each) —
|
||||
* an unrealistic per-message cost. Real transcripts are LUMPY: an assistant turn
|
||||
|
|
@ -3,21 +3,25 @@
|
|||
* harness for tuning the rolling `HERMES_TUI_MAX_MESSAGES` cap. Mounts the
|
||||
* production `<App store={createSessionStore()}>` under the `@opentui/solid` test
|
||||
* renderer and samples `process.memoryUsage()` + the mounted-renderable count +
|
||||
* `getAllocatorStats().activeAllocations`, forcing `Bun.gc(true)` before each
|
||||
* sample. Excluded from `bun test` (not a *.test.ts) and lint-clean.
|
||||
* `getAllocatorStats().activeAllocations`, forcing `global.gc()` before each
|
||||
* sample. Excluded from the test run (not a *.test.ts) and lint-clean.
|
||||
*
|
||||
* It pushes a REALISTIC heavy-session fixture (scripts/fixture.ts) — varied user
|
||||
* turns + fat multi-part assistant turns (markdown + reasoning + several tool
|
||||
* headers) — because per-message size varies hugely, so message-count is only a
|
||||
* LOOSE memory proxy and we're choosing a cap default.
|
||||
*
|
||||
* Uncapped: MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=100000 bun scripts/mem-bench.tsx
|
||||
* Capped: MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=1500 bun scripts/mem-bench.tsx
|
||||
* node scripts/build.mjs scripts/mem-bench.tsx .bench # build once (Solid+TS → JS)
|
||||
* Uncapped: MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=100000 \
|
||||
* node --experimental-ffi --expose-gc --no-warnings .bench/mem-bench.js
|
||||
* Capped: MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=1500 \
|
||||
* node --experimental-ffi --expose-gc --no-warnings .bench/mem-bench.js
|
||||
*
|
||||
* Run each cap as a SEPARATE bun invocation so the WASM/native heap starts fresh.
|
||||
* Run each cap as a SEPARATE node invocation so the WASM/native heap starts fresh.
|
||||
* The matrix loop:
|
||||
* for cap in 400 1500 3000 6000 100000; do \
|
||||
* MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=$cap bun scripts/mem-bench.tsx; done
|
||||
* MEM_BENCH_TOTAL=8000 HERMES_TUI_MAX_MESSAGES=$cap \
|
||||
* node --experimental-ffi --expose-gc --no-warnings .bench/mem-bench.js; done
|
||||
*
|
||||
* Signal: native `getAllocatorStats().activeAllocations` (the Zig-side allocator
|
||||
* count — every live renderable/Yoga subtree contributes) and the recursive
|
||||
|
|
@ -26,8 +30,8 @@
|
|||
* meaningful comparison is the STEADY-STATE plateau: capped should flatten after
|
||||
* ~CAP messages; uncapped should keep climbing.
|
||||
*
|
||||
* GC: forces `Bun.gc(true)` (synchronous) before each sample to measure RETAINED
|
||||
* memory, not garbage. (`--expose-gc`/`global.gc` is unavailable under Bun.)
|
||||
* GC: forces `global.gc()` (synchronous) before each sample to measure RETAINED
|
||||
* memory, not garbage — run Node with `--expose-gc` or the GC call is a no-op.
|
||||
*
|
||||
* RESUME PATH: after the live push matrix, builds the full fixture as a settled
|
||||
* Message[] and `commitSnapshot`s it (the resume path), reporting mounted nodes +
|
||||
|
|
@ -50,6 +54,12 @@ const cap = process.env.HERMES_TUI_MAX_MESSAGES ?? '(default 400)'
|
|||
|
||||
const MB = (bytes: number) => (bytes / 1024 / 1024).toFixed(1)
|
||||
|
||||
/** Force a synchronous full GC to measure RETAINED memory. No-op without `node --expose-gc`. */
|
||||
const forceGc = (): void => {
|
||||
const gc = (globalThis as { gc?: () => void }).gc
|
||||
if (gc) gc()
|
||||
}
|
||||
|
||||
/** Recursively count every Renderable under root (a proxy for live Yoga nodes). */
|
||||
function descendantCount(node: Renderable): number {
|
||||
let n = 0
|
||||
|
|
@ -85,7 +95,7 @@ async function main(): Promise<void> {
|
|||
async function sample(pushes: number): Promise<void> {
|
||||
await setup.renderOnce()
|
||||
await setup.flush()
|
||||
Bun.gc(true) // synchronous, full GC — measure retained, not garbage
|
||||
forceGc() // synchronous, full GC — measure retained, not garbage
|
||||
const m = process.memoryUsage()
|
||||
const alloc = lib.getAllocatorStats()
|
||||
const renderables = descendantCount(setup.renderer.root)
|
||||
|
|
@ -125,7 +135,7 @@ async function main(): Promise<void> {
|
|||
// already isolated per-renderer-root, but RSS is process-global.)
|
||||
store.clearTranscript()
|
||||
setup.renderer.destroy()
|
||||
Bun.gc(true)
|
||||
forceGc()
|
||||
|
||||
// ── RESUME PATH: build the full settled fixture and commitSnapshot it (the
|
||||
// resume hydrate path). Verifies the slice-before-set fix bounds resume mounting
|
||||
|
|
@ -150,7 +160,7 @@ async function main(): Promise<void> {
|
|||
resumeStore.commitSnapshot(fullFixture)
|
||||
await resumeSetup.renderOnce()
|
||||
await resumeSetup.flush()
|
||||
Bun.gc(true)
|
||||
forceGc()
|
||||
const rm = process.memoryUsage()
|
||||
const ralloc = lib.getAllocatorStats()
|
||||
const rrenderables = descendantCount(resumeSetup.renderer.root)
|
||||
|
|
@ -5,9 +5,30 @@
|
|||
* everything is best-effort and never throws into the view.
|
||||
*/
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { platform } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
/** Run a command, optionally piping `input` to stdin; resolve its stdout bytes. */
|
||||
/** Whether `cmd` resolves on PATH (cached). We DON'T spawn missing tools: a failed
|
||||
* spawn + writing to its dead stdin pipe raises EPIPE/SIGPIPE, and OpenTUI used to
|
||||
* treat SIGPIPE as a shutdown signal — i.e. a clipboard miss would quit the TUI.
|
||||
* Skipped on Windows (the built-in `clip` is always present; PATHEXT complicates
|
||||
* a filename probe). */
|
||||
const commandCache = new Map<string, boolean>()
|
||||
function commandExists(cmd: string): boolean {
|
||||
if (platform() === 'win32') return true
|
||||
const cached = commandCache.get(cmd)
|
||||
if (cached !== undefined) return cached
|
||||
const dirs = (process.env.PATH ?? '').split(':').filter(Boolean)
|
||||
const found = dirs.some(dir => existsSync(join(dir, cmd)))
|
||||
commandCache.set(cmd, found)
|
||||
return found
|
||||
}
|
||||
|
||||
/** Run a command, optionally piping `input` to stdin; resolve its stdout bytes.
|
||||
* Best-effort and crash-proof: every stream error (incl. EPIPE → SIGPIPE on a
|
||||
* clipboard tool that exits early) is swallowed so a failed copy never throws out
|
||||
* of the boundary or signals the process. */
|
||||
function run(cmd: string, args: string[] = [], input?: string): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let child
|
||||
|
|
@ -19,9 +40,18 @@ function run(cmd: string, args: string[] = [], input?: string): Promise<Buffer>
|
|||
}
|
||||
const out: Buffer[] = []
|
||||
child.on('error', reject)
|
||||
child.stdout?.on('error', () => {}) // a closed stdout pipe must not throw
|
||||
child.stdout?.on('data', (c: Buffer) => out.push(c))
|
||||
child.on('close', code => (code === 0 ? resolve(Buffer.concat(out)) : reject(new Error(`${cmd} exit ${code}`))))
|
||||
if (input !== undefined) child.stdin?.end(input)
|
||||
if (input !== undefined && child.stdin) {
|
||||
// Writing to a tool that died/closed early raises EPIPE (→ SIGPIPE). Swallow it.
|
||||
child.stdin.on('error', () => {})
|
||||
try {
|
||||
child.stdin.end(input)
|
||||
} catch {
|
||||
// pipe already gone — nothing to flush
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +77,9 @@ function copyCandidates(): Array<[string, string[]]> {
|
|||
|
||||
/** Copy `text` to the clipboard: OSC 52 (always) + the first native command that works. */
|
||||
export async function writeClipboard(text: string): Promise<void> {
|
||||
writeOsc52(text)
|
||||
writeOsc52(text) // primary path — SSH/tmux-safe, no subprocess
|
||||
for (const [cmd, args] of copyCandidates()) {
|
||||
if (!commandExists(cmd)) continue // never spawn a missing tool (avoids EPIPE/SIGPIPE)
|
||||
try {
|
||||
await run(cmd, args, text)
|
||||
return
|
||||
|
|
@ -79,6 +110,7 @@ export async function readClipboardImage(): Promise<{ data: string; mime: string
|
|||
])
|
||||
}
|
||||
for (const [cmd, args] of tries) {
|
||||
if (!commandExists(cmd)) continue // skip missing tools (no pointless failing spawns)
|
||||
try {
|
||||
const buf = await run(cmd, args)
|
||||
if (buf.length) {
|
||||
|
|
@ -21,6 +21,8 @@
|
|||
* layer Schema-decodes them once at the boundary (spec v4 §3.3); this client
|
||||
* stays decode-agnostic so the transport and the schema evolve independently.
|
||||
*/
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
|
||||
import type { Log } from '../log.ts'
|
||||
import { resolvePython, resolveSrcRoot } from './python.ts'
|
||||
|
||||
|
|
@ -49,7 +51,7 @@ const STARTUP_TIMEOUT_MS = (() => {
|
|||
})()
|
||||
|
||||
export class RawGatewayClient {
|
||||
private proc: ReturnType<typeof Bun.spawn> | null = null
|
||||
private proc: ChildProcessWithoutNullStreams | null = null
|
||||
private pending = new Map<string, Pending>()
|
||||
private reqId = 0
|
||||
private stdinBuffer = ''
|
||||
|
|
@ -76,26 +78,27 @@ export class RawGatewayClient {
|
|||
|
||||
this.log.info('gateway', 'spawning tui_gateway', { python, cwd, srcRoot })
|
||||
|
||||
const proc = Bun.spawn([python, '-m', 'tui_gateway.entry'], {
|
||||
const proc = spawn(python, ['-m', 'tui_gateway.entry'], {
|
||||
cwd,
|
||||
env,
|
||||
stdin: 'pipe',
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
onExit: (_p, code, signal) => {
|
||||
// Identity guard: a stale child's late exit must not act after a restart
|
||||
// has already installed a new `this.proc` (else it'd null the live child).
|
||||
if (this.proc !== proc) return
|
||||
const reason = `gateway exited (code=${code ?? 'null'} signal=${signal ?? 'null'})`
|
||||
this.log.warn('gateway', reason)
|
||||
this.rejectAll(reason)
|
||||
this.proc = null
|
||||
this.onExit?.(reason)
|
||||
}
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
})
|
||||
// Identity guard: a stale child's late exit/error must not act after a restart
|
||||
// has already installed a new `this.proc` (else it'd null the live child).
|
||||
// Nulling `this.proc` here makes a subsequent finish() a no-op (idempotent),
|
||||
// covering the ENOENT case where 'error' fires and 'exit' does not.
|
||||
const finish = (reason: string) => {
|
||||
if (this.proc !== proc) return
|
||||
this.log.warn('gateway', reason)
|
||||
this.rejectAll(reason)
|
||||
this.proc = null
|
||||
this.onExit?.(reason)
|
||||
}
|
||||
proc.on('exit', (code, signal) => finish(`gateway exited (code=${code ?? 'null'} signal=${signal ?? 'null'})`))
|
||||
proc.on('error', err => finish(`gateway spawn error: ${err instanceof Error ? err.message : String(err)}`))
|
||||
this.proc = proc
|
||||
void this.readStdout(proc)
|
||||
void this.readStderr(proc)
|
||||
this.readStdout(proc)
|
||||
this.readStderr(proc)
|
||||
|
||||
// Startup-readiness watchdog: a child that hangs on import (wrong python /
|
||||
// missing dep) never emits the unsolicited `gateway.ready` handshake, leaving
|
||||
|
|
@ -111,53 +114,38 @@ export class RawGatewayClient {
|
|||
}, STARTUP_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
private async readStdout(proc: ReturnType<typeof Bun.spawn>): Promise<void> {
|
||||
const stream = proc.stdout
|
||||
if (!(stream instanceof ReadableStream)) return
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
try {
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
this.stdinBuffer += decoder.decode(value, { stream: true })
|
||||
let nl: number
|
||||
while ((nl = this.stdinBuffer.indexOf('\n')) >= 0) {
|
||||
const line = this.stdinBuffer.slice(0, nl)
|
||||
this.stdinBuffer = this.stdinBuffer.slice(nl + 1)
|
||||
if (line.trim()) this.dispatch(line)
|
||||
}
|
||||
private readStdout(proc: ChildProcessWithoutNullStreams): void {
|
||||
proc.stdout.setEncoding('utf8')
|
||||
proc.stdout.on('data', (chunk: string) => {
|
||||
this.stdinBuffer += chunk
|
||||
let nl: number
|
||||
while ((nl = this.stdinBuffer.indexOf('\n')) >= 0) {
|
||||
const line = this.stdinBuffer.slice(0, nl)
|
||||
this.stdinBuffer = this.stdinBuffer.slice(nl + 1)
|
||||
if (line.trim()) this.dispatch(line)
|
||||
}
|
||||
} catch (cause) {
|
||||
this.log.error('gateway', 'stdout read loop failed', { cause: String(cause) })
|
||||
}
|
||||
})
|
||||
proc.stdout.on('error', cause => this.log.error('gateway', 'stdout read loop failed', { cause: String(cause) }))
|
||||
}
|
||||
|
||||
private async readStderr(proc: ReturnType<typeof Bun.spawn>): Promise<void> {
|
||||
const stream = proc.stderr
|
||||
if (!(stream instanceof ReadableStream)) return
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
private readStderr(proc: ChildProcessWithoutNullStreams): void {
|
||||
let buf = ''
|
||||
try {
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
buf += decoder.decode(value, { stream: true })
|
||||
let nl: number
|
||||
while ((nl = buf.indexOf('\n')) >= 0) {
|
||||
const line = buf.slice(0, nl)
|
||||
buf = buf.slice(nl + 1)
|
||||
if (line.trim()) {
|
||||
this.log.debug('gateway.stderr', line)
|
||||
// Surface as a synthetic gateway.stderr event (matches Ink).
|
||||
this.onEvent({ type: 'gateway.stderr', payload: { line } })
|
||||
}
|
||||
proc.stderr.setEncoding('utf8')
|
||||
proc.stderr.on('data', (chunk: string) => {
|
||||
buf += chunk
|
||||
let nl: number
|
||||
while ((nl = buf.indexOf('\n')) >= 0) {
|
||||
const line = buf.slice(0, nl)
|
||||
buf = buf.slice(nl + 1)
|
||||
if (line.trim()) {
|
||||
this.log.debug('gateway.stderr', line)
|
||||
// Surface as a synthetic gateway.stderr event (matches Ink).
|
||||
this.onEvent({ type: 'gateway.stderr', payload: { line } })
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// stderr pipe closing on exit is expected; ignore.
|
||||
}
|
||||
})
|
||||
// stderr pipe closing on exit is expected; ignore errors.
|
||||
proc.stderr.on('error', () => {})
|
||||
}
|
||||
|
||||
private dispatch(line: string): void {
|
||||
|
|
@ -208,7 +196,7 @@ export class RawGatewayClient {
|
|||
// comes from subscribe() → client.start()). A null proc rejects below.
|
||||
const proc = this.proc
|
||||
const stdin = proc?.stdin
|
||||
if (!stdin || typeof stdin === 'number') return Promise.reject(new Error('gateway not running'))
|
||||
if (!stdin) return Promise.reject(new Error('gateway not running'))
|
||||
|
||||
const id = `r${++this.reqId}`
|
||||
const frame = JSON.stringify({ id, jsonrpc: '2.0', method, params: params ?? {} }) + '\n'
|
||||
|
|
@ -231,11 +219,10 @@ export class RawGatewayClient {
|
|||
})
|
||||
|
||||
try {
|
||||
// FileSink.write/flush type as `number | Promise<number>`; for a piped
|
||||
// child stdin they return synchronously — this is intentional
|
||||
// fire-and-forget (no backpressure await), so mark the intent explicitly.
|
||||
void stdin.write(frame)
|
||||
void stdin.flush()
|
||||
// Newline-delimited JSON to the child's stdin. Fire-and-forget: the write
|
||||
// returns a backpressure boolean we intentionally ignore (frames are tiny
|
||||
// and ordered; Node flushes the pipe itself).
|
||||
stdin.write(frame)
|
||||
} catch (cause) {
|
||||
this.pending.delete(id)
|
||||
clearTimeout(timer)
|
||||
|
|
@ -255,10 +242,10 @@ export class RawGatewayClient {
|
|||
this.startupTimer = undefined
|
||||
this.rejectAll('gateway stopping')
|
||||
const stdin = this.proc?.stdin
|
||||
if (stdin && typeof stdin !== 'number') {
|
||||
if (stdin) {
|
||||
try {
|
||||
// Fire-and-forget EOF: FileSink.end types as `number | Promise<number>`.
|
||||
void stdin.end()
|
||||
// Close stdin → child sees EOF and exits.
|
||||
stdin.end()
|
||||
} catch {
|
||||
// already gone
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
* sets it) so the child resolves modules against the right checkout.
|
||||
*/
|
||||
import { existsSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
export function resolvePython(root: string): string {
|
||||
const configured = process.env.HERMES_PYTHON?.trim() || process.env.PYTHON?.trim()
|
||||
|
|
@ -33,7 +33,17 @@ export function resolvePython(root: string): string {
|
|||
export function resolveSrcRoot(): string {
|
||||
const configured = process.env.HERMES_PYTHON_SRC_ROOT?.trim()
|
||||
if (configured) return configured
|
||||
// Fallback: the package lives at <root>/ui-tui-opentui-v2/src/boundary/gateway,
|
||||
// so the checkout root is four levels up from this file's dir.
|
||||
return resolve(import.meta.dir, '../../../../')
|
||||
// Fallback (no launcher env): walk up from this module to the Hermes checkout
|
||||
// root — the dir holding the `hermes_cli` package / `pyproject.toml`. Bundle-
|
||||
// agnostic, so it works whether running the source tree (.../src/boundary/gateway)
|
||||
// or the built `dist/main.js`. (Under the real launcher this never runs — the
|
||||
// launcher always sets HERMES_PYTHON_SRC_ROOT.)
|
||||
let dir = import.meta.dirname
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (existsSync(resolve(dir, 'hermes_cli')) || existsSync(resolve(dir, 'pyproject.toml'))) return dir
|
||||
const parent = dirname(dir)
|
||||
if (parent === dir) break
|
||||
dir = parent
|
||||
}
|
||||
return resolve(import.meta.dirname, '../../../../')
|
||||
}
|
||||
|
|
@ -13,6 +13,24 @@ import { createCliRenderer, type CliRenderer, type KeyEvent, type Selection } fr
|
|||
import { Deferred, Effect } from 'effect'
|
||||
|
||||
import { RendererError } from './errors.ts'
|
||||
import { getLog } from './log.ts'
|
||||
|
||||
/**
|
||||
* The text a finished selection copies: the RENDERED text the user highlighted,
|
||||
* verbatim (`getSelectedText()` does correct same-line merging). Markdown markers
|
||||
* are concealed in the pretty render, so a partial selection cannot recover source —
|
||||
* this copies exactly what was highlighted (the `/copy` command gives full source).
|
||||
* Total by construction — a copy must NEVER throw out of an input/event handler
|
||||
* (that would tear down the render loop).
|
||||
*/
|
||||
function selectionCopyText(selection: Selection): string {
|
||||
try {
|
||||
return selection.getSelectedText()
|
||||
} catch (cause) {
|
||||
getLog().warn('copy', 'getSelectedText failed', { cause: String(cause) })
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export interface RendererOptions {
|
||||
/** Mouse tracking on/off (from decoded display config). */
|
||||
|
|
@ -28,7 +46,7 @@ export interface RendererOptions {
|
|||
/**
|
||||
* Copy a mouse selection (item 1). When there's a live selection, Ctrl+C copies
|
||||
* it (this callback) instead of interrupting/quitting — opencode's selection
|
||||
* key precedence (`app.tsx:388`).
|
||||
* key precedence (`app.tsx:388`). Receives the rendered text the user highlighted.
|
||||
*/
|
||||
readonly onCopySelection?: (text: string) => void
|
||||
}
|
||||
|
|
@ -48,6 +66,12 @@ export const acquireRenderer = Effect.fn('Renderer.acquire')(function* (options:
|
|||
targetFps: 60,
|
||||
// prompts own Ctrl+C → deny/cancel (gotcha §8 #6); the global quit is gated on !blocked.
|
||||
exitOnCtrlC: false,
|
||||
// OpenTUI's default exitSignals include SIGPIPE + SIGBUS, and its handler
|
||||
// calls renderer.destroy() — so a broken clipboard pipe (writeClipboard
|
||||
// spawning xclip/wl-copy that dies) raises SIGPIPE and QUITS THE TUI on
|
||||
// copy. SIGPIPE/SIGBUS are not shutdown intents; restrict to the genuine
|
||||
// termination signals so a stray pipe error can never tear down the UI.
|
||||
exitSignals: ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP'],
|
||||
useKittyKeyboard: {},
|
||||
useMouse: options.mouse
|
||||
}),
|
||||
|
|
@ -73,7 +97,8 @@ export const acquireRenderer = Effect.fn('Renderer.acquire')(function* (options:
|
|||
// Copy a live mouse selection first (item 1) — takes precedence over the
|
||||
// interrupt/quit machine and over a blocking prompt's cancel.
|
||||
if (options.onCopySelection) {
|
||||
const text = renderer.getSelection()?.getSelectedText() ?? ''
|
||||
const selection = renderer.getSelection()
|
||||
const text = selection ? selectionCopyText(selection) : ''
|
||||
if (text) {
|
||||
options.onCopySelection(text)
|
||||
renderer.clearSelection()
|
||||
|
|
@ -95,7 +120,7 @@ export const acquireRenderer = Effect.fn('Renderer.acquire')(function* (options:
|
|||
const onCopy = options.onCopySelection
|
||||
if (onCopy) {
|
||||
renderer.on('selection', (selection: Selection) => {
|
||||
const text = selection.getSelectedText()
|
||||
const text = selectionCopyText(selection)
|
||||
if (text) onCopy(text)
|
||||
})
|
||||
}
|
||||
|
|
@ -274,9 +274,11 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) {
|
|||
}
|
||||
|
||||
// Copy a mouse selection to the clipboard (item 1) — OSC 52 + native command.
|
||||
// Copies exactly the rendered text the user highlighted (markers are concealed
|
||||
// in the pretty render; the `/copy` command copies a full response's source).
|
||||
const onCopySelection = (text: string) => {
|
||||
void writeClipboard(text)
|
||||
flashHint('Copied to clipboard')
|
||||
flashHint('Copied selection')
|
||||
}
|
||||
|
||||
// Paste an IMAGE (item 1): read the clipboard image and attach it to the
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
/**
|
||||
* Assistant-text extraction (the /copy command's pure logic). An assistant turn's
|
||||
* answer lives in `parts` (the `type:'text'` fragments, concatenated) while live,
|
||||
* OR in `.text` once settled/resumed. We copy the ANSWER only — reasoning and tool
|
||||
* parts are excluded. `nthAssistantResponse` indexes newest-first (1-based).
|
||||
* Assistant-text extraction (the `/copy [n]` command's pure logic). An assistant
|
||||
* turn's answer lives in `parts` (the `type:'text'` fragments, concatenated) while
|
||||
* live, OR in `.text` once settled/resumed. We copy the ANSWER only — reasoning and
|
||||
* tool parts are excluded. `nthAssistantResponse` indexes newest-first (1-based).
|
||||
*
|
||||
* NB: mouse-selection copies the RENDERED text verbatim (native OpenTUI selection,
|
||||
* `selection.getSelectedText()`), not markdown source — markers are concealed in the
|
||||
* pretty render and can't be recovered from a partial selection (user's choice). The
|
||||
* source-bearing path is this `/copy` command, which copies a whole response's source.
|
||||
*/
|
||||
import type { Message } from './store.ts'
|
||||
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* pull the answer text out of a live (parts) or settled (.text) assistant turn,
|
||||
* excluding reasoning/tool parts; pick the n-th newest assistant response.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { assistantResponses, messageText, nthAssistantResponse } from '../logic/copy.ts'
|
||||
import type { Message } from '../logic/store.ts'
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { envFlag } from '../logic/env.ts'
|
||||
|
||||
43
ui-opentui/src/test/gateway.test.ts
Normal file
43
ui-opentui/src/test/gateway.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Phase 0 boundary test (spec v4 §5 Layer 1). Exercises the GatewayService shape
|
||||
* through the FakeGateway layer using @effect/vitest's `it.effect`: subscribe
|
||||
* receives emitted events; request records the call. Proves the Effect<->Solid
|
||||
* seam (subscribe) and the typed request path compile + run.
|
||||
*
|
||||
* `it.effect` runs the program in a scoped test runtime (TestClock + TestConsole
|
||||
* provided automatically), replacing the old hand-rolled ManagedRuntime shim.
|
||||
* The fake layer carries per-test controller state (we assert `controller.calls`),
|
||||
* so it's provided locally — the testing guide's allowed one-off, not a shared
|
||||
* `layer(...)` group.
|
||||
*/
|
||||
import { assert, describe, it } from '@effect/vitest'
|
||||
import { Effect } from 'effect'
|
||||
|
||||
import { GatewayService } from '../boundary/gateway/GatewayService.ts'
|
||||
import type { GatewayEvent } from '../boundary/schema/GatewayEvent.ts'
|
||||
import { fakeGatewayLayerWith, makeFakeGateway } from '../entry/fakeGateway.ts'
|
||||
|
||||
describe('GatewayService via FakeGateway (Phase 0)', () => {
|
||||
it.effect('subscribe receives emitted events; request records the call', () => {
|
||||
const controller = makeFakeGateway('sess-123')
|
||||
const received: GatewayEvent[] = []
|
||||
|
||||
return Effect.gen(function* () {
|
||||
const gateway = yield* GatewayService
|
||||
const unsubscribe = yield* gateway.subscribe(event => received.push(event))
|
||||
// Emit after subscribing (synchronous fan-out in the fake).
|
||||
controller.emit({ type: 'gateway.ready' })
|
||||
controller.emit({ type: 'message.start' })
|
||||
yield* gateway.request('prompt.submit', { text: 'hi' })
|
||||
unsubscribe()
|
||||
controller.emit({ type: 'message.complete' }) // dropped: unsubscribed
|
||||
|
||||
assert.strictEqual(gateway.sessionId(), 'sess-123')
|
||||
assert.deepStrictEqual(
|
||||
received.map(e => e.type),
|
||||
['gateway.ready', 'message.start']
|
||||
)
|
||||
assert.deepStrictEqual(controller.calls, [{ method: 'prompt.submit', params: { text: 'hi' } }])
|
||||
}).pipe(Effect.provide(fakeGatewayLayerWith(controller)))
|
||||
})
|
||||
})
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* are capped within a sliding window, stale attempts are pruned, and recovery is
|
||||
* refused with no session. Plus opencode-style exponential backoff (1s→30s cap).
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
backoffMs,
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* the next Up back at the newest. Per-directory file persistence is exercised
|
||||
* only via the injected `persist` sink here.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { createPromptHistory } from '../logic/history.ts'
|
||||
|
||||
|
|
@ -5,7 +5,10 @@
|
|||
* decode-once boundary + the handshake against the real server, NOT a fake.
|
||||
*
|
||||
* Skips gracefully when no Hermes python resolves (CI without the venv). Run
|
||||
* explicitly: `bun src/test/liveGateway.smoke.ts`.
|
||||
* explicitly (no Bun):
|
||||
* node scripts/build.mjs src/test/liveGateway.smoke.ts .out
|
||||
* node --experimental-ffi --no-warnings .out/liveGateway.smoke.js
|
||||
* (or `bash scripts/acceptance.sh`, which runs it as the transport gate).
|
||||
*/
|
||||
import { Effect, ManagedRuntime } from 'effect'
|
||||
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* Rotation is exercised with a real temp dir; since LOG_MAX_BYTES (5 MiB) is not
|
||||
* exported, we seed the live file above the cap so the next write must rotate.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* content, multiple pastes round-trip, unknown refs pass through, single-pass
|
||||
* replace keeps a self-referential paste safe. (input polish.)
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { createPasteStore, shouldPlaceholder } from '../logic/pastes.ts'
|
||||
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* THEMED (brand name/icon from the theme, not hardcoded), and that applying a
|
||||
* custom skin re-themes the brand name reactively.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { createSessionStore } from '../logic/store.ts'
|
||||
import { App } from '../view/App.tsx'
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* history maps into the store's Message[], folding tool rows ({name,context},
|
||||
* NO text) into the preceding assistant turn's ordered parts so they render.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { mapResumeHistory } from '../logic/resume.ts'
|
||||
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* events decode with typed narrowing, unrecognized `type` and malformed payloads
|
||||
* are SKIPPED (Option.none) so a stray wire event never tears down the stream.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { Option, Schema } from 'effect'
|
||||
|
||||
import { GatewayEventSchema } from '../boundary/schema/GatewayEvent.ts'
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* Slash dispatch test (spec §5 Layer 3/4). Pure logic: parse + the dispatch
|
||||
* ladder (client → slash.exec → command.dispatch) against a fake SlashContext.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
dispatchSlash,
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* `parts[]` model — text/tool interleave in one turn, tool start↔complete matched
|
||||
* by id and updated IN PLACE, `{output,exit_code}` envelope stripped.
|
||||
*/
|
||||
import { afterEach, describe, expect, test } from 'bun:test'
|
||||
import { afterEach, describe, expect, test } from 'vitest'
|
||||
|
||||
import { DEFAULT_THEME } from '../logic/theme.ts'
|
||||
import { createSessionStore, type Message } from '../logic/store.ts'
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* toolOutput unit test (spec v4 §5 Layer 4 — Hermes-specific contract). The
|
||||
* `{output,exit_code}` envelope unwrap + the line/char collapse, as pure data.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { collapseToolOutput, stripAnsi, stripOmittedNote, stripToolEnvelope, truncate } from '../logic/toolOutput.ts'
|
||||
|
||||
|
|
@ -63,6 +63,8 @@ export function Markdown(props: { text: string; streaming?: boolean; fg?: string
|
|||
// `internalBlockMode="top-level"` is the anti-flicker mode (stable head blocks
|
||||
// aren't re-rendered per delta); `tableOptions` gives native GFM tables with
|
||||
// inline formatting; `fg` overrides the base text color (muted for reasoning).
|
||||
// `conceal` hides the markdown markers for clean prose — mouse-selection then
|
||||
// copies the RENDERED text (markers gone) via native selection, by design.
|
||||
return (
|
||||
<markdown
|
||||
content={props.text}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"noImplicitReturns": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "@opentui/solid",
|
||||
"types": ["bun"],
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
59
ui-opentui/vitest.config.ts
Normal file
59
ui-opentui/vitest.config.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Vitest config for the Node-26 engine (replaces `bun test`).
|
||||
*
|
||||
* Same Solid transform as the production build (scripts/build.mjs): app .tsx/.jsx
|
||||
* go through babel-preset-solid in `generate:"universal"` mode with
|
||||
* `moduleName:"@opentui/solid"`, and solid-js resolves to its CLIENT build (the
|
||||
* package `node` condition points at the SSR `server.js`, which lacks the
|
||||
* universal reactive primitives). See docs/plans/opentui-node26-build-spec.md.
|
||||
*
|
||||
* render.test.tsx mounts the native @opentui/solid test renderer, so the test
|
||||
* forks need `--experimental-ffi`. We inject it into NODE_OPTIONS here (the config
|
||||
* runs in vitest's main process before it forks workers, which inherit the env) —
|
||||
* self-contained and cross-platform, no shell wrapper needed. The other suites are
|
||||
* pure logic.
|
||||
*/
|
||||
import { transformAsync } from '@babel/core'
|
||||
import tsPreset from '@babel/preset-typescript'
|
||||
import solidPreset from 'babel-preset-solid'
|
||||
import { createRequire } from 'node:module'
|
||||
import type { Plugin } from 'vite'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
// Ensure forked test workers load OpenTUI's native core via node:ffi.
|
||||
const ffiOpts = '--experimental-ffi --no-warnings'
|
||||
if (!(process.env.NODE_OPTIONS ?? '').includes('--experimental-ffi')) {
|
||||
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} ${ffiOpts}`.trim()
|
||||
}
|
||||
|
||||
const opentuiSolid = (): Plugin => ({
|
||||
name: 'opentui-solid',
|
||||
enforce: 'pre',
|
||||
async transform(code, id) {
|
||||
const path = id.split('?')[0]
|
||||
if (!/\.[cm]?[jt]sx$/.test(path) || path.includes('/node_modules/')) return null
|
||||
const out = await transformAsync(code, {
|
||||
filename: path,
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
sourceMaps: true,
|
||||
presets: [[solidPreset, { moduleName: '@opentui/solid', generate: 'universal' }], [tsPreset]]
|
||||
})
|
||||
return out?.code ? { code: out.code, map: out.map } : null
|
||||
}
|
||||
})
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [opentuiSolid()],
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^solid-js\/store$/, replacement: require.resolve('solid-js/store/dist/store.js') },
|
||||
{ find: /^solid-js$/, replacement: require.resolve('solid-js/dist/solid.js') }
|
||||
]
|
||||
},
|
||||
test: {
|
||||
include: ['src/test/**/*.test.{ts,tsx}']
|
||||
}
|
||||
})
|
||||
|
|
@ -1,514 +0,0 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@hermes/ui-tui-opentui-v2",
|
||||
"dependencies": {
|
||||
"@opentui/core": "0.3.2",
|
||||
"@opentui/keymap": "0.3.2",
|
||||
"@opentui/solid": "0.3.2",
|
||||
"effect": "4.0.0-beta.78",
|
||||
"solid-js": "1.9.10",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9",
|
||||
"@types/bun": "latest",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-unused-imports": "^4",
|
||||
"prettier": "^3",
|
||||
"typescript": "^5",
|
||||
"typescript-eslint": "^8",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
|
||||
|
||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
|
||||
|
||||
"@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="],
|
||||
|
||||
"@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.3.2", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.3.2", "@opentui/core-darwin-x64": "0.3.2", "@opentui/core-linux-arm64": "0.3.2", "@opentui/core-linux-arm64-musl": "0.3.2", "@opentui/core-linux-x64": "0.3.2", "@opentui/core-linux-x64-musl": "0.3.2", "@opentui/core-win32-arm64": "0.3.2", "@opentui/core-win32-x64": "0.3.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-5rCVS/3Obb3iLqg/egLCRArt7hAu3lX/9PWVHqUlnJylCT6b5NYDFljt0r3x8v3VG98LB71UzpnDv7DgmGKATw=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rFnGfqqEOGiUTbxglpiDA500KeRqcI1ukemhNfDrEzx3imAArS8mFZSuUG7ib31P5EpX+PXuvg0G9/3YXfgWeQ=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z+/GxKvB3NzMDSwuyWR7HDStbaNRf5a09lt5W9b4BGmCAFW/mbX0Tuh3kloubcMgiq5vLnVaNzY19hrIgJrjGQ=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-0TRuGNR+2GGk0rQuDaIxkIa/3Ty/XySfeOQLAUX4Jqaifky04As69fYT17yOhHqg5viCJUAGG/SdW8IH6C2osg=="],
|
||||
|
||||
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-noDKfwYUjutQUx5rtoyKrBIYaeSCmAmtxOSJdnKecyWEhMygtdHp4ssPtxzsZMuQAliHogCmD7vUG/pGMgcXMQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EZp+Lg9eZwzwNny4l2ACHdC95JbEKYbor1WImnm6IEo1e2Fgl9mltYv2J+i+Ea+dXQbrkK/MRIw7CRusxfqFFA=="],
|
||||
|
||||
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/FNGeJYhCHcIE3qBIo+nl8014NZ8u/XXUwQY2RjuWhMnzK9kQUfZ3cW4FP6FkOA/k4jGvByya47193II6djFtw=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-/zb5nCZKDgBS1UEQXrzTYUbbTPFMygbiZ/BfNWjEDIbm63gVzQ7pVouYbGP+88CRXIJtwT6LeoVPgp9nmOrDWQ=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-q8xqMhW1jlJVzos+A5+FXRquH01j1ZHmrNi/9++W1Ebz3LQYn+8Z8j7rcV/meIiDuo9nyRHigQQT+cy9xV4N2g=="],
|
||||
|
||||
"@opentui/keymap": ["@opentui/keymap@0.3.2", "", { "dependencies": { "@opentui/core": "0.3.2" }, "peerDependencies": { "@opentui/react": "0.3.2", "@opentui/solid": "0.3.2", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-y+IPBagxPvVrKIISmlfvO7ScvVIXGMmV92x0O+WzQ2vPf6fi5xgJc4YNmgEACfGdnM3ub14MkgEXqMOeUN7ZQA=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.3.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.3.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-Yff0gSwIY/o0XeMciYeAUkQtea8bWzR0UjjVglmcBe13hWuJZt/GfjbDMdNNQ8zCrLubLEh04an5fYXCd7NMYQ=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/type-utils": "8.60.1", "@typescript-eslint/utils": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.1", "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1" } }, "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.60.1", "", {}, "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.1", "@typescript-eslint/tsconfig-utils": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.7", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ=="],
|
||||
|
||||
"babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
|
||||
|
||||
"babel-preset-solid": ["babel-preset-solid@1.9.12", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.34", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
|
||||
|
||||
"effect": ["effect@4.0.0-beta.78", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-j79Rl9QpHwMz/ZJWLNpZoiVj9N7zHqiLKN5EcYd/A8J1oqejILWQLfc4HPlvqHqKC8SK55LJ+X4gy4ONJ+JpfQ=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.368", "", {}, "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||
|
||||
"eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.4.1", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
|
||||
|
||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||
|
||||
"glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
|
||||
|
||||
"html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"msgpackr": ["msgpackr@2.0.3", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-vFKpMYFTEQujRQxvdS/u6zlfesws0J40K74w6E1fVsYnIa9WKJKB5xIVVON8L7S39hCNrCVGXcPjrYmCb9lT+w=="],
|
||||
|
||||
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
|
||||
|
||||
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="],
|
||||
|
||||
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
||||
|
||||
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="],
|
||||
|
||||
"semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
|
||||
|
||||
"seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="],
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.60.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.1", "@typescript-eslint/parser": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
||||
|
||||
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
preload = ["@opentui/solid/preload"]
|
||||
|
||||
[test]
|
||||
preload = ["@opentui/solid/preload"]
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Phase gate for the native OpenTUI engine (spec v4 §5). Runs the full headless
|
||||
# suite: type-check + lint + bun test (which includes the headless frame gate via
|
||||
# captureCharFrame). The agentic smoke (docs/plans/opentui-smoke.md) is the live
|
||||
# complement — run BOTH every phase.
|
||||
#
|
||||
# OpenTUI core is Bun/FFI-only — everything runs via bun, never node (gotcha §8 #8).
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "== [1/4] format (prettier --check) =="
|
||||
bunx prettier --check src
|
||||
|
||||
echo "== [2/4] type-check =="
|
||||
bun run type-check
|
||||
|
||||
echo "== [3/4] lint =="
|
||||
bun run lint
|
||||
|
||||
echo "== [4/4] bun test (incl. headless frame gate) =="
|
||||
bun test
|
||||
|
||||
echo "== check OK =="
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Phase 0 boundary test (spec v4 §5 Layer 1). Exercises the GatewayService
|
||||
* shape through the FakeGateway layer using the bun-test Effect helper:
|
||||
* subscribe receives emitted events; request records the call. Proves the
|
||||
* Effect<->Solid seam (subscribe) and the typed request path compile + run.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { Effect } from 'effect'
|
||||
|
||||
import { GatewayService } from '../boundary/gateway/GatewayService.ts'
|
||||
import type { GatewayEvent } from '../boundary/schema/GatewayEvent.ts'
|
||||
import { makeFakeGateway, fakeGatewayLayerWith } from '../entry/fakeGateway.ts'
|
||||
import { testLayer } from './lib/effect.ts'
|
||||
|
||||
describe('GatewayService via FakeGateway (Phase 0)', () => {
|
||||
test('subscribe receives emitted events; request records the call', async () => {
|
||||
const controller = makeFakeGateway('sess-123')
|
||||
const layer = fakeGatewayLayerWith(controller)
|
||||
|
||||
const received: GatewayEvent[] = []
|
||||
|
||||
const program = Effect.gen(function* () {
|
||||
const gateway = yield* GatewayService
|
||||
const unsubscribe = yield* gateway.subscribe(event => received.push(event))
|
||||
// Emit after subscribing (synchronous fan-out in the fake).
|
||||
controller.emit({ type: 'gateway.ready' })
|
||||
controller.emit({ type: 'message.start' })
|
||||
yield* gateway.request('prompt.submit', { text: 'hi' })
|
||||
unsubscribe()
|
||||
controller.emit({ type: 'message.complete' }) // dropped: unsubscribed
|
||||
return gateway.sessionId()
|
||||
})
|
||||
|
||||
const sessionId = await testLayer(layer, program)
|
||||
|
||||
expect(sessionId).toBe('sess-123')
|
||||
expect(received.map(e => e.type)).toEqual(['gateway.ready', 'message.start'])
|
||||
expect(controller.calls).toEqual([{ method: 'prompt.submit', params: { text: 'hi' } }])
|
||||
})
|
||||
})
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* test/lib/effect.ts — recovers `it.effect` ergonomics on plain `bun test`
|
||||
* WITHOUT @effect/vitest (spec v4 §5 Layer 1). Each test gets a per-call
|
||||
* ManagedRuntime so layers/services don't leak between tests; the TestClock
|
||||
* is installed via `TestClock.layer()` (beta.78 replacement for 3.x
|
||||
* `TestContext.layer()` — there is no `TestContext` on this line).
|
||||
*
|
||||
* Gotcha (verified against effect@4.0.0-beta.78 .d.ts): `TestClock.adjust`/
|
||||
* `setTime` return `Effect<void>` with no R requirement, so a program that only
|
||||
* uses them won't surface TestClock in its R channel — hence the
|
||||
* `R extends TestClock.TestClock` constraint to force the layer to be provided.
|
||||
*/
|
||||
import { type Effect, Layer, ManagedRuntime } from 'effect'
|
||||
import { TestClock } from 'effect/testing'
|
||||
|
||||
export const TestClockLayer: Layer.Layer<TestClock.TestClock> = TestClock.layer()
|
||||
|
||||
/** Run an Effect with the test clock available; fresh runtime per call. */
|
||||
export async function testEffect<A, E, R extends TestClock.TestClock>(effect: Effect.Effect<A, E, R>): Promise<A> {
|
||||
const runtime = ManagedRuntime.make(TestClockLayer)
|
||||
try {
|
||||
return await runtime.runPromise(effect as Effect.Effect<A, E, TestClock.TestClock>)
|
||||
} finally {
|
||||
await runtime.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
/** Run an Effect against a provided layer (+ test clock), fresh runtime per call. */
|
||||
export async function testLayer<A, E, ROut, RErr>(
|
||||
layer: Layer.Layer<ROut, RErr, never>,
|
||||
effect: Effect.Effect<A, E, ROut | TestClock.TestClock>
|
||||
): Promise<A> {
|
||||
const full = Layer.mergeAll(TestClock.layer(), layer)
|
||||
const runtime = ManagedRuntime.make(full)
|
||||
try {
|
||||
return await runtime.runPromise(effect)
|
||||
} finally {
|
||||
await runtime.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
export { TestClock }
|
||||
Loading…
Add table
Add a link
Reference in a new issue