From e2ed4cdb1f8cfc01dc8b8bff966e600f384898e6 Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Wed, 4 Feb 2026 15:14:57 -0800 Subject: [PATCH 1/4] feat(headless): add openwrk TUI dashboard --- packages/headless/README.md | 10 + packages/headless/package.json | 5 +- packages/headless/src/cli.ts | 274 ++++++- packages/headless/src/tui/app.tsx | 443 +++++++++++ packages/headless/tsconfig.json | 7 +- pnpm-lock.yaml | 1160 ++++++++++++++++++++++++++++- 6 files changed, 1885 insertions(+), 14 deletions(-) create mode 100644 packages/headless/src/tui/app.tsx diff --git a/packages/headless/README.md b/packages/headless/README.md index d48aaa3f..f592563b 100644 --- a/packages/headless/README.md +++ b/packages/headless/README.md @@ -9,6 +9,13 @@ npm install -g openwrk openwrk start --workspace /path/to/workspace --approval auto ``` +When run in a TTY, `openwrk` shows an interactive status dashboard with service health, ports, and +connection details. Use `openwrk serve` or `--no-tui` for log-only mode. + +```bash +openwrk serve --workspace /path/to/workspace +``` + `openwrk` ships as a compiled binary, so Bun is not required at runtime. `openwrk` downloads and caches the `openwork-server`, `owpenbot`, and `opencode` sidecars on @@ -40,6 +47,9 @@ pnpm --filter openwrk dev -- \ The command prints pairing details (OpenWork server URL + token, OpenCode URL + auth) so remote OpenWork clients can connect. +Use `--detach` to keep services running and exit the dashboard. The detach summary includes the +OpenWork URL, tokens, and the `opencode attach` command. + ## Logging `openwrk` emits a unified log stream from OpenCode, OpenWork server, and Owpenbot. Use JSON format for diff --git a/packages/headless/package.json b/packages/headless/package.json index 6279477e..58b74529 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -43,8 +43,11 @@ }, "dependencies": { "@opencode-ai/sdk": "^1.1.31", + "@opentui/core": "0.1.77", + "@opentui/solid": "0.1.77", "openwork-server": "0.1.2", - "owpenwork": "0.1.19" + "owpenwork": "0.1.19", + "solid-js": "1.9.9" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/packages/headless/src/cli.ts b/packages/headless/src/cli.ts index aa01827f..075ca989 100644 --- a/packages/headless/src/cli.ts +++ b/packages/headless/src/cli.ts @@ -12,6 +12,7 @@ import { createRequire } from "node:module"; import { once } from "node:events"; import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"; +import { startOpenwrkTui, type TuiHandle } from "./tui/app.js"; type ApprovalMode = "manual" | "auto"; @@ -31,6 +32,7 @@ type LoggerChild = { type Logger = { format: LogFormat; + output: "stdout" | "silent"; log: (level: LogLevel, message: string, attributes?: LogAttributes, component?: string) => void; debug: (message: string, attributes?: LogAttributes, component?: string) => void; info: (message: string, attributes?: LogAttributes, component?: string) => void; @@ -39,6 +41,14 @@ type Logger = { child: (component: string, attributes?: LogAttributes) => LoggerChild; }; +type LogEvent = { + time: number; + level: LogLevel; + message: string; + component?: string; + attributes?: LogAttributes; +}; + const FALLBACK_VERSION = "0.1.0"; const DEFAULT_OPENWORK_PORT = 8787; const DEFAULT_OWPENBOT_HEALTH_PORT = 3005; @@ -516,7 +526,7 @@ function prefixStream( buffer = lines.pop() ?? ""; for (const line of lines) { if (!line.trim()) continue; - if (logger.format === "json" && looksLikeOtelLogLine(line)) { + if (logger.output === "stdout" && logger.format === "json" && looksLikeOtelLogLine(line)) { process.stdout.write(`${line}\n`); continue; } @@ -526,7 +536,7 @@ function prefixStream( }); stream.on("end", () => { if (!buffer.trim()) return; - if (logger.format === "json" && looksLikeOtelLogLine(buffer)) { + if (logger.output === "stdout" && logger.format === "json" && looksLikeOtelLogLine(buffer)) { process.stdout.write(`${buffer}\n`); return; } @@ -1376,6 +1386,7 @@ function printHelp(): void { "", "Usage:", " openwrk start [--workspace ] [options]", + " openwrk serve [--workspace ] [options]", " openwrk daemon [run|start|stop|status] [options]", " openwrk workspace [options]", " openwrk instance dispose [options]", @@ -1385,6 +1396,7 @@ function printHelp(): void { "", "Commands:", " start Start OpenCode + OpenWork server + Owpenbot", + " serve Start services and stream logs (no TUI)", " daemon Run openwrk router daemon (multi-workspace)", " workspace Manage workspaces (add/list/switch/path)", " instance Manage workspace instances (dispose)", @@ -1427,9 +1439,14 @@ function printHelp(): void { " --opencode-source auto | bundled | downloaded | external", " --check Run health checks then exit", " --check-events Verify SSE events during check", + " --tui Force interactive dashboard (TTY only)", + " --no-tui Disable interactive dashboard", + " --detach Detach after start and keep services running", " --json Output JSON when applicable", " --verbose Print additional diagnostics", " --log-format Log output format: pretty | json", + " --color Force ANSI color output", + " --no-color Disable ANSI color output", " --run-id Correlation id for logs (default: random UUID)", " --help Show help", " --version Show version", @@ -1881,6 +1898,21 @@ const LOG_LEVEL_NUMBERS: Record = { error: 17, }; +const ANSI = { + reset: "\x1b[0m", + gray: "\x1b[90m", + red: "\x1b[31m", + yellow: "\x1b[33m", + green: "\x1b[32m", + cyan: "\x1b[36m", + magenta: "\x1b[35m", +}; + +function colorize(input: string, color: string, enabled: boolean): string { + if (!enabled) return input; + return `${color}${input}${ANSI.reset}`; +} + function toUnixNano(): string { return (BigInt(Date.now()) * 1_000_000n).toString(); } @@ -1911,6 +1943,9 @@ function createLogger(options: { runId: string; serviceName: string; serviceVersion?: string; + output?: "stdout" | "silent"; + color?: boolean; + onLog?: (event: LogEvent) => void; }): Logger { const host = hostname().trim(); const resource: Record = { @@ -1927,6 +1962,21 @@ function createLogger(options: { "run.id": options.runId, "process.pid": process.pid, }; + const output = options.output ?? "stdout"; + const colorEnabled = options.color ?? false; + const componentColors: Record = { + openwrk: ANSI.gray, + opencode: ANSI.cyan, + "openwork-server": ANSI.green, + owpenbot: ANSI.magenta, + "openwrk-router": ANSI.cyan, + }; + const levelColors: Record = { + debug: ANSI.gray, + info: ANSI.gray, + warn: ANSI.yellow, + error: ANSI.red, + }; const emit = (level: LogLevel, message: string, attributes?: LogAttributes, component?: string) => { const mergedAttributes: LogAttributes = { @@ -1934,6 +1984,14 @@ function createLogger(options: { ...(component ? { "service.component": component } : {}), ...(attributes ?? {}), }; + options.onLog?.({ + time: Date.now(), + level, + message, + component, + attributes: mergedAttributes, + }); + if (output === "silent") return; if (options.format === "json") { const record = { timeUnixNano: toUnixNano(), @@ -1947,8 +2005,15 @@ function createLogger(options: { return; } const label = component ?? options.serviceName; - const levelTag = level === "info" ? "" : ` ${level.toUpperCase()}`; - const tag = label ? `${`[${label}]`}${levelTag}` : levelTag.trim(); + const tagLabel = label ? `[${label}]` : ""; + const levelTag = level === "info" ? "" : level.toUpperCase(); + const coloredLabel = tagLabel + ? colorize(tagLabel, componentColors[label] ?? ANSI.gray, colorEnabled) + : ""; + const coloredLevel = levelTag + ? colorize(levelTag, levelColors[level] ?? ANSI.gray, colorEnabled) + : ""; + const tag = [coloredLabel, coloredLevel].filter(Boolean).join(" "); const line = tag ? `${tag} ${message}` : message; process.stdout.write(`${line}\n`); }; @@ -1963,6 +2028,7 @@ function createLogger(options: { return { format: options.format, + output, log: emit, debug: (message, attrs, component) => emit("debug", message, attrs, component), info: (message, attrs, component) => emit("info", message, attrs, component), @@ -1984,6 +2050,56 @@ function looksLikeOtelLogLine(line: string): boolean { } } +function buildAttachCommand(input: { + url: string; + workspace: string; + username?: string; + password?: string; +}): string { + const parts: string[] = []; + if (input.username && input.password && input.username !== DEFAULT_OPENCODE_USERNAME) { + parts.push(`OPENCODE_SERVER_USERNAME=${input.username}`); + } + if (input.password) { + parts.push(`OPENCODE_SERVER_PASSWORD=${input.password}`); + } + parts.push("opencode", "attach", input.url, "--dir", input.workspace); + return parts.join(" "); +} + +async function runClipboardCommand(command: string, args: string[], text: string): Promise { + return await new Promise((resolve) => { + const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"] }); + child.on("error", () => resolve(false)); + child.stdin?.write(text); + child.stdin?.end(); + child.on("exit", (code) => resolve(code === 0)); + }); +} + +async function copyToClipboard(text: string): Promise<{ copied: boolean; error?: string }> { + const platform = process.platform; + const commands: Array<{ command: string; args: string[] }> = []; + if (platform === "darwin") { + commands.push({ command: "pbcopy", args: [] }); + } else if (platform === "win32") { + commands.push({ command: "clip", args: [] }); + } else { + commands.push({ command: "wl-copy", args: [] }); + commands.push({ command: "xclip", args: ["-selection", "clipboard"] }); + commands.push({ command: "xsel", args: ["--clipboard", "--input"] }); + } + for (const entry of commands) { + try { + const ok = await runClipboardCommand(entry.command, entry.args, text); + if (ok) return { copied: true }; + } catch { + // ignore + } + } + return { copied: false, error: "Clipboard unavailable" }; +} + async function spawnRouterDaemon(args: ParsedArgs, dataDir: string, host: string, port: number) { const self = resolveSelfCommand(); const commandArgs = [ @@ -2196,6 +2312,8 @@ async function runRouterDaemon(args: ParsedArgs) { const outputJson = readBool(args.flags, "json", false); const verbose = readBool(args.flags, "verbose", false, "OPENWRK_VERBOSE"); const logFormat = readLogFormat(args.flags, "log-format", "pretty", "OPENWRK_LOG_FORMAT"); + const colorEnabled = + readBool(args.flags, "color", process.stdout.isTTY, "OPENWRK_COLOR") && !process.env.NO_COLOR; const runId = readFlag(args.flags, "run-id") ?? process.env.OPENWRK_RUN_ID ?? randomUUID(); const cliVersion = await resolveCliVersion(); const logger = createLogger({ @@ -2203,6 +2321,8 @@ async function runRouterDaemon(args: ParsedArgs) { runId, serviceName: "openwrk", serviceVersion: cliVersion, + output: "stdout", + color: colorEnabled, }); const logVerbose = createVerboseLogger(verbose && !outputJson, logger, "openwrk"); const sidecarSource = readBinarySource(args.flags, "sidecar-source", "auto", "OPENWRK_SIDECAR_SOURCE"); @@ -2718,13 +2838,32 @@ async function runStart(args: ParsedArgs) { const checkEvents = readBool(args.flags, "check-events", false); const verbose = readBool(args.flags, "verbose", false, "OPENWRK_VERBOSE"); const logFormat = readLogFormat(args.flags, "log-format", "pretty", "OPENWRK_LOG_FORMAT"); + const detachRequested = readBool(args.flags, "detach", false, "OPENWRK_DETACH"); + const defaultTui = process.stdout.isTTY && !outputJson && !checkOnly && !checkEvents; + const tuiRequested = readBool(args.flags, "tui", defaultTui); + const useTui = tuiRequested && !detachRequested && !outputJson && !checkOnly && !checkEvents && logFormat === "pretty"; + const colorEnabled = + !useTui && readBool(args.flags, "color", process.stdout.isTTY, "OPENWRK_COLOR") && !process.env.NO_COLOR; const runId = readFlag(args.flags, "run-id") ?? process.env.OPENWRK_RUN_ID ?? randomUUID(); const cliVersion = await resolveCliVersion(); + let tui: TuiHandle | undefined; const logger = createLogger({ format: logFormat, runId, serviceName: "openwrk", serviceVersion: cliVersion, + output: useTui ? "silent" : "stdout", + color: colorEnabled, + onLog: (event) => { + if (!tui) return; + const component = event.component ?? "openwrk"; + tui.pushLog({ + time: event.time, + level: event.level, + component, + message: event.message, + }); + }, }); const logVerbose = createVerboseLogger(verbose && !outputJson, logger, "openwrk"); const sidecarSource = readBinarySource(args.flags, "sidecar-source", "auto", "OPENWRK_SIDECAR_SOURCE"); @@ -2829,8 +2968,17 @@ async function runStart(args: ParsedArgs) { const openworkConnect = resolveConnectUrl(openworkPort, connectHost); const openworkConnectUrl = openworkConnect.connectUrl ?? openworkBaseUrl; + const attachCommand = buildAttachCommand({ + url: opencodeConnectUrl, + workspace: resolvedWorkspace, + username: opencodeUsername, + password: opencodePassword, + }); + const children: ChildHandle[] = []; let shuttingDown = false; + let detached = false; + const startedAt = Date.now(); const shutdown = async () => { if (shuttingDown) return; shuttingDown = true; @@ -2838,15 +2986,88 @@ async function runStart(args: ParsedArgs) { await Promise.all(children.map((handle) => stopChild(handle.child))); }; + const detachChildren = () => { + detached = true; + for (const handle of children) { + try { + handle.child.unref(); + } catch { + // ignore + } + handle.child.stdout?.removeAllListeners(); + handle.child.stderr?.removeAllListeners(); + handle.child.stdout?.destroy(); + handle.child.stderr?.destroy(); + } + }; + + const handleQuit = async () => { + tui?.stop(); + await shutdown(); + process.exit(0); + }; + + const handleDetach = async () => { + if (detached) return; + tui?.stop(); + detachChildren(); + const summary = [ + "Detached. Services still running:", + ...children.map((handle) => `- ${handle.name} (pid ${handle.child.pid ?? "unknown"})`), + `OpenWork URL: ${openworkConnectUrl}`, + `OpenWork Token: ${openworkToken}`, + `OpenCode URL: ${opencodeConnectUrl}`, + `Attach: ${attachCommand}`, + ].join("\n"); + process.stdout.write(`${summary}\n`); + process.exit(0); + }; + + if (useTui) { + tui = startOpenwrkTui({ + version: cliVersion, + connect: { + runId, + workspace: resolvedWorkspace, + openworkUrl: openworkConnectUrl, + openworkToken, + hostToken: openworkHostToken, + opencodeUrl: opencodeConnectUrl, + opencodePassword: opencodePassword ?? undefined, + opencodeUsername: opencodeUsername ?? undefined, + attachCommand, + }, + services: [ + { name: "opencode", label: "opencode", status: "starting", port: opencodePort }, + { name: "openwork-server", label: "openwork-server", status: "starting", port: openworkPort }, + { + name: "owpenbot", + label: "owpenbot", + status: owpenbotEnabled ? "starting" : "disabled", + port: owpenbotHealthPort, + }, + ], + onQuit: handleQuit, + onDetach: handleDetach, + onCopyAttach: async () => { + const result = await copyToClipboard(attachCommand); + return { command: attachCommand, ...result }; + }, + }); + tui.setUptimeStart(startedAt); + } + const handleExit = (name: string, code: number | null, signal: NodeJS.Signals | null) => { - if (shuttingDown) return; + if (shuttingDown || detached) return; const reason = code !== null ? `code ${code}` : signal ? `signal ${signal}` : "unknown"; + tui?.updateService(name, { status: "stopped", message: reason }); logger.error("Process exited", { reason, code, signal }, name); void shutdown().then(() => process.exit(code ?? 1)); }; const handleSpawnError = (name: string, error: unknown) => { - if (shuttingDown) return; + if (shuttingDown || detached) return; + tui?.updateService(name, { status: "error", message: String(error) }); logger.error("Process failed to start", { error: String(error) }, name); void shutdown().then(() => process.exit(1)); }; @@ -2866,6 +3087,11 @@ async function runStart(args: ParsedArgs) { logFormat, }); children.push({ name: "opencode", child: opencodeChild }); + tui?.updateService("opencode", { + status: "running", + pid: opencodeChild.pid ?? undefined, + port: opencodePort, + }); logger.info("Process spawned", { pid: opencodeChild.pid ?? 0 }, "opencode"); opencodeChild.on("exit", (code, signal) => handleExit("opencode", code, signal)); opencodeChild.on("error", (error) => handleSpawnError("opencode", error)); @@ -2883,6 +3109,7 @@ async function runStart(args: ParsedArgs) { logger.info("Waiting for health", { url: opencodeBaseUrl }, "opencode"); await waitForOpencodeHealthy(opencodeClient); logger.info("Healthy", { url: opencodeBaseUrl }, "opencode"); + tui?.updateService("opencode", { status: "healthy" }); const openworkChild = await startOpenworkServer({ bin: openworkServerBinary.bin, @@ -2905,6 +3132,11 @@ async function runStart(args: ParsedArgs) { logFormat, }); children.push({ name: "openwork-server", child: openworkChild }); + tui?.updateService("openwork-server", { + status: "running", + pid: openworkChild.pid ?? undefined, + port: openworkPort, + }); logger.info("Process spawned", { pid: openworkChild.pid ?? 0 }, "openwork-server"); openworkChild.on("exit", (code, signal) => handleExit("openwork-server", code, signal)); openworkChild.on("error", (error) => handleSpawnError("openwork-server", error)); @@ -2912,6 +3144,7 @@ async function runStart(args: ParsedArgs) { logger.info("Waiting for health", { url: openworkBaseUrl }, "openwork-server"); await waitForHealthy(openworkBaseUrl); logger.info("Healthy", { url: openworkBaseUrl }, "openwork-server"); + tui?.updateService("openwork-server", { status: "healthy" }); const openworkActualVersion = await verifyOpenworkServer({ baseUrl: openworkBaseUrl, @@ -2944,6 +3177,11 @@ async function runStart(args: ParsedArgs) { logFormat, }); children.push({ name: "owpenbot", child: owpenbotChild }); + tui?.updateService("owpenbot", { + status: "running", + pid: owpenbotChild.pid ?? undefined, + port: owpenbotHealthPort, + }); logger.info("Process spawned", { pid: owpenbotChild.pid ?? 0 }, "owpenbot"); owpenbotChild.on("exit", (code, signal) => { if (owpenbotRequired) { @@ -2951,6 +3189,7 @@ async function runStart(args: ParsedArgs) { return; } const reason = code !== null ? `code ${code}` : signal ? `signal ${signal}` : "unknown"; + tui?.updateService("owpenbot", { status: "stopped", message: reason }); logger.warn("Process exited, continuing without owpenbot", { reason, code, signal }, "owpenbot"); }); owpenbotChild.on("error", (error) => handleSpawnError("owpenbot", error)); @@ -3025,6 +3264,17 @@ async function runStart(args: ParsedArgs) { if (outputJson) { console.log(JSON.stringify(payload, null, 2)); + } else if (useTui) { + logger.info( + "Ready", + { + workspace: payload.workspace, + opencode: payload.opencode, + openwork: payload.openwork, + owpenbot: payload.owpenbot, + }, + "openwrk", + ); } else if (logFormat === "json") { logger.info( "Ready", @@ -3051,6 +3301,10 @@ async function runStart(args: ParsedArgs) { console.log(`Host token: ${payload.openwork.hostToken}`); } + if (detachRequested) { + await handleDetach(); + } + if (checkOnly) { try { await runChecks({ @@ -3066,9 +3320,11 @@ async function runStart(args: ParsedArgs) { } catch (error) { logger.error("Checks failed", { error: String(error) }, "openwrk"); await shutdown(); + tui?.stop(); process.exit(1); } await shutdown(); + tui?.stop(); process.exit(0); } @@ -3077,6 +3333,7 @@ async function runStart(args: ParsedArgs) { await new Promise(() => undefined); } catch (error) { await shutdown(); + tui?.stop(); logger.error("Run failed", { error: error instanceof Error ? error.message : String(error) }, "openwrk"); process.exit(1); } @@ -3098,6 +3355,11 @@ async function main() { await runStart(args); return; } + if (command === "serve") { + args.flags.set("tui", false); + await runStart(args); + return; + } if (command === "daemon") { await runDaemonCommand(args); return; diff --git a/packages/headless/src/tui/app.tsx b/packages/headless/src/tui/app.tsx new file mode 100644 index 00000000..f9820a45 --- /dev/null +++ b/packages/headless/src/tui/app.tsx @@ -0,0 +1,443 @@ +import { RGBA, TextAttributes, type KeyEvent } from "@opentui/core"; +import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"; +import { For, Show, createMemo, createSignal, onCleanup } from "solid-js"; +import { createStore } from "solid-js/store"; + +export type TuiLogLevel = "debug" | "info" | "warn" | "error"; + +export type TuiServiceStatus = "starting" | "running" | "healthy" | "stopped" | "disabled" | "error"; + +export type TuiService = { + name: string; + label: string; + status: TuiServiceStatus; + pid?: number; + port?: number; + message?: string; +}; + +export type TuiConnectInfo = { + runId: string; + workspace: string; + openworkUrl: string; + openworkToken: string; + hostToken: string; + opencodeUrl: string; + opencodePassword?: string; + opencodeUsername?: string; + attachCommand: string; +}; + +export type TuiLogEntry = { + time: number; + level: TuiLogLevel; + component: string; + message: string; +}; + +export type TuiHandle = { + updateService: (name: string, update: Partial) => void; + setConnectInfo: (info: Partial) => void; + pushLog: (entry: TuiLogEntry) => void; + setUptimeStart: (time: number) => void; + stop: () => void; +}; + +type TuiOptions = { + version: string; + connect: TuiConnectInfo; + services: TuiService[]; + onQuit: () => void | Promise; + onDetach: () => void | Promise; + onCopyAttach: () => Promise<{ command: string; copied: boolean; error?: string }>; +}; + +type ViewName = "overview" | "logs" | "help"; + +const MAX_LOGS = 800; + +const theme = { + text: RGBA.fromInts(235, 235, 235), + textMuted: RGBA.fromInts(150, 150, 160), + border: RGBA.fromInts(70, 70, 80), + accent: RGBA.fromInts(120, 180, 255), + success: RGBA.fromInts(90, 210, 140), + warning: RGBA.fromInts(240, 200, 90), + error: RGBA.fromInts(240, 120, 120), + panel: RGBA.fromInts(28, 28, 32), +} as const; + +const statusLabel: Record = { + starting: "Starting", + running: "Running", + healthy: "Healthy", + stopped: "Stopped", + disabled: "Disabled", + error: "Error", +}; + +const statusColor: Record = { + starting: theme.warning, + running: theme.accent, + healthy: theme.success, + stopped: theme.textMuted, + disabled: theme.textMuted, + error: theme.error, +}; + +const levelColor: Record = { + debug: theme.textMuted, + info: theme.text, + warn: theme.warning, + error: theme.error, +}; + +const levelCycle: Array<"all" | TuiLogLevel> = ["all", "info", "warn", "error", "debug"]; + +const serviceCycle = ["all", "openwrk", "opencode", "openwork-server", "owpenbot"]; + +function formatDuration(ms: number) { + if (ms < 1000) return `${ms}ms`; + if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; + const minutes = Math.floor(ms / 60000); + const seconds = Math.floor((ms % 60000) / 1000); + if (minutes < 60) return `${minutes}m ${seconds}s`; + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${hours}h ${mins}m`; +} + +function formatTime(ms: number) { + const date = new Date(ms); + return date.toLocaleTimeString(undefined, { timeStyle: "short" }); +} + +function clamp(value: number, min: number, max: number) { + return Math.max(min, Math.min(max, value)); +} + +export function startOpenwrkTui(options: TuiOptions): TuiHandle { + let stop: (() => void) | undefined; + const api: TuiHandle = { + updateService: () => undefined, + setConnectInfo: () => undefined, + pushLog: () => undefined, + setUptimeStart: () => undefined, + stop: () => stop?.(), + }; + + render( + () => { + const renderer = useRenderer(); + const dimensions = useTerminalDimensions(); + renderer.disableStdoutInterception(); + + const [state, setState] = createStore({ + view: "overview" as ViewName, + follow: true, + serviceFilter: "all" as (typeof serviceCycle)[number], + levelFilter: "all" as (typeof levelCycle)[number], + scrollOffset: 0, + logs: [] as TuiLogEntry[], + services: options.services as TuiService[], + connect: options.connect, + uptimeStart: Date.now(), + }); + + api.updateService = (name, update) => { + setState("services", (items) => + items.map((item) => (item.name === name ? { ...item, ...update } : item)), + ); + }; + + api.setConnectInfo = (info) => { + setState("connect", (prev) => ({ ...prev, ...info })); + }; + + api.pushLog = (entry) => { + setState("logs", (prev) => { + const next = [...prev, entry]; + return next.length > MAX_LOGS ? next.slice(next.length - MAX_LOGS) : next; + }); + if (state.follow) { + setState("scrollOffset", 0); + } + }; + + api.setUptimeStart = (time) => { + setState("uptimeStart", time); + }; + + stop = () => renderer.destroy(); + + const [now, setNow] = createSignal(Date.now()); + const interval = setInterval(() => setNow(Date.now()), 1000); + onCleanup(() => clearInterval(interval)); + + const [toast, setToast] = createSignal(null); + let toastTimer: NodeJS.Timeout | undefined; + const showToast = (message: string) => { + if (toastTimer) clearTimeout(toastTimer); + setToast(message); + toastTimer = setTimeout(() => setToast(null), 2500); + }; + onCleanup(() => { + if (toastTimer) clearTimeout(toastTimer); + }); + + const logHeight = createMemo(() => { + const height = dimensions().height; + return clamp(height - 9, 4, height); + }); + + const filteredLogs = createMemo(() => { + return state.logs.filter((entry) => { + if (state.serviceFilter !== "all" && entry.component !== state.serviceFilter) return false; + if (state.levelFilter !== "all" && entry.level !== state.levelFilter) return false; + return true; + }); + }); + + const visibleLogs = createMemo(() => { + const items = filteredLogs(); + const total = items.length; + const height = logHeight(); + const offset = clamp(state.scrollOffset, 0, Math.max(0, total - height)); + const start = Math.max(0, total - height - offset); + return items.slice(start, start + height); + }); + + const uptimeLabel = createMemo(() => formatDuration(now() - state.uptimeStart)); + + const healthLabel = createMemo(() => { + const active = state.services.filter((item) => item.status !== "disabled"); + if (!active.length) return "No services"; + const unhealthy = active.find((item) => item.status === "error" || item.status === "stopped"); + if (unhealthy) return "Degraded"; + const healthy = active.every((item) => item.status === "healthy" || item.status === "running"); + return healthy ? "All green" : "Starting"; + }); + + const actions = (view: ViewName) => { + if (view === "logs") { + return "[B] Back [F] Follow [S] Service [E] Level [D] Detach [Q] Quit"; + } + if (view === "help") { + return "[B] Back [D] Detach [Q] Quit"; + } + return "[L] Logs [C] Copy attach command [D] Detach [Q] Quit"; + }; + + useKeyboard((evt: KeyEvent) => { + if (evt.ctrl && evt.name === "c") { + evt.preventDefault(); + void options.onQuit(); + return; + } + if (evt.name === "q") { + evt.preventDefault(); + void options.onQuit(); + return; + } + if (evt.name === "d") { + evt.preventDefault(); + void options.onDetach(); + return; + } + if (evt.name === "l") { + evt.preventDefault(); + setState("view", "logs"); + return; + } + if (evt.name === "h" || evt.name === "?") { + evt.preventDefault(); + setState("view", "help"); + return; + } + if (evt.name === "b" || evt.name === "o") { + evt.preventDefault(); + setState("view", "overview"); + return; + } + if (evt.name === "c") { + evt.preventDefault(); + options + .onCopyAttach() + .then((result) => { + const label = result.copied + ? "Attach command copied" + : `Attach command ready${result.error ? `: ${result.error}` : ""}`; + showToast(label); + }) + .catch((error) => { + showToast(`Attach command error: ${String(error)}`); + }); + return; + } + if (state.view !== "logs") return; + if (evt.name === "f") { + evt.preventDefault(); + setState("follow", !state.follow); + if (!state.follow) { + setState("scrollOffset", 0); + } + return; + } + if (evt.name === "s") { + evt.preventDefault(); + const next = serviceCycle[(serviceCycle.indexOf(state.serviceFilter) + 1) % serviceCycle.length]; + setState("serviceFilter", next); + setState("scrollOffset", 0); + return; + } + if (evt.name === "e") { + evt.preventDefault(); + const next = levelCycle[(levelCycle.indexOf(state.levelFilter) + 1) % levelCycle.length]; + setState("levelFilter", next); + setState("scrollOffset", 0); + return; + } + if (evt.name === "up" || evt.name === "k") { + evt.preventDefault(); + setState("follow", false); + setState("scrollOffset", (value) => value + 1); + return; + } + if (evt.name === "down" || evt.name === "j") { + evt.preventDefault(); + setState("scrollOffset", (value) => Math.max(0, value - 1)); + return; + } + if (evt.name === "pageup") { + evt.preventDefault(); + setState("follow", false); + setState("scrollOffset", (value) => value + logHeight()); + return; + } + if (evt.name === "pagedown") { + evt.preventDefault(); + setState("scrollOffset", (value) => Math.max(0, value - logHeight())); + } + }); + + return ( + + + + openwrk · {state.view} + + v{options.version} + + run id: {state.connect.runId} + + + + + + Services + + + {(service) => ( + + + {service.label} + {statusLabel[service.status]} + + )} + + + Health: {healthLabel()} + Uptime: {uptimeLabel()} + + + + Ports + + item.port)}> + {(service) => ( + + {service.label}: {service.port} + + )} + + + + + + Connect + + OpenWork URL (LAN) + {state.connect.openworkUrl} + OpenWork Token + {state.connect.openworkToken} + Host Token + {state.connect.hostToken} + OpenCode URL + {state.connect.opencodeUrl} + + OpenCode Password + {state.connect.opencodePassword} + + Attach command + {state.connect.attachCommand} + + + + + + + + Filters: {state.serviceFilter} · level: {state.levelFilter} · follow: {state.follow ? "on" : "off"} + + + + {(entry) => ( + + {formatTime(entry.time)} + [{entry.component}] + {entry.message} + + )} + + + + + + + + + Shortcuts + + L: Logs + C: Copy attach command + D: Detach + Q: Quit + + + + + + {(val) => {val()}} + + + + + Actions: {actions(state.view)} + + + ); + }, + { + targetFps: 60, + gatherStats: false, + exitOnCtrlC: false, + useKittyKeyboard: {}, + autoFocus: false, + consoleOptions: { + keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], + }, + }, + ); + + return api; +} diff --git a/packages/headless/tsconfig.json b/packages/headless/tsconfig.json index d5509c4d..739e18ae 100644 --- a/packages/headless/tsconfig.json +++ b/packages/headless/tsconfig.json @@ -1,8 +1,11 @@ { "compilerOptions": { "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "jsxImportSource": "@opentui/solid", + "lib": ["ESNext", "DOM", "DOM.Iterable"], "outDir": "dist", "rootDir": "src", "strict": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdb98267..213b9d32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,12 +88,21 @@ importers: '@opencode-ai/sdk': specifier: ^1.1.31 version: 1.1.39 + '@opentui/core': + specifier: 0.1.77 + version: 0.1.77(stage-js@1.0.0-alpha.17)(typescript@5.9.3)(web-tree-sitter@0.25.10) + '@opentui/solid': + specifier: 0.1.77 + version: 0.1.77(solid-js@1.9.9)(stage-js@1.0.0-alpha.17)(typescript@5.9.3)(web-tree-sitter@0.25.10) openwork-server: specifier: 0.1.2 version: 0.1.2 owpenwork: specifier: 0.1.19 - version: 0.1.19(sharp@0.34.5) + version: 0.1.19(jimp@1.6.0)(sharp@0.34.5) + solid-js: + specifier: 1.9.9 + version: 1.9.9 devDependencies: '@types/node': specifier: ^22.10.2 @@ -146,7 +155,7 @@ importers: version: 1.1.39 '@whiskeysockets/baileys': specifier: 7.0.0-rc.9 - version: 7.0.0-rc.9(sharp@0.34.5) + version: 7.0.0-rc.9(jimp@1.6.0)(sharp@0.34.5) commander: specifier: ^12.1.0 version: 12.1.0 @@ -207,6 +216,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@babel/code-frame@7.28.6': resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} engines: {node: '>=6.9.0'} @@ -215,6 +228,10 @@ packages: resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.6': resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} engines: {node: '>=6.9.0'} @@ -223,14 +240,28 @@ packages: resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.18.6': resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} @@ -245,10 +276,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -276,6 +321,30 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.27.1': + resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -301,6 +370,9 @@ packages: '@cacheable/utils@2.3.3': resolution: {integrity: sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==} + '@dimforge/rapier2d-simd-compat@0.17.3': + resolution: {integrity: sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} @@ -770,6 +842,118 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} + '@jimp/core@1.6.0': + resolution: {integrity: sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==} + engines: {node: '>=18'} + + '@jimp/diff@1.6.0': + resolution: {integrity: sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==} + engines: {node: '>=18'} + + '@jimp/file-ops@1.6.0': + resolution: {integrity: sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==} + engines: {node: '>=18'} + + '@jimp/js-bmp@1.6.0': + resolution: {integrity: sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==} + engines: {node: '>=18'} + + '@jimp/js-gif@1.6.0': + resolution: {integrity: sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==} + engines: {node: '>=18'} + + '@jimp/js-jpeg@1.6.0': + resolution: {integrity: sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==} + engines: {node: '>=18'} + + '@jimp/js-png@1.6.0': + resolution: {integrity: sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==} + engines: {node: '>=18'} + + '@jimp/js-tiff@1.6.0': + resolution: {integrity: sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==} + engines: {node: '>=18'} + + '@jimp/plugin-blit@1.6.0': + resolution: {integrity: sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==} + engines: {node: '>=18'} + + '@jimp/plugin-blur@1.6.0': + resolution: {integrity: sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==} + engines: {node: '>=18'} + + '@jimp/plugin-circle@1.6.0': + resolution: {integrity: sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==} + engines: {node: '>=18'} + + '@jimp/plugin-color@1.6.0': + resolution: {integrity: sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==} + engines: {node: '>=18'} + + '@jimp/plugin-contain@1.6.0': + resolution: {integrity: sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==} + engines: {node: '>=18'} + + '@jimp/plugin-cover@1.6.0': + resolution: {integrity: sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==} + engines: {node: '>=18'} + + '@jimp/plugin-crop@1.6.0': + resolution: {integrity: sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==} + engines: {node: '>=18'} + + '@jimp/plugin-displace@1.6.0': + resolution: {integrity: sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==} + engines: {node: '>=18'} + + '@jimp/plugin-dither@1.6.0': + resolution: {integrity: sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==} + engines: {node: '>=18'} + + '@jimp/plugin-fisheye@1.6.0': + resolution: {integrity: sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==} + engines: {node: '>=18'} + + '@jimp/plugin-flip@1.6.0': + resolution: {integrity: sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==} + engines: {node: '>=18'} + + '@jimp/plugin-hash@1.6.0': + resolution: {integrity: sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==} + engines: {node: '>=18'} + + '@jimp/plugin-mask@1.6.0': + resolution: {integrity: sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==} + engines: {node: '>=18'} + + '@jimp/plugin-print@1.6.0': + resolution: {integrity: sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==} + engines: {node: '>=18'} + + '@jimp/plugin-quantize@1.6.0': + resolution: {integrity: sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==} + engines: {node: '>=18'} + + '@jimp/plugin-resize@1.6.0': + resolution: {integrity: sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==} + engines: {node: '>=18'} + + '@jimp/plugin-rotate@1.6.0': + resolution: {integrity: sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==} + engines: {node: '>=18'} + + '@jimp/plugin-threshold@1.6.0': + resolution: {integrity: sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==} + engines: {node: '>=18'} + + '@jimp/types@1.6.0': + resolution: {integrity: sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==} + engines: {node: '>=18'} + + '@jimp/utils@1.6.0': + resolution: {integrity: sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==} + engines: {node: '>=18'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -867,6 +1051,46 @@ packages: '@opencode-ai/sdk@1.1.39': resolution: {integrity: sha512-EUYBZAci0bzG9+a7JVINmqAqis71ipG2/D3juvmvvKFyu0YBIT/6b+g3+p82Eb5CU2dujxpPdJJCaexZ1389eQ==} + '@opentui/core-darwin-arm64@0.1.77': + resolution: {integrity: sha512-SNqmygCMEsPCW7xWjzCZ5caBf36xaprwVdAnFijGDOuIzLA4iaDa6um8cj3TJh7awenN3NTRsuRc7OuH42UH+g==} + cpu: [arm64] + os: [darwin] + + '@opentui/core-darwin-x64@0.1.77': + resolution: {integrity: sha512-/8fsa03swEHTQt/9NrGm98kemlU+VuTURI/OFZiH53vPDRrOYIYoa4Jyga/H7ZMcG+iFhkq97zIe+0Kw95LGmA==} + cpu: [x64] + os: [darwin] + + '@opentui/core-linux-arm64@0.1.77': + resolution: {integrity: sha512-QfUXZJPc69OvqoMu+AlLgjqXrwu4IeqcBuUWYMuH8nPTeLsVUc3CBbXdV2lv9UDxWzxzrxdS4ALPaxvmEv9lsQ==} + cpu: [arm64] + os: [linux] + + '@opentui/core-linux-x64@0.1.77': + resolution: {integrity: sha512-Kmfx0yUKnPj67AoXYIgL7qQo0QVsUG5Iw8aRtv6XFzXqa5SzBPhaKkKZ9yHPjOmTalZquUs+9zcCRNKpYYuL7A==} + cpu: [x64] + os: [linux] + + '@opentui/core-win32-arm64@0.1.77': + resolution: {integrity: sha512-HGTscPXc7gdd23Nh1DbzUNjog1I+5IZp95XPtLftGTpjrWs60VcetXcyJqK2rQcXNxewJK5yDyaa5QyMjfEhCQ==} + cpu: [arm64] + os: [win32] + + '@opentui/core-win32-x64@0.1.77': + resolution: {integrity: sha512-c7GijsbvVgnlzd2murIbwuwrGbcv76KdUw6WlVv7a0vex50z6xJCpv1keGzpe0QfxrZ/6fFEFX7JnwGLno0wjA==} + cpu: [x64] + os: [win32] + + '@opentui/core@0.1.77': + resolution: {integrity: sha512-lE3kabm6jdqK3AuBq+O0zZrXdxt6ulmibTc57sf+AsPny6cmwYHnWI4wD6hcreFiYoQVNVvdiJchVgPtowMlEg==} + peerDependencies: + web-tree-sitter: 0.25.10 + + '@opentui/solid@0.1.77': + resolution: {integrity: sha512-JY+hUbXVV+XCk6bC8dvcwawWCEmC3Gid6GDs23AJWBgHZ3TU2kRKrgwTdltm45DOq2cZXrYCt690/yE8bP+Gxg==} + peerDependencies: + solid-js: 1.9.9 + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -1271,6 +1495,9 @@ packages: '@types/node@10.17.60': resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + '@types/node@16.9.1': + resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} + '@types/node@20.12.12': resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} @@ -1289,6 +1516,9 @@ packages: '@types/react@18.2.79': resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==} + '@webgpu/types@0.1.69': + resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} + '@whiskeysockets/baileys@7.0.0-rc.9': resolution: {integrity: sha512-YFm5gKXfDP9byCXCW3OPHKXLzrAKzolzgVUlRosHHgwbnf2YOO3XknkMm6J7+F0ns8OA0uuSBhgkRHTDtqkacw==} engines: {node: '>=20.0.0'} @@ -1313,6 +1543,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + any-base@1.1.0: + resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1337,11 +1570,18 @@ packages: peerDependencies: postcss: ^8.1.0 + await-to-js@3.0.0: + resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} + engines: {node: '>=6.0.0'} + babel-plugin-jsx-dom-expressions@0.40.3: resolution: {integrity: sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==} peerDependencies: '@babel/core': ^7.20.12 + babel-plugin-module-resolver@5.0.2: + resolution: {integrity: sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==} + babel-preset-solid@1.9.10: resolution: {integrity: sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==} peerDependencies: @@ -1351,6 +1591,21 @@ packages: solid-js: optional: true + babel-preset-solid@1.9.9: + resolution: {integrity: sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==} + peerDependencies: + '@babel/core': ^7.0.0 + solid-js: ^1.9.8 + peerDependenciesMeta: + solid-js: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.9.14: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true @@ -1359,6 +1614,12 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bmp-ts@1.0.9: + resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1368,9 +1629,40 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bun-ffi-structs@0.1.2: + resolution: {integrity: sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w==} + peerDependencies: + typescript: ^5 + bun-types@1.3.6: resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} + bun-webgpu-darwin-arm64@0.1.4: + resolution: {integrity: sha512-eDgLN9teKTfmvrCqgwwmWNsNszxYs7IZdCqk0S1DCarvMhr4wcajoSBlA/nQA0/owwLduPTS8xxCnQp4/N/gDg==} + cpu: [arm64] + os: [darwin] + + bun-webgpu-darwin-x64@0.1.4: + resolution: {integrity: sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg==} + cpu: [x64] + os: [darwin] + + bun-webgpu-linux-x64@0.1.4: + resolution: {integrity: sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA==} + cpu: [x64] + os: [linux] + + bun-webgpu-win32-x64@0.1.4: + resolution: {integrity: sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA==} + cpu: [x64] + os: [win32] + + bun-webgpu@0.1.4: + resolution: {integrity: sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1434,6 +1726,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -1473,6 +1769,13 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + exif-parser@0.1.12: + resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1489,6 +1792,10 @@ packages: picomatch: optional: true + file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + file-type@21.3.0: resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} @@ -1497,9 +1804,19 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-babel-config@2.1.2: + resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1515,6 +1832,9 @@ packages: get-tsconfig@4.13.1: resolution: {integrity: sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==} + gifwrap@0.10.1: + resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1523,6 +1843,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -1547,6 +1872,9 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + image-q@4.0.0: + resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1571,6 +1899,10 @@ packages: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} + jimp@1.6.0: + resolution: {integrity: sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==} + engines: {node: '>=18'} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -1579,6 +1911,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1679,6 +2014,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} @@ -1689,6 +2028,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.5: resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} engines: {node: 20 || >=22} @@ -1725,10 +2067,27 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1791,6 +2150,9 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -1803,6 +2165,14 @@ packages: resolution: {integrity: sha512-s/K0M82zq+nyhqyv0T8mYM8fMkFAN9H7yLDut9bwaXUtX2EzvSC0wqVjJaRLhXDq9CzSJ339LVy6kXPBdqfi0A==} hasBin: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + p-queue@9.1.0: resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==} engines: {node: '>=20'} @@ -1811,12 +2181,40 @@ packages: resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} engines: {node: '>=20'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parse-bmfont-ascii@1.0.6: + resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} + + parse-bmfont-binary@1.0.6: + resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} + + parse-bmfont-xml@1.1.6: + resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1846,6 +2244,28 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + planck@1.4.2: + resolution: {integrity: sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew==} + engines: {node: '>=14.0'} + peerDependencies: + stage-js: ^1.0.0-alpha.12 + + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1898,6 +2318,10 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + protobufjs@6.8.8: resolution: {integrity: sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==} hasBin: true @@ -1932,6 +2356,14 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readable-web-to-node-stream@3.0.4: + resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} + engines: {node: '>=8'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1940,6 +2372,9 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1960,10 +2395,20 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + s-js@0.4.9: + resolution: {integrity: sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1990,9 +2435,16 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + simple-xml-to-json@1.2.3: + resolution: {integrity: sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==} + engines: {node: '>=20.12.2'} + solid-js@1.9.10: resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} + solid-js@1.9.9: + resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} + solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: @@ -2009,14 +2461,25 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + stage-js@1.0.0-alpha.17: + resolution: {integrity: sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw==} + engines: {node: '>=18.0'} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} + strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -2061,6 +2524,12 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + three@0.177.0: + resolution: {integrity: sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2069,6 +2538,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} + token-types@6.1.2: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} @@ -2113,6 +2586,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + utif2@4.1.0: + resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2174,6 +2650,14 @@ packages: vite: optional: true + web-tree-sitter@0.25.10: + resolution: {integrity: sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==} + peerDependencies: + '@types/emscripten': ^1.40.0 + peerDependenciesMeta: + '@types/emscripten': + optional: true + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -2195,6 +2679,17 @@ packages: utf-8-validate: optional: true + xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2203,10 +2698,21 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + snapshots: '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@babel/code-frame@7.28.6': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -2215,6 +2721,26 @@ snapshots: '@babel/compat-data@7.28.6': {} + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -2243,6 +2769,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.6 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.28.6 @@ -2251,8 +2781,28 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.6 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.18.6': dependencies: '@babel/types': 7.28.6 @@ -2264,6 +2814,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 @@ -2273,8 +2832,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.6 + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -2290,11 +2869,51 @@ snapshots: dependencies: '@babel/types': 7.28.6 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -2338,6 +2957,9 @@ snapshots: hashery: 1.4.0 keyv: 5.6.0 + '@dimforge/rapier2d-simd-compat@0.17.3': + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -2609,6 +3231,195 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 + '@jimp/core@1.6.0': + dependencies: + '@jimp/file-ops': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + await-to-js: 3.0.0 + exif-parser: 0.1.12 + file-type: 16.5.4 + mime: 3.0.0 + + '@jimp/diff@1.6.0': + dependencies: + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + pixelmatch: 5.3.0 + + '@jimp/file-ops@1.6.0': {} + + '@jimp/js-bmp@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + bmp-ts: 1.0.9 + + '@jimp/js-gif@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + gifwrap: 0.10.1 + omggif: 1.0.10 + + '@jimp/js-jpeg@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + jpeg-js: 0.4.4 + + '@jimp/js-png@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + pngjs: 7.0.0 + + '@jimp/js-tiff@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + utif2: 4.1.0 + + '@jimp/plugin-blit@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-blur@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/utils': 1.6.0 + + '@jimp/plugin-circle@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-color@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + tinycolor2: 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-contain@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-cover@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-crop@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-displace@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-dither@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + + '@jimp/plugin-fisheye@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-flip@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-hash@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/js-bmp': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/js-tiff': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + any-base: 1.1.0 + + '@jimp/plugin-mask@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-print@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/types': 1.6.0 + parse-bmfont-ascii: 1.0.6 + parse-bmfont-binary: 1.0.6 + parse-bmfont-xml: 1.1.6 + simple-xml-to-json: 1.2.3 + zod: 3.25.76 + + '@jimp/plugin-quantize@1.6.0': + dependencies: + image-q: 4.0.0 + zod: 3.25.76 + + '@jimp/plugin-resize@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-rotate@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-threshold@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-hash': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/types@1.6.0': + dependencies: + zod: 3.25.76 + + '@jimp/utils@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + tinycolor2: 1.6.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2679,6 +3490,62 @@ snapshots: '@opencode-ai/sdk@1.1.39': {} + '@opentui/core-darwin-arm64@0.1.77': + optional: true + + '@opentui/core-darwin-x64@0.1.77': + optional: true + + '@opentui/core-linux-arm64@0.1.77': + optional: true + + '@opentui/core-linux-x64@0.1.77': + optional: true + + '@opentui/core-win32-arm64@0.1.77': + optional: true + + '@opentui/core-win32-x64@0.1.77': + optional: true + + '@opentui/core@0.1.77(stage-js@1.0.0-alpha.17)(typescript@5.9.3)(web-tree-sitter@0.25.10)': + dependencies: + bun-ffi-structs: 0.1.2(typescript@5.9.3) + diff: 8.0.2 + jimp: 1.6.0 + marked: 17.0.1 + web-tree-sitter: 0.25.10 + yoga-layout: 3.2.1 + optionalDependencies: + '@dimforge/rapier2d-simd-compat': 0.17.3 + '@opentui/core-darwin-arm64': 0.1.77 + '@opentui/core-darwin-x64': 0.1.77 + '@opentui/core-linux-arm64': 0.1.77 + '@opentui/core-linux-x64': 0.1.77 + '@opentui/core-win32-arm64': 0.1.77 + '@opentui/core-win32-x64': 0.1.77 + bun-webgpu: 0.1.4 + planck: 1.4.2(stage-js@1.0.0-alpha.17) + three: 0.177.0 + transitivePeerDependencies: + - stage-js + - typescript + + '@opentui/solid@0.1.77(solid-js@1.9.9)(stage-js@1.0.0-alpha.17)(typescript@5.9.3)(web-tree-sitter@0.25.10)': + dependencies: + '@babel/core': 7.28.0 + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) + '@opentui/core': 0.1.77(stage-js@1.0.0-alpha.17)(typescript@5.9.3)(web-tree-sitter@0.25.10) + babel-plugin-module-resolver: 5.0.2 + babel-preset-solid: 1.9.9(@babel/core@7.28.0)(solid-js@1.9.9) + s-js: 0.4.9 + solid-js: 1.9.9 + transitivePeerDependencies: + - stage-js + - supports-color + - typescript + - web-tree-sitter + '@pinojs/redact@0.4.0': {} '@protobufjs/aspromise@1.1.2': {} @@ -2981,6 +3848,8 @@ snapshots: '@types/node@10.17.60': {} + '@types/node@16.9.1': {} + '@types/node@20.12.12': dependencies: undici-types: 5.26.5 @@ -3002,7 +3871,10 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@whiskeysockets/baileys@7.0.0-rc.9(sharp@0.34.5)': + '@webgpu/types@0.1.69': + optional: true + + '@whiskeysockets/baileys@7.0.0-rc.9(jimp@1.6.0)(sharp@0.34.5)': dependencies: '@cacheable/node-cache': 1.7.6 '@hapi/boom': 9.1.4 @@ -3015,6 +3887,8 @@ snapshots: protobufjs: 7.5.4 sharp: 0.34.5 ws: 8.19.0 + optionalDependencies: + jimp: 1.6.0 transitivePeerDependencies: - bufferutil - supports-color @@ -3029,6 +3903,8 @@ snapshots: dependencies: event-target-shim: 5.0.1 + any-base@1.1.0: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -3054,6 +3930,17 @@ snapshots: postcss: 8.4.38 postcss-value-parser: 4.2.0 + await-to-js@3.0.0: {} + + babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.0): + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.0) + '@babel/types': 7.28.6 + html-entities: 2.3.3 + parse5: 7.3.0 + babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.6): dependencies: '@babel/core': 7.28.6 @@ -3063,6 +3950,14 @@ snapshots: html-entities: 2.3.3 parse5: 7.3.0 + babel-plugin-module-resolver@5.0.2: + dependencies: + find-babel-config: 2.1.2 + glob: 9.3.5 + pkg-up: 3.1.0 + reselect: 4.1.8 + resolve: 1.22.11 + babel-preset-solid@1.9.10(@babel/core@7.28.6)(solid-js@1.9.10): dependencies: '@babel/core': 7.28.6 @@ -3070,10 +3965,27 @@ snapshots: optionalDependencies: solid-js: 1.9.10 + babel-preset-solid@1.9.9(@babel/core@7.28.0)(solid-js@1.9.9): + dependencies: + '@babel/core': 7.28.0 + babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.28.0) + optionalDependencies: + solid-js: 1.9.9 + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + baseline-browser-mapping@2.9.14: {} binary-extensions@2.3.0: {} + bmp-ts@1.0.9: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3086,10 +3998,41 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bun-ffi-structs@0.1.2(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + bun-types@1.3.6: dependencies: '@types/node': 22.19.7 + bun-webgpu-darwin-arm64@0.1.4: + optional: true + + bun-webgpu-darwin-x64@0.1.4: + optional: true + + bun-webgpu-linux-x64@0.1.4: + optional: true + + bun-webgpu-win32-x64@0.1.4: + optional: true + + bun-webgpu@0.1.4: + dependencies: + '@webgpu/types': 0.1.69 + optionalDependencies: + bun-webgpu-darwin-arm64: 0.1.4 + bun-webgpu-darwin-x64: 0.1.4 + bun-webgpu-linux-x64: 0.1.4 + bun-webgpu-win32-x64: 0.1.4 + optional: true + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -3142,6 +4085,8 @@ snapshots: didyoumean@1.2.2: {} + diff@8.0.2: {} + dlv@1.1.3: {} dotenv@16.6.1: {} @@ -3220,6 +4165,10 @@ snapshots: eventemitter3@5.0.4: {} + events@3.3.0: {} + + exif-parser@0.1.12: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3236,6 +4185,12 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + file-type@16.5.4: + dependencies: + readable-web-to-node-stream: 3.0.4 + strtok3: 6.3.0 + token-types: 4.2.1 + file-type@21.3.0: dependencies: '@tokenizer/inflate': 0.4.1 @@ -3249,8 +4204,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-babel-config@2.1.2: + dependencies: + json5: 2.2.3 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + fraction.js@4.3.7: {} + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true @@ -3263,6 +4228,11 @@ snapshots: resolve-pkg-maps: 1.0.0 optional: true + gifwrap@0.10.1: + dependencies: + image-q: 4.0.0 + omggif: 1.0.10 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3271,6 +4241,13 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@9.3.5: + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.11.1 + graceful-fs@4.2.11: {} grammy@1.39.3: @@ -3297,6 +4274,10 @@ snapshots: ieee754@1.2.1: {} + image-q@4.0.0: + dependencies: + '@types/node': 16.9.1 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3315,10 +4296,42 @@ snapshots: is-what@4.1.16: {} + jimp@1.6.0: + dependencies: + '@jimp/core': 1.6.0 + '@jimp/diff': 1.6.0 + '@jimp/js-bmp': 1.6.0 + '@jimp/js-gif': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/js-tiff': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/plugin-blur': 1.6.0 + '@jimp/plugin-circle': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-contain': 1.6.0 + '@jimp/plugin-cover': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-displace': 1.6.0 + '@jimp/plugin-dither': 1.6.0 + '@jimp/plugin-fisheye': 1.6.0 + '@jimp/plugin-flip': 1.6.0 + '@jimp/plugin-hash': 1.6.0 + '@jimp/plugin-mask': 1.6.0 + '@jimp/plugin-print': 1.6.0 + '@jimp/plugin-quantize': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/plugin-rotate': 1.6.0 + '@jimp/plugin-threshold': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + jiti@1.21.7: {} jiti@2.6.1: {} + jpeg-js@0.4.4: {} + js-tokens@4.0.0: {} jsesc@3.1.0: {} @@ -3386,6 +4399,11 @@ snapshots: lines-and-columns@1.2.4: {} + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + long@4.0.0: {} long@5.3.2: {} @@ -3394,6 +4412,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@10.4.3: {} + lru-cache@11.2.5: {} lru-cache@5.1.1: @@ -3423,10 +4443,20 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime@3.0.0: {} + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@8.0.4: + dependencies: + brace-expansion: 2.0.2 + + minipass@4.2.8: {} + + minipass@7.1.2: {} + ms@2.1.3: {} music-metadata@11.11.1: @@ -3491,6 +4521,8 @@ snapshots: object-hash@3.0.0: {} + omggif@1.0.10: {} + on-exit-leak-free@2.1.2: {} openwork-server@0.1.2: @@ -3499,10 +4531,10 @@ snapshots: minimatch: 10.1.1 yaml: 2.8.2 - owpenwork@0.1.19(sharp@0.34.5): + owpenwork@0.1.19(jimp@1.6.0)(sharp@0.34.5): dependencies: '@opencode-ai/sdk': 1.1.39 - '@whiskeysockets/baileys': 7.0.0-rc.9(sharp@0.34.5) + '@whiskeysockets/baileys': 7.0.0-rc.9(jimp@1.6.0)(sharp@0.34.5) commander: 12.1.0 dotenv: 16.6.1 grammy: 1.39.3 @@ -3518,6 +4550,14 @@ snapshots: - supports-color - utf-8-validate + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + p-queue@9.1.0: dependencies: eventemitter3: 5.0.4 @@ -3525,12 +4565,34 @@ snapshots: p-timeout@7.0.1: {} + p-try@2.2.0: {} + + pako@1.0.11: {} + + parse-bmfont-ascii@1.0.6: {} + + parse-bmfont-binary@1.0.6: {} + + parse-bmfont-xml@1.1.6: + dependencies: + xml-parse-from-string: 1.0.1 + xml2js: 0.5.0 + parse5@7.3.0: dependencies: entities: 6.0.1 + path-exists@3.0.0: {} + path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + peek-readable@4.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3561,6 +4623,23 @@ snapshots: pirates@4.0.7: {} + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + planck@1.4.2(stage-js@1.0.0-alpha.17): + dependencies: + stage-js: 1.0.0-alpha.17 + optional: true + + pngjs@6.0.0: {} + + pngjs@7.0.0: {} + postcss-import@15.1.0(postcss@8.4.38): dependencies: postcss: 8.4.38 @@ -3612,6 +4691,8 @@ snapshots: process-warning@5.0.0: {} + process@0.11.10: {} + protobufjs@6.8.8: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -3667,12 +4748,26 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readable-web-to-node-stream@3.0.4: + dependencies: + readable-stream: 4.7.0 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 real-require@0.2.0: {} + reselect@4.1.8: {} + resolve-pkg-maps@1.0.0: optional: true @@ -3719,8 +4814,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 + s-js@0.4.9: {} + + safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + sax@1.4.4: {} + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -3766,12 +4867,20 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + simple-xml-to-json@1.2.3: {} + solid-js@1.9.10: dependencies: csstype: 3.2.3 seroval: 1.3.2 seroval-plugins: 1.3.3(seroval@1.3.2) + solid-js@1.9.9: + dependencies: + csstype: 3.2.3 + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) + solid-refresh@0.6.3(solid-js@1.9.10): dependencies: '@babel/generator': 7.28.6 @@ -3789,12 +4898,24 @@ snapshots: split2@4.2.0: {} + stage-js@1.0.0-alpha.17: + optional: true + streamsearch@1.1.0: {} + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 + strtok3@6.3.0: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + styled-jsx@5.1.1(react@18.2.0): dependencies: client-only: 0.0.1 @@ -3855,6 +4976,11 @@ snapshots: dependencies: real-require: 0.2.0 + three@0.177.0: + optional: true + + tinycolor2@1.6.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -3864,6 +4990,11 @@ snapshots: dependencies: is-number: 7.0.0 + token-types@4.2.1: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + token-types@6.1.2: dependencies: '@borewit/text-codec': 0.2.1 @@ -3900,6 +5031,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + utif2@4.1.0: + dependencies: + pako: 1.0.11 + util-deprecate@1.0.2: {} vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@6.4.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)): @@ -3935,6 +5070,8 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + web-tree-sitter@0.25.10: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -3946,6 +5083,19 @@ snapshots: ws@8.19.0: {} + xml-parse-from-string@1.0.1: {} + + xml2js@0.5.0: + dependencies: + sax: 1.4.4 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + yallist@3.1.1: {} yaml@2.8.2: {} + + yoga-layout@3.2.1: {} + + zod@3.25.76: {} From 356ef041e1b938fb02efe4110484479fbd566c50 Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Wed, 4 Feb 2026 17:58:45 -0800 Subject: [PATCH 2/4] fix(headless): preload opentui solid for bun --- packages/headless/bunfig.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/headless/bunfig.toml diff --git a/packages/headless/bunfig.toml b/packages/headless/bunfig.toml new file mode 100644 index 00000000..7693482f --- /dev/null +++ b/packages/headless/bunfig.toml @@ -0,0 +1 @@ +preload = ["@opentui/solid/preload"] From fd1b962c14684829f5b560c329cdc674b3337a7c Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Wed, 4 Feb 2026 20:55:14 -0800 Subject: [PATCH 3/4] fix(headless): relax owpenbot version checks --- packages/headless/src/cli.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/headless/src/cli.ts b/packages/headless/src/cli.ts index 075ca989..7d13c151 100644 --- a/packages/headless/src/cli.ts +++ b/packages/headless/src/cli.ts @@ -955,7 +955,7 @@ async function readCliVersion(bin: string, timeoutMs = 4000): Promise "exit"), + once(child, "close").then(() => "close"), once(child, "error").then(() => "error"), new Promise((resolve) => setTimeout(resolve, timeoutMs, "timeout")), ]); @@ -1697,6 +1697,9 @@ async function owpenbotSupportsOpencodeUrl(bin: string): Promise { } async function verifyOwpenbotVersion(binary: ResolvedBinary): Promise { + if (binary.source !== "external") { + return binary.expectedVersion; + } const actual = await readCliVersion(binary.bin); assertVersionMatch("owpenbot", binary.expectedVersion, actual, binary.bin); return actual; From 617754d59d4e806882bc19390ff8607190fbbc95 Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Wed, 4 Feb 2026 21:12:39 -0800 Subject: [PATCH 4/4] feat(headless): add owpenbot health + controls --- packages/headless/src/cli.ts | 147 +++++++++++++++++++++++ packages/headless/src/tui/app.tsx | 190 +++++++++++++++++++++++++++++- 2 files changed, 334 insertions(+), 3 deletions(-) diff --git a/packages/headless/src/cli.ts b/packages/headless/src/cli.ts index 7d13c151..6683cc09 100644 --- a/packages/headless/src/cli.ts +++ b/packages/headless/src/cli.ts @@ -49,6 +49,22 @@ type LogEvent = { attributes?: LogAttributes; }; +type OwpenbotHealthSnapshot = { + ok: boolean; + opencode: { + url: string; + healthy: boolean; + version?: string; + }; + channels: { + telegram: boolean; + whatsapp: boolean; + }; + config: { + groupsEnabled: boolean; + }; +}; + const FALLBACK_VERSION = "0.1.0"; const DEFAULT_OPENWORK_PORT = 8787; const DEFAULT_OWPENBOT_HEALTH_PORT = 3005; @@ -976,6 +992,47 @@ async function readCliVersion(bin: string, timeoutMs = 4000): Promise { + const resolved = resolveBinCommand(bin); + const child = spawn(resolved.command, [...resolved.prefixArgs, ...args], { + stdio: ["ignore", "pipe", "pipe"], + env: options?.env ?? process.env, + }); + let output = ""; + child.stdout?.on("data", (chunk) => { + output += chunk.toString(); + }); + child.stderr?.on("data", (chunk) => { + output += chunk.toString(); + }); + + const timeoutMs = options?.timeoutMs ?? 30_000; + const result = await Promise.race([ + once(child, "close").then(() => "close"), + once(child, "error").then(() => "error"), + new Promise((resolve) => setTimeout(resolve, timeoutMs, "timeout")), + ]); + + if (result === "timeout") { + try { + child.kill("SIGKILL"); + } catch { + // ignore + } + throw new Error("Command timed out"); + } + + if (result === "error") { + throw new Error("Command failed to run"); + } + + return output.trim(); +} + function assertVersionMatch( name: string, expected: string | undefined, @@ -1364,6 +1421,28 @@ async function waitForHealthy(url: string, timeoutMs = 10_000, pollMs = 250): Pr throw new Error(lastError ?? "Timed out waiting for health check"); } +async function fetchOwpenbotHealth(baseUrl: string): Promise { + return (await fetchJson(`${baseUrl.replace(/\/$/, "")}/health`)) as OwpenbotHealthSnapshot; +} + +async function waitForOwpenbotHealthy(baseUrl: string, timeoutMs = 10_000, pollMs = 500) { + const start = Date.now(); + let lastError: string | null = null; + while (Date.now() - start < timeoutMs) { + try { + const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`); + if (response.ok) { + return (await response.json()) as OwpenbotHealthSnapshot; + } + lastError = `HTTP ${response.status}`; + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + } + await new Promise((resolve) => setTimeout(resolve, pollMs)); + } + throw new Error(lastError ?? "Timed out waiting for owpenbot health"); +} + async function waitForOpencodeHealthy(client: ReturnType, timeoutMs = 10_000, pollMs = 250) { const start = Date.now(); let lastError: string | null = null; @@ -2978,13 +3057,28 @@ async function runStart(args: ParsedArgs) { password: opencodePassword, }); + const owpenbotHealthUrl = `http://127.0.0.1:${owpenbotHealthPort}`; + const owpenbotEnv: NodeJS.ProcessEnv = { + ...process.env, + OPENCODE_DIRECTORY: resolvedWorkspace, + OPENCODE_URL: opencodeConnectUrl, + ...(opencodeUsername ? { OPENCODE_SERVER_USERNAME: opencodeUsername } : {}), + ...(opencodePassword ? { OPENCODE_SERVER_PASSWORD: opencodePassword } : {}), + ...(owpenbotHealthPort ? { OWPENBOT_HEALTH_PORT: String(owpenbotHealthPort) } : {}), + }; + const children: ChildHandle[] = []; let shuttingDown = false; let detached = false; const startedAt = Date.now(); + let owpenbotHealthInterval: NodeJS.Timeout | null = null; const shutdown = async () => { if (shuttingDown) return; shuttingDown = true; + if (owpenbotHealthInterval) { + clearInterval(owpenbotHealthInterval); + owpenbotHealthInterval = null; + } logger.info("Shutting down", { children: children.map((handle) => handle.name) }, "openwrk"); await Promise.all(children.map((handle) => stopChild(handle.child))); }; @@ -3012,6 +3106,10 @@ async function runStart(args: ParsedArgs) { const handleDetach = async () => { if (detached) return; + if (owpenbotHealthInterval) { + clearInterval(owpenbotHealthInterval); + owpenbotHealthInterval = null; + } tui?.stop(); detachChildren(); const summary = [ @@ -3056,6 +3154,33 @@ async function runStart(args: ParsedArgs) { const result = await copyToClipboard(attachCommand); return { command: attachCommand, ...result }; }, + onOwpenbotHealth: async () => fetchOwpenbotHealth(owpenbotHealthUrl), + onOwpenbotQr: async () => { + if (!owpenbotBinary) { + throw new Error("Owpenbot binary missing"); + } + const output = await captureCommandOutput( + owpenbotBinary.bin, + ["whatsapp", "qr", "--format", "ascii"], + { env: owpenbotEnv }, + ); + if (!output) { + throw new Error("No QR output received"); + } + return output; + }, + onOwpenbotSetTelegramToken: async (token) => { + try { + await fetchJson(`${owpenbotHealthUrl}/config/telegram-token`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token }), + }); + return { ok: true }; + } catch (error) { + return { ok: false, error: error instanceof Error ? error.message : String(error) }; + } + }, }); tui.setUptimeStart(startedAt); } @@ -3186,6 +3311,28 @@ async function runStart(args: ParsedArgs) { port: owpenbotHealthPort, }); logger.info("Process spawned", { pid: owpenbotChild.pid ?? 0 }, "owpenbot"); + try { + logger.info("Waiting for health", { url: owpenbotHealthUrl }, "owpenbot"); + const health = await waitForOwpenbotHealthy(owpenbotHealthUrl); + tui?.setOwpenbotHealth(health); + tui?.updateService("owpenbot", { status: health.ok ? "healthy" : "running" }); + logger.info("Healthy", { url: owpenbotHealthUrl, ok: health.ok }, "owpenbot"); + } catch (error) { + logger.warn("Owpenbot health check failed", { error: String(error) }, "owpenbot"); + tui?.updateService("owpenbot", { status: "running", message: String(error) }); + } + if (!owpenbotHealthInterval) { + owpenbotHealthInterval = setInterval(() => { + fetchOwpenbotHealth(owpenbotHealthUrl) + .then((health) => { + tui?.setOwpenbotHealth(health); + if (health.ok) { + tui?.updateService("owpenbot", { status: "healthy" }); + } + }) + .catch(() => undefined); + }, 15_000); + } owpenbotChild.on("exit", (code, signal) => { if (owpenbotRequired) { handleExit("owpenbot", code, signal); diff --git a/packages/headless/src/tui/app.tsx b/packages/headless/src/tui/app.tsx index f9820a45..d99a737e 100644 --- a/packages/headless/src/tui/app.tsx +++ b/packages/headless/src/tui/app.tsx @@ -1,4 +1,4 @@ -import { RGBA, TextAttributes, type KeyEvent } from "@opentui/core"; +import { RGBA, TextAttributes, type InputRenderable, type KeyEvent } from "@opentui/core"; import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"; import { For, Show, createMemo, createSignal, onCleanup } from "solid-js"; import { createStore } from "solid-js/store"; @@ -28,6 +28,22 @@ export type TuiConnectInfo = { attachCommand: string; }; +export type TuiOwpenbotHealth = { + ok: boolean; + opencode: { + url: string; + healthy: boolean; + version?: string; + }; + channels: { + telegram: boolean; + whatsapp: boolean; + }; + config: { + groupsEnabled: boolean; + }; +}; + export type TuiLogEntry = { time: number; level: TuiLogLevel; @@ -38,6 +54,7 @@ export type TuiLogEntry = { export type TuiHandle = { updateService: (name: string, update: Partial) => void; setConnectInfo: (info: Partial) => void; + setOwpenbotHealth: (health: TuiOwpenbotHealth | null) => void; pushLog: (entry: TuiLogEntry) => void; setUptimeStart: (time: number) => void; stop: () => void; @@ -50,9 +67,12 @@ type TuiOptions = { onQuit: () => void | Promise; onDetach: () => void | Promise; onCopyAttach: () => Promise<{ command: string; copied: boolean; error?: string }>; + onOwpenbotHealth: () => Promise; + onOwpenbotQr: () => Promise; + onOwpenbotSetTelegramToken: (token: string) => Promise<{ ok: boolean; error?: string }>; }; -type ViewName = "overview" | "logs" | "help"; +type ViewName = "overview" | "logs" | "help" | "owpenbot"; const MAX_LOGS = 800; @@ -121,6 +141,7 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { const api: TuiHandle = { updateService: () => undefined, setConnectInfo: () => undefined, + setOwpenbotHealth: () => undefined, pushLog: () => undefined, setUptimeStart: () => undefined, stop: () => stop?.(), @@ -141,6 +162,11 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { logs: [] as TuiLogEntry[], services: options.services as TuiService[], connect: options.connect, + owpenbotHealth: null as TuiOwpenbotHealth | null, + owpenbotQr: "", + owpenbotQrError: "", + owpenbotToken: "", + owpenbotEditing: false, uptimeStart: Date.now(), }); @@ -154,6 +180,10 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { setState("connect", (prev) => ({ ...prev, ...info })); }; + api.setOwpenbotHealth = (health) => { + setState("owpenbotHealth", health); + }; + api.pushLog = (entry) => { setState("logs", (prev) => { const next = [...prev, entry]; @@ -185,6 +215,8 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { if (toastTimer) clearTimeout(toastTimer); }); + let tokenInput: InputRenderable | undefined; + const logHeight = createMemo(() => { const height = dimensions().height; return clamp(height - 9, 4, height); @@ -218,6 +250,11 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { return healthy ? "All green" : "Starting"; }); + const owpenbotStatus = createMemo(() => { + if (!state.owpenbotHealth) return "Pending"; + return state.owpenbotHealth.ok ? "Healthy" : "Needs attention"; + }); + const actions = (view: ViewName) => { if (view === "logs") { return "[B] Back [F] Follow [S] Service [E] Level [D] Detach [Q] Quit"; @@ -225,7 +262,10 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { if (view === "help") { return "[B] Back [D] Detach [Q] Quit"; } - return "[L] Logs [C] Copy attach command [D] Detach [Q] Quit"; + if (view === "owpenbot") { + return "[B] Back [G] QR [T] Telegram token [R] Refresh [D] Detach [Q] Quit"; + } + return "[L] Logs [W] Owpenbot [C] Copy attach command [D] Detach [Q] Quit"; }; useKeyboard((evt: KeyEvent) => { @@ -244,11 +284,24 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { void options.onDetach(); return; } + if (state.owpenbotEditing) { + if (evt.name === "escape") { + evt.preventDefault(); + setState("owpenbotEditing", false); + tokenInput?.blur(); + } + return; + } if (evt.name === "l") { evt.preventDefault(); setState("view", "logs"); return; } + if (evt.name === "w") { + evt.preventDefault(); + setState("view", "owpenbot"); + return; + } if (evt.name === "h" || evt.name === "?") { evt.preventDefault(); setState("view", "help"); @@ -257,6 +310,7 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { if (evt.name === "b" || evt.name === "o") { evt.preventDefault(); setState("view", "overview"); + setState("owpenbotEditing", false); return; } if (evt.name === "c") { @@ -274,6 +328,48 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { }); return; } + if (state.view === "owpenbot") { + if (evt.name === "b" || evt.name === "o") { + evt.preventDefault(); + setState("view", "overview"); + return; + } + if (evt.name === "g") { + evt.preventDefault(); + setState("owpenbotQr", ""); + setState("owpenbotQrError", ""); + options + .onOwpenbotQr() + .then((qr) => { + setState("owpenbotQr", qr.trim()); + showToast("WhatsApp QR loaded"); + }) + .catch((error) => { + setState("owpenbotQrError", String(error)); + showToast("WhatsApp QR failed"); + }); + return; + } + if (evt.name === "r") { + evt.preventDefault(); + options + .onOwpenbotHealth() + .then((health) => { + api.setOwpenbotHealth(health); + showToast("Owpenbot health refreshed"); + }) + .catch((error) => { + showToast(`Owpenbot health error: ${String(error)}`); + }); + return; + } + if (evt.name === "t") { + evt.preventDefault(); + setState("owpenbotEditing", true); + setTimeout(() => tokenInput?.focus(), 1); + return; + } + } if (state.view !== "logs") return; if (evt.name === "f") { evt.preventDefault(); @@ -403,13 +499,101 @@ export function startOpenwrkTui(options: TuiOptions): TuiHandle { + + + + Owpenbot + + Health: {owpenbotStatus()} + + + OpenCode: {state.owpenbotHealth?.opencode.healthy ? "healthy" : "down"} + + + WhatsApp: {state.owpenbotHealth?.channels.whatsapp ? "connected" : "not connected"} + + + Telegram: {state.owpenbotHealth?.channels.telegram ? "enabled" : "not configured"} + + + Groups enabled: {state.owpenbotHealth?.config.groupsEnabled ? "yes" : "no"} + + + + Press [R] to refresh owpenbot health. + + + + + WhatsApp QR + + + + + + {(line) => {line}} + + + + + Press [G] to fetch a QR code. + + + {state.owpenbotQrError} + + + + + Telegram token + + + setState("owpenbotToken", value)} + onSubmit={(value) => { + const token = typeof value === "string" ? value.trim() : ""; + if (!token) { + showToast("Telegram token required"); + return; + } + options + .onOwpenbotSetTelegramToken(token) + .then((result) => { + if (!result.ok) { + showToast(result.error ? `Telegram error: ${result.error}` : "Telegram token failed"); + return; + } + setState("owpenbotToken", ""); + setState("owpenbotEditing", false); + tokenInput?.blur(); + showToast("Telegram token saved"); + }) + .catch((error) => { + showToast(`Telegram error: ${String(error)}`); + }); + }} + ref={(node) => { + tokenInput = node; + if (state.owpenbotEditing) { + setTimeout(() => tokenInput?.focus(), 1); + } + }} + /> + + + Shortcuts L: Logs + W: Owpenbot C: Copy attach command + G: WhatsApp QR (owpenbot) + T: Telegram token (owpenbot) D: Detach Q: Quit