From 6ac882d746ebd6aacebebd2e2e7e9f6f03271497 Mon Sep 17 00:00:00 2001 From: Tarquinen Date: Sun, 8 Mar 2026 19:38:11 -0400 Subject: [PATCH 01/40] feat(tui): add dcp sidebar widget --- tui/commands.ts | 19 +++ tui/components/metric-row.tsx | 23 +++ tui/components/screen.tsx | 35 ++++ tui/components/section.tsx | 29 ++++ tui/data/context.ts | 289 +++++++++++++++++++++++++++++++++ tui/dcp-probe.tsx | 1 + tui/index.tsx | 30 ++++ tui/package.json | 12 ++ tui/routes/panel.tsx | 82 ++++++++++ tui/shared/config.ts | 14 ++ tui/shared/names.ts | 13 ++ tui/shared/navigation.ts | 47 ++++++ tui/shared/theme.ts | 48 ++++++ tui/shared/types.ts | 54 +++++++ tui/slots/sidebar-top.tsx | 291 ++++++++++++++++++++++++++++++++++ tui/tsconfig.json | 14 ++ 16 files changed, 1001 insertions(+) create mode 100644 tui/commands.ts create mode 100644 tui/components/metric-row.tsx create mode 100644 tui/components/screen.tsx create mode 100644 tui/components/section.tsx create mode 100644 tui/data/context.ts create mode 100644 tui/dcp-probe.tsx create mode 100644 tui/index.tsx create mode 100644 tui/package.json create mode 100644 tui/routes/panel.tsx create mode 100644 tui/shared/config.ts create mode 100644 tui/shared/names.ts create mode 100644 tui/shared/navigation.ts create mode 100644 tui/shared/theme.ts create mode 100644 tui/shared/types.ts create mode 100644 tui/slots/sidebar-top.tsx create mode 100644 tui/tsconfig.json diff --git a/tui/commands.ts b/tui/commands.ts new file mode 100644 index 00000000..5971ae2a --- /dev/null +++ b/tui/commands.ts @@ -0,0 +1,19 @@ +// @ts-nocheck +import type { TuiApi } from "@opencode-ai/plugin/tui" +import { openPanel } from "./shared/navigation" +import type { DcpRouteNames, DcpTuiConfig } from "./shared/types" + +export const registerCommands = (api: TuiApi, config: DcpTuiConfig, names: DcpRouteNames) => { + api.command.register(() => [ + { + title: `${config.label} panel`, + value: names.commands.panel, + description: "Open the DCP placeholder panel", + category: config.label, + slash: { + name: "dcp-panel", + }, + onSelect: () => openPanel(api, names, "command"), + }, + ]) +} diff --git a/tui/components/metric-row.tsx b/tui/components/metric-row.tsx new file mode 100644 index 00000000..2474905d --- /dev/null +++ b/tui/components/metric-row.tsx @@ -0,0 +1,23 @@ +/** @jsxImportSource @opentui/solid */ +import type { DcpPalette } from "../shared/theme" + +const pad = (value: string, width: number) => { + if (value.length >= width) return value + return value.padEnd(width, " ") +} + +export const MetricRow = (props: { + palette: DcpPalette + label: string + value: string + tone?: "text" | "muted" | "accent" +}) => { + const fg = + props.tone === "accent" + ? props.palette.accent + : props.tone === "muted" + ? props.palette.muted + : props.palette.text + + return {`${pad(props.label, 18)} ${props.value}`} +} diff --git a/tui/components/screen.tsx b/tui/components/screen.tsx new file mode 100644 index 00000000..5025c1bc --- /dev/null +++ b/tui/components/screen.tsx @@ -0,0 +1,35 @@ +/** @jsxImportSource @opentui/solid */ +import type { DcpPalette } from "../shared/theme" + +export const Screen = (props: { + palette: DcpPalette + title: string + subtitle?: string + footer?: string + children?: unknown +}) => { + return ( + + + + + {props.title} + + {props.subtitle && {props.subtitle}} + + + {props.children} + + {props.footer && {props.footer}} + + + ) +} diff --git a/tui/components/section.tsx b/tui/components/section.tsx new file mode 100644 index 00000000..546152a5 --- /dev/null +++ b/tui/components/section.tsx @@ -0,0 +1,29 @@ +/** @jsxImportSource @opentui/solid */ +import type { DcpPalette } from "../shared/theme" + +export const Section = (props: { + palette: DcpPalette + title: string + subtitle?: string + children?: unknown +}) => { + return ( + + + {props.title} + + {props.subtitle && {props.subtitle}} + + {props.children} + + + ) +} diff --git a/tui/data/context.ts b/tui/data/context.ts new file mode 100644 index 00000000..5e64e7e9 --- /dev/null +++ b/tui/data/context.ts @@ -0,0 +1,289 @@ +import type { AssistantMessage, TextPart, ToolPart } from "@opencode-ai/sdk/v2" +import { Logger } from "../../lib/logger" +import { isIgnoredUserMessage } from "../../lib/messages/utils" +import { countTokens } from "../../lib/strategies/utils" +import { + createSessionState, + loadSessionState, + type SessionState, + type WithParts, +} from "../../lib/state" +import { + findLastCompactionTimestamp, + loadPruneMap, + loadPruneMessagesState, +} from "../../lib/state/utils" +import { isMessageCompacted } from "../../lib/shared-utils" +import type { DcpContextBreakdown, DcpContextSnapshot, DcpTuiClient } from "../shared/types" + +const logger = new Logger(false) +const snapshotCache = new Map() +const inflightSnapshots = new Map>() +const CACHE_TTL_MS = 5000 + +const emptyBreakdown = (): DcpContextBreakdown => ({ + system: 0, + user: 0, + assistant: 0, + tools: 0, + toolCount: 0, + toolsInContextCount: 0, + prunedTokens: 0, + prunedToolCount: 0, + prunedMessageCount: 0, + total: 0, + messageCount: 0, +}) + +const createSnapshot = (sessionID?: string, notes: string[] = []): DcpContextSnapshot => ({ + sessionID, + breakdown: emptyBreakdown(), + persisted: { + available: false, + activeBlockCount: 0, + activeBlockTopics: [], + }, + notes, + loadedAt: Date.now(), +}) + +const buildState = async ( + sessionID: string, + messages: WithParts[], +): Promise<{ state: SessionState; persisted: Awaited> }> => { + const state = createSessionState() + const persisted = await loadSessionState(sessionID, logger) + + state.sessionId = sessionID + state.lastCompaction = findLastCompactionTimestamp(messages) + state.stats.pruneTokenCounter = 0 + state.stats.totalPruneTokens = persisted?.stats?.totalPruneTokens || 0 + state.prune.tools = loadPruneMap(persisted?.prune?.tools) + state.prune.messages = loadPruneMessagesState(persisted?.prune?.messages) + + return { + state, + persisted, + } +} + +const analyzeTokens = (state: SessionState, messages: WithParts[]): DcpContextBreakdown => { + const breakdown = emptyBreakdown() + breakdown.prunedTokens = state.stats.totalPruneTokens + breakdown.messageCount = messages.length + + let firstAssistant: AssistantMessage | undefined + for (const msg of messages) { + if (msg.info.role !== "assistant") continue + const assistantInfo = msg.info as AssistantMessage + if ( + assistantInfo.tokens?.input > 0 || + assistantInfo.tokens?.cache?.read > 0 || + assistantInfo.tokens?.cache?.write > 0 + ) { + firstAssistant = assistantInfo + break + } + } + + let lastAssistant: AssistantMessage | undefined + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i] + if (msg.info.role !== "assistant") continue + const assistantInfo = msg.info as AssistantMessage + if (assistantInfo.tokens?.output > 0) { + lastAssistant = assistantInfo + break + } + } + + const apiInput = lastAssistant?.tokens?.input || 0 + const apiOutput = lastAssistant?.tokens?.output || 0 + const apiReasoning = lastAssistant?.tokens?.reasoning || 0 + const apiCacheRead = lastAssistant?.tokens?.cache?.read || 0 + const apiCacheWrite = lastAssistant?.tokens?.cache?.write || 0 + breakdown.total = apiInput + apiOutput + apiReasoning + apiCacheRead + apiCacheWrite + + const userTextParts: string[] = [] + const toolInputParts: string[] = [] + const toolOutputParts: string[] = [] + const allToolIds = new Set() + const activeToolIds = new Set() + const prunedByMessageToolIds = new Set() + const allMessageIds = new Set() + + let firstUserText = "" + let foundFirstUser = false + + for (const msg of messages) { + allMessageIds.add(msg.info.id) + const parts = Array.isArray(msg.parts) ? msg.parts : [] + const compacted = isMessageCompacted(state, msg) + const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id) + const messagePruned = !!pruneEntry && pruneEntry.activeBlockIds.length > 0 + const ignoredUser = msg.info.role === "user" && isIgnoredUserMessage(msg) + + for (const part of parts) { + if (part.type === "tool") { + const toolPart = part as ToolPart + if (toolPart.callID) { + allToolIds.add(toolPart.callID) + if (!compacted) activeToolIds.add(toolPart.callID) + if (messagePruned) prunedByMessageToolIds.add(toolPart.callID) + } + + const toolPruned = toolPart.callID && state.prune.tools.has(toolPart.callID) + if (!compacted && !toolPruned) { + if (toolPart.state?.input) { + const inputText = + typeof toolPart.state.input === "string" + ? toolPart.state.input + : JSON.stringify(toolPart.state.input) + toolInputParts.push(inputText) + } + if (toolPart.state?.status === "completed" && toolPart.state?.output) { + const outputText = + typeof toolPart.state.output === "string" + ? toolPart.state.output + : JSON.stringify(toolPart.state.output) + toolOutputParts.push(outputText) + } + } + continue + } + + if (part.type === "text" && msg.info.role === "user" && !compacted && !ignoredUser) { + const textPart = part as TextPart + const text = textPart.text || "" + userTextParts.push(text) + if (!foundFirstUser) firstUserText += text + } + } + + if (msg.info.role === "user" && !ignoredUser && !foundFirstUser) { + foundFirstUser = true + } + } + + const prunedByToolIds = new Set() + for (const toolID of allToolIds) { + if (state.prune.tools.has(toolID)) prunedByToolIds.add(toolID) + } + + const prunedToolIds = new Set([...prunedByToolIds, ...prunedByMessageToolIds]) + breakdown.toolCount = allToolIds.size + breakdown.toolsInContextCount = [...activeToolIds].filter( + (id) => !prunedByToolIds.has(id), + ).length + breakdown.prunedToolCount = prunedToolIds.size + + for (const [messageID, entry] of state.prune.messages.byMessageId) { + if (allMessageIds.has(messageID) && entry.activeBlockIds.length > 0) { + breakdown.prunedMessageCount += 1 + } + } + + const firstUserTokens = countTokens(firstUserText) + breakdown.user = countTokens(userTextParts.join("\n")) + const toolInputTokens = countTokens(toolInputParts.join("\n")) + const toolOutputTokens = countTokens(toolOutputParts.join("\n")) + + if (firstAssistant) { + const firstInput = + (firstAssistant.tokens?.input || 0) + + (firstAssistant.tokens?.cache?.read || 0) + + (firstAssistant.tokens?.cache?.write || 0) + breakdown.system = Math.max(0, firstInput - firstUserTokens) + } + + breakdown.tools = toolInputTokens + toolOutputTokens + breakdown.assistant = Math.max( + 0, + breakdown.total - breakdown.system - breakdown.user - breakdown.tools, + ) + + return breakdown +} + +export const loadContextSnapshot = async ( + client: DcpTuiClient, + sessionID?: string, +): Promise => { + if (!sessionID) { + return createSnapshot(undefined, ["Open this panel from a session to inspect DCP context."]) + } + + const messagesResult = await client.session.messages({ sessionID }) + const messages = Array.isArray(messagesResult.data) + ? (messagesResult.data as WithParts[]) + : ([] as WithParts[]) + + const { state, persisted } = await buildState(sessionID, messages) + const breakdown = analyzeTokens(state, messages) + + const topics = Array.from(state.prune.messages.activeBlockIds) + .map((blockID) => state.prune.messages.blocksById.get(blockID)) + .filter((block): block is NonNullable => !!block) + .map((block) => block.topic) + .filter((topic) => !!topic) + .slice(0, 3) + + const notes: string[] = [] + if (persisted) { + notes.push("Using live session messages plus persisted DCP state.") + } else { + notes.push("No saved DCP state found for this session yet.") + } + if (messages.length === 0) { + notes.push("This session does not have any messages yet.") + } + + return { + sessionID, + breakdown, + persisted: { + available: !!persisted, + activeBlockCount: state.prune.messages.activeBlockIds.size, + activeBlockTopics: topics, + lastUpdated: persisted?.lastUpdated, + }, + notes, + loadedAt: Date.now(), + } +} + +export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | undefined => { + if (!sessionID) return undefined + return snapshotCache.get(sessionID) +} + +export const loadContextSnapshotCached = async ( + client: DcpTuiClient, + sessionID?: string, +): Promise => { + if (!sessionID) { + return createSnapshot(undefined, ["Open this panel from a session to inspect DCP context."]) + } + + const cached = snapshotCache.get(sessionID) + if (cached && Date.now() - cached.loadedAt < CACHE_TTL_MS) { + return cached + } + + const inflight = inflightSnapshots.get(sessionID) + if (inflight) { + return inflight + } + + const request = loadContextSnapshot(client, sessionID) + .then((snapshot) => { + snapshotCache.set(sessionID, snapshot) + return snapshot + }) + .finally(() => { + inflightSnapshots.delete(sessionID) + }) + + inflightSnapshots.set(sessionID, request) + return request +} diff --git a/tui/dcp-probe.tsx b/tui/dcp-probe.tsx new file mode 100644 index 00000000..a84c7b34 --- /dev/null +++ b/tui/dcp-probe.tsx @@ -0,0 +1 @@ +export { default } from "./index" diff --git a/tui/index.tsx b/tui/index.tsx new file mode 100644 index 00000000..6f0c5f3b --- /dev/null +++ b/tui/index.tsx @@ -0,0 +1,30 @@ +// @ts-nocheck +/** @jsxImportSource @opentui/solid */ +import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import { registerCommands } from "./commands" +import { createPanelRoute } from "./routes/panel" +import { createSidebarTopSlot } from "./slots/sidebar-top" +import { readConfig } from "./shared/config" +import { createNames } from "./shared/names" + +const tui = async (input: TuiPluginInput, options?: Record) => { + if (options?.enabled === false) return + + const config = readConfig(options) + const names = createNames(config) + + input.api.route.register([ + createPanelRoute({ + api: input.api, + config, + names, + }), + ]) + + registerCommands(input.api, config, names) + input.slots.register(createSidebarTopSlot(input.api, input.client, config, names)) +} + +export default { + tui, +} diff --git a/tui/package.json b/tui/package.json new file mode 100644 index 00000000..3383012b --- /dev/null +++ b/tui/package.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "dcp-tui-probe-local", + "private": true, + "type": "module", + "dependencies": { + "@opencode-ai/plugin": "1.2.20", + "@opentui/core": "0.0.0-20260304-eee67156", + "@opentui/solid": "0.0.0-20260304-eee67156", + "solid-js": "1.9.9" + } +} diff --git a/tui/routes/panel.tsx b/tui/routes/panel.tsx new file mode 100644 index 00000000..69cb8b2b --- /dev/null +++ b/tui/routes/panel.tsx @@ -0,0 +1,82 @@ +// @ts-nocheck +/** @jsxImportSource @opentui/solid */ +import { useKeyboard } from "@opentui/solid" +import type { TuiApi, TuiRouteDefinition } from "@opencode-ai/plugin/tui" +import { MetricRow } from "../components/metric-row" +import { Screen } from "../components/screen" +import { Section } from "../components/section" +import { getRouteSource, getSessionIDFromParams, goBack } from "../shared/navigation" +import { getPalette } from "../shared/theme" +import type { DcpRouteNames, DcpTuiConfig } from "../shared/types" + +const PanelScreen = (props: { + api: TuiApi + config: DcpTuiConfig + names: DcpRouteNames + params?: Record +}) => { + const palette = getPalette(props.api.theme.current as Record) + const sessionID = () => getSessionIDFromParams(props.params) + const source = () => getRouteSource(props.params) + + useKeyboard((evt) => { + if (props.api.route.current.name !== props.names.routes.panel) return + if (evt.name !== "escape" && !(evt.ctrl && evt.name === "h")) return + evt.preventDefault() + evt.stopPropagation() + goBack(props.api, sessionID()) + }) + + return ( + +
+ + Use this page as the home for future DCP-specific TUI work. + + + The live context breakdown now lives directly in the session sidebar. + +
+ +
+ + + +
+ +
+ - block explorer + - prune history and diagnostics + - manual DCP actions +
+
+ ) +} + +export const createPanelRoute = (input: { + api: TuiApi + config: DcpTuiConfig + names: DcpRouteNames +}): TuiRouteDefinition => { + return { + name: input.names.routes.panel, + render: ({ params }) => ( + + ), + } +} diff --git a/tui/shared/config.ts b/tui/shared/config.ts new file mode 100644 index 00000000..6114f540 --- /dev/null +++ b/tui/shared/config.ts @@ -0,0 +1,14 @@ +import type { DcpTuiConfig } from "./types" + +const pick = (value: unknown, fallback: string) => { + if (typeof value !== "string") return fallback + if (!value.trim()) return fallback + return value +} + +export const readConfig = (options: Record | undefined): DcpTuiConfig => { + return { + label: pick(options?.label, "DCP"), + route: pick(options?.route, "dcp"), + } +} diff --git a/tui/shared/names.ts b/tui/shared/names.ts new file mode 100644 index 00000000..84cb8102 --- /dev/null +++ b/tui/shared/names.ts @@ -0,0 +1,13 @@ +import type { DcpRouteNames, DcpTuiConfig } from "./types" + +export const createNames = (config: DcpTuiConfig): DcpRouteNames => { + return { + slot: `${config.route}.sidebar`, + routes: { + panel: `${config.route}.panel`, + }, + commands: { + panel: `plugin.${config.route}.panel`, + }, + } +} diff --git a/tui/shared/navigation.ts b/tui/shared/navigation.ts new file mode 100644 index 00000000..c1369a6c --- /dev/null +++ b/tui/shared/navigation.ts @@ -0,0 +1,47 @@ +// @ts-nocheck +import type { TuiApi } from "@opencode-ai/plugin/tui" +import type { DcpRouteNames, DcpRouteParams, DcpRouteSource } from "./types" + +export const getSessionIDFromParams = (params?: Record) => { + if (typeof params?.session_id === "string") return params.session_id + return undefined +} + +export const getRouteSource = (params?: Record) => { + if (typeof params?.source === "string") return params.source + return "unknown" +} + +export const getCurrentSessionID = (api: TuiApi) => { + const current = api.route.current + if (current.name === "session") return current.params.sessionID + if ("params" in current && current.params && typeof current.params === "object") { + return getSessionIDFromParams(current.params) + } + return undefined +} + +const navigate = (api: TuiApi, routeName: string, source: DcpRouteSource, sessionID?: string) => { + const params: DcpRouteParams = { + source, + session_id: sessionID ?? getCurrentSessionID(api), + } + api.route.navigate(routeName, params) +} + +export const openPanel = ( + api: TuiApi, + names: DcpRouteNames, + source: DcpRouteSource, + sessionID?: string, +) => { + navigate(api, names.routes.panel, source, sessionID) +} + +export const goBack = (api: TuiApi, sessionID?: string) => { + if (sessionID) { + api.route.navigate("session", { sessionID }) + return + } + api.route.navigate("home") +} diff --git a/tui/shared/theme.ts b/tui/shared/theme.ts new file mode 100644 index 00000000..10da6467 --- /dev/null +++ b/tui/shared/theme.ts @@ -0,0 +1,48 @@ +import type { RGBA } from "@opentui/core" + +export type DcpColor = RGBA | string + +export interface DcpPalette { + panel: DcpColor + base: DcpColor + surface: DcpColor + border: DcpColor + text: DcpColor + muted: DcpColor + accent: DcpColor + success: DcpColor + warning: DcpColor +} + +const defaults = { + panel: "#111111", + base: "#1d1d1d", + surface: "#171717", + border: "#4a4a4a", + text: "#f0f0f0", + muted: "#a5a5a5", + accent: "#5f87ff", + success: "#67b95f", + warning: "#d7a94b", +} + +export const getPalette = (theme: Record): DcpPalette => { + const get = (name: string, fallback: string): DcpColor => { + const value = theme[name] + if (typeof value === "string") return value + if (value && typeof value === "object") return value as RGBA + return fallback + } + + return { + panel: get("backgroundPanel", defaults.panel), + base: get("backgroundElement", defaults.base), + surface: get("background", defaults.surface), + border: get("border", defaults.border), + text: get("text", defaults.text), + muted: get("textMuted", defaults.muted), + accent: get("primary", defaults.accent), + success: get("success", defaults.success), + warning: get("warning", defaults.warning), + } +} diff --git a/tui/shared/types.ts b/tui/shared/types.ts new file mode 100644 index 00000000..c2ca1409 --- /dev/null +++ b/tui/shared/types.ts @@ -0,0 +1,54 @@ +// @ts-nocheck +import type { TuiPluginInput } from "@opencode-ai/plugin/tui" + +export type DcpTuiClient = TuiPluginInput["client"] +export type DcpRouteSource = "sidebar" | "command" + +export interface DcpTuiConfig { + label: string + route: string +} + +export interface DcpRouteNames { + slot: string + routes: { + panel: string + } + commands: { + panel: string + } +} + +export interface DcpRouteParams { + session_id?: string + source?: string +} + +export interface DcpContextBreakdown { + system: number + user: number + assistant: number + tools: number + toolCount: number + toolsInContextCount: number + prunedTokens: number + prunedToolCount: number + prunedMessageCount: number + total: number + messageCount: number +} + +export interface DcpPersistedSummary { + available: boolean + activeBlockCount: number + activeBlockTopics: string[] + lastUpdated?: string +} + +export interface DcpContextSnapshot { + sessionID?: string + breakdown: DcpContextBreakdown + persisted: DcpPersistedSummary + notes: string[] + loadedAt: number +} diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx new file mode 100644 index 00000000..a2542787 --- /dev/null +++ b/tui/slots/sidebar-top.tsx @@ -0,0 +1,291 @@ +// @ts-nocheck +/** @jsxImportSource @opentui/solid */ +import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js" +import { formatTokenCount } from "../../lib/ui/utils" +import { loadContextSnapshotCached, peekContextSnapshot } from "../data/context" +import { openPanel } from "../shared/navigation" +import { getPalette, type DcpPalette } from "../shared/theme" +import type { DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" + +const BAR_WIDTH = 12 + +const toneColor = ( + palette: DcpPalette, + tone: "text" | "muted" | "accent" | "success" | "warning" = "text", +) => { + if (tone === "accent") return palette.accent + if (tone === "success") return palette.success + if (tone === "warning") return palette.warning + if (tone === "muted") return palette.muted + return palette.text +} + +const compactTokenCount = (value: number) => formatTokenCount(value).replace(/ tokens$/, "") + +const buildBar = (value: number, total: number, char: string) => { + if (total <= 0) return " ".repeat(BAR_WIDTH) + const filled = Math.max(0, Math.round((value / total) * BAR_WIDTH)) + return char.repeat(filled).padEnd(BAR_WIDTH, " ") +} + +const SummaryRow = (props: { + palette: DcpPalette + label: string + value: string + tone?: "text" | "muted" | "accent" | "success" | "warning" +}) => { + return ( + + {props.label} + + {props.value} + + + ) +} + +const SidebarContextBar = (props: { + palette: DcpPalette + label: string + value: number + total: number + char: string + tone?: "text" | "muted" | "accent" | "success" | "warning" +}) => { + const percent = props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%" + const label = props.label.padEnd(8, " ") + const bar = buildBar(props.value, props.total, props.char) + return ( + {`${label} ${percent.padStart(4, " ")} |${bar}| ${compactTokenCount(props.value)}`} + ) +} + +const SidebarContext = (props: { + api: any + client: DcpTuiClient + config: DcpTuiConfig + names: DcpRouteNames + palette: DcpPalette + sessionID: string +}) => { + const [snapshot, setSnapshot] = createSignal(peekContextSnapshot(props.sessionID)) + const [loading, setLoading] = createSignal(!snapshot()) + const [error, setError] = createSignal() + + createEffect(() => { + const sessionID = props.sessionID + const cached = peekContextSnapshot(sessionID) + setSnapshot(cached) + setLoading(!cached) + setError(undefined) + + let active = true + void loadContextSnapshotCached(props.client, sessionID) + .then((value) => { + if (!active) return + setSnapshot(value) + setLoading(false) + }) + .catch((cause) => { + if (!active) return + setError(cause instanceof Error ? cause.message : String(cause)) + setLoading(false) + }) + + onCleanup(() => { + active = false + }) + }) + + const prunedItems = createMemo(() => { + const value = snapshot() + if (!value) return "No pruned items" + const parts: string[] = [] + if (value.breakdown.prunedToolCount > 0) { + parts.push( + `${value.breakdown.prunedToolCount} tool${value.breakdown.prunedToolCount === 1 ? "" : "s"}`, + ) + } + if (value.breakdown.prunedMessageCount > 0) { + parts.push( + `${value.breakdown.prunedMessageCount} msg${value.breakdown.prunedMessageCount === 1 ? "" : "s"}`, + ) + } + return parts.length > 0 ? `${parts.join(", ")} pruned` : "No pruned items" + }) + + const blockSummary = createMemo(() => { + const value = snapshot() + if (!value) return "0" + return `${value.persisted.activeBlockCount}` + }) + + const topicLine = createMemo(() => { + const value = snapshot() + if (!value) return "" + if (!value.persisted.activeBlockTopics.length) return "" + return `Topics: ${value.persisted.activeBlockTopics.join(" | ")}` + }) + + const status = createMemo(() => { + if (error() && snapshot()) return { label: "cached", tone: "warning" as const } + if (error()) return { label: "error", tone: "warning" as const } + if (loading() && snapshot()) return { label: "refreshing", tone: "warning" as const } + if (loading()) return { label: "loading", tone: "warning" as const } + return { label: "loaded", tone: "success" as const } + }) + + return ( + openPanel(props.api, props.names, "sidebar", props.sessionID)} + > + + + + + {props.config.label} + + + click for more + + {status().label} + + + + session {props.sessionID.slice(0, 18)} + + + + + Loading DCP context... + + + + + + DCP context failed to load. + + + + + {(value) => ( + + + Current + + ~{compactTokenCount(value().breakdown.total)} + + + + + + + + + + + + + + {prunedItems()} + + {topicLine()} + + + {value().notes[0]} + + + + )} + + + ) +} + +export const createSidebarTopSlot = ( + api: any, + client: DcpTuiClient, + config: DcpTuiConfig, + names: DcpRouteNames, +) => ({ + id: names.slot, + slots: { + sidebar_top(ctx, value: { session_id: string }) { + const palette = getPalette(ctx.theme.current as Record) + return ( + + ) + }, + }, +}) diff --git a/tui/tsconfig.json b/tui/tsconfig.json new file mode 100644 index 00000000..ca44f5cc --- /dev/null +++ b/tui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "preserve", + "jsxImportSource": "@opentui/solid", + "strict": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.ts", "./**/*.tsx"] +} From c34a7b7184f3890cfdffad1129916d2cc86c8e65 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 9 Mar 2026 19:35:24 -0400 Subject: [PATCH 02/40] fix(tui): improve type safety and adopt plugin keybind API --- tui/commands.ts | 3 ++- tui/components/screen.tsx | 5 +++-- tui/components/section.tsx | 5 +++-- tui/index.tsx | 1 - tui/routes/panel.tsx | 35 ++++++++++++++++++++--------------- tui/shared/navigation.ts | 1 - tui/shared/types.ts | 1 - tui/slots/sidebar-top.tsx | 14 ++++++++------ 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/tui/commands.ts b/tui/commands.ts index 5971ae2a..af6a792d 100644 --- a/tui/commands.ts +++ b/tui/commands.ts @@ -1,15 +1,16 @@ -// @ts-nocheck import type { TuiApi } from "@opencode-ai/plugin/tui" import { openPanel } from "./shared/navigation" import type { DcpRouteNames, DcpTuiConfig } from "./shared/types" export const registerCommands = (api: TuiApi, config: DcpTuiConfig, names: DcpRouteNames) => { + const keys = api.keybind?.create({ close: "escape,ctrl+h" }) api.command.register(() => [ { title: `${config.label} panel`, value: names.commands.panel, description: "Open the DCP placeholder panel", category: config.label, + ...(keys ? { keybind: keys.get("close") } : {}), slash: { name: "dcp-panel", }, diff --git a/tui/components/screen.tsx b/tui/components/screen.tsx index 5025c1bc..22f48801 100644 --- a/tui/components/screen.tsx +++ b/tui/components/screen.tsx @@ -1,4 +1,5 @@ /** @jsxImportSource @opentui/solid */ +import type { JSX } from "solid-js" import type { DcpPalette } from "../shared/theme" export const Screen = (props: { @@ -6,7 +7,7 @@ export const Screen = (props: { title: string subtitle?: string footer?: string - children?: unknown + children?: JSX.Element }) => { return ( @@ -17,7 +18,7 @@ export const Screen = (props: { gap={1} padding={1} backgroundColor={props.palette.base} - border={["left"]} + border={{ type: "single" }} borderColor={props.palette.accent} > diff --git a/tui/components/section.tsx b/tui/components/section.tsx index 546152a5..6c95e95a 100644 --- a/tui/components/section.tsx +++ b/tui/components/section.tsx @@ -1,11 +1,12 @@ /** @jsxImportSource @opentui/solid */ +import type { JSX } from "solid-js" import type { DcpPalette } from "../shared/theme" export const Section = (props: { palette: DcpPalette title: string subtitle?: string - children?: unknown + children?: JSX.Element }) => { return ( diff --git a/tui/index.tsx b/tui/index.tsx index 6f0c5f3b..4cc1b3c6 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck /** @jsxImportSource @opentui/solid */ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" import { registerCommands } from "./commands" diff --git a/tui/routes/panel.tsx b/tui/routes/panel.tsx index 69cb8b2b..64f7b2f7 100644 --- a/tui/routes/panel.tsx +++ b/tui/routes/panel.tsx @@ -1,5 +1,5 @@ -// @ts-nocheck /** @jsxImportSource @opentui/solid */ +import { createMemo } from "solid-js" import { useKeyboard } from "@opentui/solid" import type { TuiApi, TuiRouteDefinition } from "@opencode-ai/plugin/tui" import { MetricRow } from "../components/metric-row" @@ -15,13 +15,18 @@ const PanelScreen = (props: { names: DcpRouteNames params?: Record }) => { - const palette = getPalette(props.api.theme.current as Record) + const palette = createMemo(() => getPalette(props.api.theme.current as Record)) const sessionID = () => getSessionIDFromParams(props.params) const source = () => getRouteSource(props.params) + const keys = props.api.keybind?.create({ close: "escape,ctrl+h" }) useKeyboard((evt) => { if (props.api.route.current.name !== props.names.routes.panel) return - if (evt.name !== "escape" && !(evt.ctrl && evt.name === "h")) return + if (props.api.ui?.dialog?.open) return + const matched = keys + ? keys.match("close", evt) + : evt.name === "escape" || (evt.ctrl && evt.name === "h") + if (!matched) return evt.preventDefault() evt.stopPropagation() goBack(props.api, sessionID()) @@ -29,35 +34,35 @@ const PanelScreen = (props: { return ( -
- +
+ Use this page as the home for future DCP-specific TUI work. - + The live context breakdown now lives directly in the session sidebar.
-
- - +
+ +
-
- - block explorer - - prune history and diagnostics - - manual DCP actions +
+ - block explorer + - prune history and diagnostics + - manual DCP actions
) diff --git a/tui/shared/navigation.ts b/tui/shared/navigation.ts index c1369a6c..2c14d727 100644 --- a/tui/shared/navigation.ts +++ b/tui/shared/navigation.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import type { TuiApi } from "@opencode-ai/plugin/tui" import type { DcpRouteNames, DcpRouteParams, DcpRouteSource } from "./types" diff --git a/tui/shared/types.ts b/tui/shared/types.ts index c2ca1409..88a95fa9 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import type { TuiPluginInput } from "@opencode-ai/plugin/tui" export type DcpTuiClient = TuiPluginInput["client"] diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index a2542787..b658a99f 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -1,6 +1,6 @@ -// @ts-nocheck /** @jsxImportSource @opentui/solid */ import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js" +import type { TuiApi } from "@opencode-ai/plugin/tui" import { formatTokenCount } from "../../lib/ui/utils" import { loadContextSnapshotCached, peekContextSnapshot } from "../data/context" import { openPanel } from "../shared/navigation" @@ -70,7 +70,7 @@ const SidebarContextBar = (props: { } const SidebarContext = (props: { - api: any + api: TuiApi client: DcpTuiClient config: DcpTuiConfig names: DcpRouteNames @@ -150,7 +150,7 @@ const SidebarContext = (props: { flexDirection="column" gap={0} backgroundColor={props.palette.base} - border={["left"]} + border={{ type: "single" }} borderColor={props.palette.accent} paddingTop={1} paddingBottom={1} @@ -267,7 +267,7 @@ const SidebarContext = (props: { } export const createSidebarTopSlot = ( - api: any, + api: TuiApi, client: DcpTuiClient, config: DcpTuiConfig, names: DcpRouteNames, @@ -275,14 +275,16 @@ export const createSidebarTopSlot = ( id: names.slot, slots: { sidebar_top(ctx, value: { session_id: string }) { - const palette = getPalette(ctx.theme.current as Record) + const palette = createMemo(() => + getPalette(ctx.theme.current as Record), + ) return ( ) From 46ba09b4de28d620a18c8ee6b284d410a59cdec3 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 9 Mar 2026 19:35:28 -0400 Subject: [PATCH 03/40] chore(tui): bump dependencies and update readme image --- tui/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tui/package.json b/tui/package.json index 3383012b..2960722a 100644 --- a/tui/package.json +++ b/tui/package.json @@ -4,9 +4,9 @@ "private": true, "type": "module", "dependencies": { - "@opencode-ai/plugin": "1.2.20", - "@opentui/core": "0.0.0-20260304-eee67156", - "@opentui/solid": "0.0.0-20260304-eee67156", + "@opencode-ai/plugin": "1.2.21", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", "solid-js": "1.9.9" } } From afd71538e17f78056aefa1e20b2cb190fbb3d0ba Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 9 Mar 2026 23:00:12 -0400 Subject: [PATCH 04/40] feat(lib): add scope support to Logger --- lib/logger.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/logger.ts b/lib/logger.ts index 43b5d9e8..8949d6c1 100644 --- a/lib/logger.ts +++ b/lib/logger.ts @@ -5,12 +5,16 @@ import { homedir } from "os" export class Logger { private logDir: string + private scope?: string public enabled: boolean - constructor(enabled: boolean) { + constructor(enabled: boolean, scope?: string) { this.enabled = enabled + this.scope = scope?.replace(/[^A-Za-z0-9._-]/g, "_") const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config") - this.logDir = join(configHome, "opencode", "logs", "dcp") + this.logDir = this.scope + ? join(configHome, "opencode", "logs", "dcp", this.scope) + : join(configHome, "opencode", "logs", "dcp") } private async ensureLogDir() { @@ -78,12 +82,17 @@ export class Logger { const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""}\n` - const dailyLogDir = join(this.logDir, "daily") - if (!existsSync(dailyLogDir)) { - await mkdir(dailyLogDir, { recursive: true }) + const logFile = this.scope + ? join(this.logDir, `${new Date().toISOString().split("T")[0]}.log`) + : join(this.logDir, "daily", `${new Date().toISOString().split("T")[0]}.log`) + + if (!this.scope) { + const dailyLogDir = join(this.logDir, "daily") + if (!existsSync(dailyLogDir)) { + await mkdir(dailyLogDir, { recursive: true }) + } } - const logFile = join(dailyLogDir, `${new Date().toISOString().split("T")[0]}.log`) await writeFile(logFile, logLine, { flag: "a" }) } catch (error) {} } From 95c38cd2348ad53db70c2cb4d0088c917c017d6e Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 9 Mar 2026 23:00:18 -0400 Subject: [PATCH 05/40] feat(tui): event-driven sidebar refresh with reactivity fix --- tui/data/context.ts | 80 +++++- tui/index.tsx | 22 +- tui/shared/config.ts | 6 + tui/shared/types.ts | 1 + tui/slots/sidebar-top.tsx | 502 ++++++++++++++++++++++++++++---------- 5 files changed, 482 insertions(+), 129 deletions(-) diff --git a/tui/data/context.ts b/tui/data/context.ts index 5e64e7e9..b9cf5ccf 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -16,11 +16,24 @@ import { import { isMessageCompacted } from "../../lib/shared-utils" import type { DcpContextBreakdown, DcpContextSnapshot, DcpTuiClient } from "../shared/types" -const logger = new Logger(false) +let logger = new Logger(false, "TUI") const snapshotCache = new Map() const inflightSnapshots = new Map>() const CACHE_TTL_MS = 5000 +const summarizeSnapshot = (snapshot: DcpContextSnapshot) => ({ + sessionID: snapshot.sessionID, + totalTokens: snapshot.breakdown.total, + messageCount: snapshot.breakdown.messageCount, + prunedTokens: snapshot.breakdown.prunedTokens, + activeBlockCount: snapshot.persisted.activeBlockCount, + loadedAt: snapshot.loadedAt, +}) + +export const setContextLogger = (nextLogger: Logger) => { + logger = nextLogger +} + const emptyBreakdown = (): DcpContextBreakdown => ({ system: 0, user: 0, @@ -35,7 +48,10 @@ const emptyBreakdown = (): DcpContextBreakdown => ({ messageCount: 0, }) -const createSnapshot = (sessionID?: string, notes: string[] = []): DcpContextSnapshot => ({ +export const createPlaceholderContextSnapshot = ( + sessionID?: string, + notes: string[] = [], +): DcpContextSnapshot => ({ sessionID, breakdown: emptyBreakdown(), persisted: { @@ -210,13 +226,21 @@ export const loadContextSnapshot = async ( sessionID?: string, ): Promise => { if (!sessionID) { - return createSnapshot(undefined, ["Open this panel from a session to inspect DCP context."]) + void logger.debug("Context snapshot requested without session") + return createPlaceholderContextSnapshot(undefined, [ + "Open this panel from a session to inspect DCP context.", + ]) } + void logger.debug("Loading context snapshot", { sessionID }) const messagesResult = await client.session.messages({ sessionID }) const messages = Array.isArray(messagesResult.data) ? (messagesResult.data as WithParts[]) : ([] as WithParts[]) + void logger.debug("Fetched session messages for context snapshot", { + sessionID, + messageCount: messages.length, + }) const { state, persisted } = await buildState(sessionID, messages) const breakdown = analyzeTokens(state, messages) @@ -238,7 +262,7 @@ export const loadContextSnapshot = async ( notes.push("This session does not have any messages yet.") } - return { + const snapshot = { sessionID, breakdown, persisted: { @@ -250,6 +274,13 @@ export const loadContextSnapshot = async ( notes, loadedAt: Date.now(), } + + void logger.debug("Loaded context snapshot", { + ...summarizeSnapshot(snapshot), + persisted: !!persisted, + }) + + return snapshot } export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | undefined => { @@ -257,31 +288,70 @@ export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | un return snapshotCache.get(sessionID) } +export const invalidateContextSnapshot = (sessionID?: string) => { + if (!sessionID) { + void logger.debug("Invalidating all context snapshots") + snapshotCache.clear() + inflightSnapshots.clear() + return + } + + void logger.debug("Invalidating context snapshot", { sessionID }) + snapshotCache.delete(sessionID) + inflightSnapshots.delete(sessionID) +} + export const loadContextSnapshotCached = async ( client: DcpTuiClient, sessionID?: string, ): Promise => { if (!sessionID) { - return createSnapshot(undefined, ["Open this panel from a session to inspect DCP context."]) + void logger.debug("Cached context snapshot requested without session") + return createPlaceholderContextSnapshot(undefined, [ + "Open this panel from a session to inspect DCP context.", + ]) } const cached = snapshotCache.get(sessionID) if (cached && Date.now() - cached.loadedAt < CACHE_TTL_MS) { + void logger.debug("Context snapshot cache hit", { + sessionID, + cacheAgeMs: Date.now() - cached.loadedAt, + }) return cached } + if (cached) { + void logger.debug("Context snapshot cache stale", { + sessionID, + cacheAgeMs: Date.now() - cached.loadedAt, + }) + } else { + void logger.debug("Context snapshot cache miss", { sessionID }) + } + const inflight = inflightSnapshots.get(sessionID) if (inflight) { + void logger.debug("Reusing inflight context snapshot request", { sessionID }) return inflight } const request = loadContextSnapshot(client, sessionID) .then((snapshot) => { snapshotCache.set(sessionID, snapshot) + void logger.debug("Stored context snapshot in cache", summarizeSnapshot(snapshot)) return snapshot }) + .catch((cause) => { + void logger.error("Context snapshot request failed", { + sessionID, + error: cause instanceof Error ? cause.message : String(cause), + }) + throw cause + }) .finally(() => { inflightSnapshots.delete(sessionID) + void logger.debug("Cleared inflight context snapshot request", { sessionID }) }) inflightSnapshots.set(sessionID, request) diff --git a/tui/index.tsx b/tui/index.tsx index 4cc1b3c6..24b22707 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -1,6 +1,8 @@ /** @jsxImportSource @opentui/solid */ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import { Logger } from "../lib/logger" import { registerCommands } from "./commands" +import { setContextLogger } from "./data/context" import { createPanelRoute } from "./routes/panel" import { createSidebarTopSlot } from "./slots/sidebar-top" import { readConfig } from "./shared/config" @@ -11,6 +13,14 @@ const tui = async (input: TuiPluginInput, options?: Record) => const config = readConfig(options) const names = createNames(config) + const logger = new Logger(config.debug, "TUI") + + setContextLogger(logger) + void logger.info("DCP TUI initialized", { + debug: config.debug, + label: config.label, + route: config.route, + }) input.api.route.register([ createPanelRoute({ @@ -21,7 +31,17 @@ const tui = async (input: TuiPluginInput, options?: Record) => ]) registerCommands(input.api, config, names) - input.slots.register(createSidebarTopSlot(input.api, input.client, config, names)) + input.slots.register( + createSidebarTopSlot( + input.api, + input.client, + input.event, + input.renderer, + logger, + config, + names, + ), + ) } export default { diff --git a/tui/shared/config.ts b/tui/shared/config.ts index 6114f540..7db8561a 100644 --- a/tui/shared/config.ts +++ b/tui/shared/config.ts @@ -6,8 +6,14 @@ const pick = (value: unknown, fallback: string) => { return value } +const pickBoolean = (value: unknown, fallback: boolean) => { + if (typeof value !== "boolean") return fallback + return value +} + export const readConfig = (options: Record | undefined): DcpTuiConfig => { return { + debug: pickBoolean(options?.debug, false), label: pick(options?.label, "DCP"), route: pick(options?.route, "dcp"), } diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 88a95fa9..2d95843a 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -4,6 +4,7 @@ export type DcpTuiClient = TuiPluginInput["client"] export type DcpRouteSource = "sidebar" | "command" export interface DcpTuiConfig { + debug: boolean label: string route: string } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index b658a99f..c24f76c1 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -1,13 +1,20 @@ /** @jsxImportSource @opentui/solid */ -import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js" -import type { TuiApi } from "@opencode-ai/plugin/tui" +import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" +import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" +import { Logger } from "../../lib/logger" import { formatTokenCount } from "../../lib/ui/utils" -import { loadContextSnapshotCached, peekContextSnapshot } from "../data/context" +import { + createPlaceholderContextSnapshot, + invalidateContextSnapshot, + loadContextSnapshotCached, + peekContextSnapshot, +} from "../data/context" import { openPanel } from "../shared/navigation" import { getPalette, type DcpPalette } from "../shared/theme" import type { DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" const BAR_WIDTH = 12 +const REFRESH_DEBOUNCE_MS = 100 const toneColor = ( palette: DcpPalette, @@ -59,56 +66,299 @@ const SidebarContextBar = (props: { char: string tone?: "text" | "muted" | "accent" | "success" | "warning" }) => { - const percent = props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%" - const label = props.label.padEnd(8, " ") - const bar = buildBar(props.value, props.total, props.char) + const percent = createMemo(() => + props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%", + ) + const label = createMemo(() => props.label.padEnd(8, " ")) + const bar = createMemo(() => buildBar(props.value, props.total, props.char)) return ( {`${label} ${percent.padStart(4, " ")} |${bar}| ${compactTokenCount(props.value)}`} + >{`${label()} ${percent().padStart(4, " ")} |${bar()}| ${compactTokenCount(props.value)}`} ) } const SidebarContext = (props: { api: TuiApi client: DcpTuiClient + event: TuiPluginInput["event"] + renderer: TuiPluginInput["renderer"] + logger: Logger config: DcpTuiConfig names: DcpRouteNames palette: DcpPalette - sessionID: string + sessionID: () => string }) => { - const [snapshot, setSnapshot] = createSignal(peekContextSnapshot(props.sessionID)) - const [loading, setLoading] = createSignal(!snapshot()) + const initialSnapshot = peekContextSnapshot(props.sessionID()) + const [snapshot, setSnapshot] = createSignal( + initialSnapshot ?? createPlaceholderContextSnapshot(props.sessionID()), + ) + const [loading, setLoading] = createSignal(!initialSnapshot) const [error, setError] = createSignal() + let requestVersion = 0 + let renderTimeout: ReturnType | undefined + + const requestRender = (reason: string, data?: Record) => { + const activeSessionID = untrack(() => props.sessionID()) + void props.logger.debug("Sidebar requested renderer refresh", { + activeSessionID, + reason, + ...data, + }) + if (renderTimeout) clearTimeout(renderTimeout) + renderTimeout = setTimeout(() => { + renderTimeout = undefined + try { + void props.logger.debug("Sidebar renderer refresh dispatched", { + activeSessionID, + reason, + ...data, + }) + props.renderer.requestRender() + } catch (cause) { + void props.logger.warn("Sidebar renderer refresh failed", { + activeSessionID, + reason, + error: cause instanceof Error ? cause.message : String(cause), + ...data, + }) + } + }, 0) + } + + onCleanup(() => { + if (renderTimeout) clearTimeout(renderTimeout) + }) + + const refreshSnapshot = async ( + sessionID: string, + options?: { invalidate?: boolean; preserveSnapshot?: boolean; reason?: string }, + ) => { + const preserveSnapshot = options?.preserveSnapshot ?? false + const reason = options?.reason ?? "unspecified" + + void props.logger.debug("Sidebar refresh start", { + sessionID, + invalidate: !!options?.invalidate, + preserveSnapshot, + reason, + }) + + if (options?.invalidate) { + invalidateContextSnapshot(sessionID) + } - createEffect(() => { - const sessionID = props.sessionID const cached = peekContextSnapshot(sessionID) - setSnapshot(cached) - setLoading(!cached) + if (cached) { + void props.logger.debug("Sidebar using cached snapshot before reload", { + sessionID, + loadedAt: cached.loadedAt, + totalTokens: cached.breakdown.total, + }) + setSnapshot(cached) + setLoading(false) + } else { + const current = untrack(snapshot) + if (!preserveSnapshot || current?.sessionID !== sessionID) { + setSnapshot(createPlaceholderContextSnapshot(sessionID, ["Loading DCP context..."])) + } + setLoading(true) + void props.logger.debug("Sidebar entering loading state", { + sessionID, + hadCurrentSnapshot: !!current, + preservedSnapshot: preserveSnapshot && current?.sessionID === sessionID, + }) + } setError(undefined) + requestRender("refresh-start", { sessionID, reason }) + + const currentRequest = ++requestVersion + void props.logger.debug("Sidebar refresh request issued", { + sessionID, + requestVersion: currentRequest, + reason, + }) - let active = true - void loadContextSnapshotCached(props.client, sessionID) - .then((value) => { - if (!active) return - setSnapshot(value) - setLoading(false) + try { + const value = await loadContextSnapshotCached(props.client, sessionID) + if (currentRequest !== requestVersion || props.sessionID() !== sessionID) { + void props.logger.debug("Sidebar refresh result ignored as stale", { + sessionID, + requestVersion: currentRequest, + activeRequestVersion: requestVersion, + activeSessionID: props.sessionID(), + reason, + }) + return + } + setSnapshot(value) + setLoading(false) + void props.logger.debug("Sidebar refresh succeeded", { + sessionID, + requestVersion: currentRequest, + totalTokens: value.breakdown.total, + messageCount: value.breakdown.messageCount, + activeBlockCount: value.persisted.activeBlockCount, + reason, }) - .catch((cause) => { - if (!active) return - setError(cause instanceof Error ? cause.message : String(cause)) - setLoading(false) + requestRender("refresh-success", { sessionID, reason, requestVersion: currentRequest }) + } catch (cause) { + if (currentRequest !== requestVersion || props.sessionID() !== sessionID) { + void props.logger.debug("Sidebar refresh error ignored as stale", { + sessionID, + requestVersion: currentRequest, + activeRequestVersion: requestVersion, + activeSessionID: props.sessionID(), + reason, + }) + return + } + setError(cause instanceof Error ? cause.message : String(cause)) + setLoading(false) + void props.logger.error("Sidebar refresh failed", { + sessionID, + requestVersion: currentRequest, + error: cause instanceof Error ? cause.message : String(cause), + reason, }) + requestRender("refresh-error", { sessionID, reason, requestVersion: currentRequest }) + } + } - onCleanup(() => { - active = false - }) - }) + createEffect( + on( + props.sessionID, + (sessionID) => { + void props.logger.info("Sidebar active session changed", { sessionID }) + void refreshSnapshot(sessionID, { reason: "session-change" }) + }, + { defer: false }, + ), + ) + + createEffect( + on( + props.sessionID, + (sessionID) => { + let timeout: ReturnType | undefined + let pendingReason: string | undefined + + void props.logger.debug("Sidebar event subscriptions armed", { sessionID }) + + const scheduleRefresh = (reason: string, data?: Record) => { + if (!sessionID) return + if (timeout) clearTimeout(timeout) + pendingReason = reason + void props.logger.debug("Sidebar refresh scheduled", { + sessionID, + debounceMs: REFRESH_DEBOUNCE_MS, + reason, + ...data, + }) + timeout = setTimeout(() => { + const flushReason = pendingReason ?? reason + pendingReason = undefined + timeout = undefined + void props.logger.debug("Sidebar refresh debounce fired", { + sessionID, + reason: flushReason, + }) + void refreshSnapshot(sessionID, { + invalidate: true, + preserveSnapshot: true, + reason: flushReason, + }) + }, REFRESH_DEBOUNCE_MS) + } + + const unsubs = [ + props.event.on("message.updated", (event) => { + if (event.properties.info.sessionID !== sessionID) return + scheduleRefresh("message.updated", { + eventSessionID: event.properties.info.sessionID, + messageID: event.properties.info.id, + }) + }), + props.event.on("message.removed", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("message.removed", { + eventSessionID: event.properties.sessionID, + messageID: event.properties.messageID, + }) + }), + props.event.on("message.part.updated", (event) => { + if (event.properties.part.sessionID !== sessionID) return + scheduleRefresh("message.part.updated", { + eventSessionID: event.properties.part.sessionID, + messageID: event.properties.part.messageID, + partID: event.properties.part.id, + }) + }), + props.event.on("message.part.delta", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("message.part.delta", { + eventSessionID: event.properties.sessionID, + messageID: event.properties.messageID, + partID: event.properties.partID, + field: event.properties.field, + }) + }), + props.event.on("message.part.removed", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("message.part.removed", { + eventSessionID: event.properties.sessionID, + messageID: event.properties.messageID, + partID: event.properties.partID, + }) + }), + props.event.on("session.updated", (event) => { + if (event.properties.info.id !== sessionID) return + scheduleRefresh("session.updated", { + eventSessionID: event.properties.info.id, + }) + }), + props.event.on("session.deleted", (event) => { + if (event.properties.info.id !== sessionID) return + scheduleRefresh("session.deleted", { + eventSessionID: event.properties.info.id, + }) + }), + props.event.on("session.diff", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("session.diff", { + eventSessionID: event.properties.sessionID, + }) + }), + props.event.on("session.error", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("session.error", { + eventSessionID: event.properties.sessionID, + error: event.properties.error, + }) + }), + props.event.on("session.status", (event) => { + if (event.properties.sessionID !== sessionID) return + scheduleRefresh("session.status", { + eventSessionID: event.properties.sessionID, + status: event.properties.status, + }) + }), + ] + + onCleanup(() => { + if (timeout) clearTimeout(timeout) + void props.logger.debug("Sidebar event subscriptions cleaned up", { sessionID }) + for (const unsub of unsubs) { + unsub() + } + }) + }, + { defer: false }, + ), + ) const prunedItems = createMemo(() => { const value = snapshot() - if (!value) return "No pruned items" const parts: string[] = [] if (value.breakdown.prunedToolCount > 0) { parts.push( @@ -124,22 +374,34 @@ const SidebarContext = (props: { }) const blockSummary = createMemo(() => { - const value = snapshot() - if (!value) return "0" - return `${value.persisted.activeBlockCount}` + return `${snapshot().persisted.activeBlockCount}` }) const topicLine = createMemo(() => { const value = snapshot() - if (!value) return "" if (!value.persisted.activeBlockTopics.length) return "" return `Topics: ${value.persisted.activeBlockTopics.join(" | ")}` }) + const noteLine = createMemo(() => { + const topic = topicLine() + if (topic) return topic + return snapshot().notes[0] ?? "" + }) + + const stateLine = createMemo(() => { + if (error() && snapshot().breakdown.total === 0) return "DCP context failed to load." + if (error()) return `Refresh failed: ${error()}` + if (loading()) return "Loading DCP context..." + return "DCP context loaded." + }) + const status = createMemo(() => { - if (error() && snapshot()) return { label: "cached", tone: "warning" as const } + if (error() && snapshot().breakdown.total > 0) + return { label: "cached", tone: "warning" as const } if (error()) return { label: "error", tone: "warning" as const } - if (loading() && snapshot()) return { label: "refreshing", tone: "warning" as const } + if (loading() && snapshot().breakdown.total > 0) + return { label: "refreshing", tone: "warning" as const } if (loading()) return { label: "loading", tone: "warning" as const } return { label: "loaded", tone: "success" as const } }) @@ -156,7 +418,7 @@ const SidebarContext = (props: { paddingBottom={1} paddingLeft={1} paddingRight={1} - onMouseUp={() => openPanel(props.api, props.names, "sidebar", props.sessionID)} + onMouseUp={() => openPanel(props.api, props.names, "sidebar", props.sessionID())} > @@ -171,97 +433,82 @@ const SidebarContext = (props: { - session {props.sessionID.slice(0, 18)} + session {props.sessionID().slice(0, 18)} + + + + + {stateLine()} + - - - Loading DCP context... + + + Current + + ~{compactTokenCount(snapshot().breakdown.total)} + - + + - - - DCP context failed to load. + + + + + - - - - {(value) => ( - - - Current - - ~{compactTokenCount(value().breakdown.total)} - - - - - - - - - - - - - - {prunedItems()} - - {topicLine()} - - - {value().notes[0]} - - - - )} - + + + {prunedItems()} + {noteLine()} + + ) } @@ -269,12 +516,18 @@ const SidebarContext = (props: { export const createSidebarTopSlot = ( api: TuiApi, client: DcpTuiClient, + event: TuiPluginInput["event"], + renderer: TuiPluginInput["renderer"], + logger: Logger, config: DcpTuiConfig, names: DcpRouteNames, ) => ({ id: names.slot, slots: { sidebar_top(ctx, value: { session_id: string }) { + // value is a reactive proxy from @opentui/solid splitProps — + // value.session_id updates automatically when the host navigates + // to a different session (no event subscription needed). const palette = createMemo(() => getPalette(ctx.theme.current as Record), ) @@ -282,10 +535,13 @@ export const createSidebarTopSlot = ( value.session_id} /> ) }, From 146b52d6843209a9f02b40f8194dc393ed15d307 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 9 Mar 2026 23:00:24 -0400 Subject: [PATCH 06/40] chore(tui): add host runtime linking dev script --- package.json | 1 + scripts/link-tui-host-runtime.mjs | 67 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 scripts/link-tui-host-runtime.mjs diff --git a/package.json b/package.json index f273cb86..43e6cb8a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "prepublishOnly": "npm run build", "dev": "opencode plugin dev", "typecheck": "tsc --noEmit", + "tui:link-host-runtime": "node scripts/link-tui-host-runtime.mjs", "test": "node --import tsx --test tests/*.test.ts", "format": "prettier --write .", "format:check": "prettier --check .", diff --git a/scripts/link-tui-host-runtime.mjs b/scripts/link-tui-host-runtime.mjs new file mode 100644 index 00000000..458278d4 --- /dev/null +++ b/scripts/link-tui-host-runtime.mjs @@ -0,0 +1,67 @@ +import fs from "node:fs" +import path from "node:path" +import { createRequire } from "node:module" + +const repoRoot = path.resolve(import.meta.dirname, "..") +const tuiNodeModules = path.join(repoRoot, "tui", "node_modules") +const hostRoot = path.resolve( + process.argv[2] ?? process.env.OPENCODE_SOURCE_ROOT ?? "/home/dan/src/opencode", +) +const hostEntry = path.join(hostRoot, "packages", "opencode", "src", "cli", "cmd", "tui", "app.tsx") +const pluginEntry = path.join(repoRoot, "tui", "index.tsx") + +if (!fs.existsSync(hostEntry)) { + throw new Error(`OpenCode TUI entry not found: ${hostEntry}`) +} + +const hostRequire = createRequire(hostEntry) +const pluginRequire = createRequire(pluginEntry) + +const findPackageRoot = (resolvedFile) => { + let current = path.dirname(resolvedFile) + while (true) { + const manifest = path.join(current, "package.json") + if (fs.existsSync(manifest)) return current + const parent = path.dirname(current) + if (parent === current) { + throw new Error(`Could not find package root for ${resolvedFile}`) + } + current = parent + } +} + +const resolveHostPackage = (specifier) => findPackageRoot(hostRequire.resolve(specifier)) +const resolvePluginPackage = (specifier) => findPackageRoot(pluginRequire.resolve(specifier)) + +const links = [ + { + specifier: "solid-js", + dest: path.join(tuiNodeModules, "solid-js"), + }, + { + specifier: "@opentui/solid", + dest: path.join(tuiNodeModules, "@opentui", "solid"), + }, + { + specifier: "@opentui/core", + dest: path.join(tuiNodeModules, "@opentui", "core"), + }, +] + +for (const link of links) { + const target = resolveHostPackage(link.specifier) + fs.mkdirSync(path.dirname(link.dest), { recursive: true }) + fs.rmSync(link.dest, { recursive: true, force: true }) + fs.symlinkSync(target, link.dest, "dir") + + const pluginResolved = resolvePluginPackage(link.specifier) + const pluginReal = fs.realpathSync.native(pluginResolved) + const targetReal = fs.realpathSync.native(target) + if (pluginReal !== targetReal) { + throw new Error(`Failed to link ${link.specifier}: ${pluginReal} != ${targetReal}`) + } + + console.log(`linked ${link.specifier}`) + console.log(` host: ${targetReal}`) + console.log(` plugin: ${pluginReal}`) +} From 657918352ce2b8090eae0ac675f3c043dbe1c91c Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 01:54:57 -0400 Subject: [PATCH 07/40] feat(tui): redesign sidebar layout with silent refresh and topic display --- lib/ui/utils.ts | 5 +- tui/data/context.ts | 7 +- tui/shared/types.ts | 1 + tui/slots/sidebar-top.tsx | 217 ++++++++++++++++++-------------------- 4 files changed, 110 insertions(+), 120 deletions(-) diff --git a/lib/ui/utils.ts b/lib/ui/utils.ts index a3de072b..ad31542c 100644 --- a/lib/ui/utils.ts +++ b/lib/ui/utils.ts @@ -144,7 +144,10 @@ export function formatStatsHeader(totalTokensSaved: number, pruneTokenCounter: n export function formatTokenCount(tokens: number, compact?: boolean): string { const suffix = compact ? "" : " tokens" - if (tokens >= 1000) { + if (tokens >= 100_000) { + return `${Math.round(tokens / 1000)}K` + suffix + } + if (tokens >= 10_000 || (compact && tokens >= 1000)) { return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K") + suffix } return tokens.toString() + suffix diff --git a/tui/data/context.ts b/tui/data/context.ts index b9cf5ccf..71e6d689 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -58,6 +58,7 @@ export const createPlaceholderContextSnapshot = ( available: false, activeBlockCount: 0, activeBlockTopics: [], + activeBlockTopicTotal: 0, }, notes, loadedAt: Date.now(), @@ -245,12 +246,13 @@ export const loadContextSnapshot = async ( const { state, persisted } = await buildState(sessionID, messages) const breakdown = analyzeTokens(state, messages) - const topics = Array.from(state.prune.messages.activeBlockIds) + const allTopics = Array.from(state.prune.messages.activeBlockIds) .map((blockID) => state.prune.messages.blocksById.get(blockID)) .filter((block): block is NonNullable => !!block) .map((block) => block.topic) .filter((topic) => !!topic) - .slice(0, 3) + const topics = allTopics.slice(0, 5) + const topicTotal = allTopics.length const notes: string[] = [] if (persisted) { @@ -269,6 +271,7 @@ export const loadContextSnapshot = async ( available: !!persisted, activeBlockCount: state.prune.messages.activeBlockIds.size, activeBlockTopics: topics, + activeBlockTopicTotal: topicTotal, lastUpdated: persisted?.lastUpdated, }, notes, diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 2d95843a..c67c8484 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -42,6 +42,7 @@ export interface DcpPersistedSummary { available: boolean activeBlockCount: number activeBlockTopics: string[] + activeBlockTopicTotal: number lastUpdated?: string } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index c24f76c1..07bf2d0d 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -14,6 +14,11 @@ import { getPalette, type DcpPalette } from "../shared/theme" import type { DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" const BAR_WIDTH = 12 +// Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5) +const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5 + +const truncate = (text: string, max: number) => + text.length > max ? text.slice(0, max - 3) + "..." : text const REFRESH_DEBOUNCE_MS = 100 const toneColor = ( @@ -40,17 +45,16 @@ const SummaryRow = (props: { label: string value: string tone?: "text" | "muted" | "accent" | "success" | "warning" + marginTop?: number }) => { return ( - {props.label} + {props.label} {props.value} @@ -69,12 +73,15 @@ const SidebarContextBar = (props: { const percent = createMemo(() => props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%", ) - const label = createMemo(() => props.label.padEnd(8, " ")) + const label = createMemo(() => props.label.padEnd(9, " ")) const bar = createMemo(() => buildBar(props.value, props.total, props.char)) return ( - {`${label()} ${percent().padStart(4, " ")} |${bar()}| ${compactTokenCount(props.value)}`} + + {label()} + + {` ${percent().padStart(4, " ")} |${bar()}| ${compactTokenCount(props.value).padStart(5, " ")}`} + + ) } @@ -149,6 +156,7 @@ const SidebarContext = (props: { } const cached = peekContextSnapshot(sessionID) + let silentRefresh = false if (cached) { void props.logger.debug("Sidebar using cached snapshot before reload", { sessionID, @@ -159,18 +167,24 @@ const SidebarContext = (props: { setLoading(false) } else { const current = untrack(snapshot) - if (!preserveSnapshot || current?.sessionID !== sessionID) { + if (preserveSnapshot && current?.sessionID === sessionID) { + silentRefresh = true + void props.logger.debug("Sidebar silent refresh, keeping current snapshot", { + sessionID, + }) + } else { setSnapshot(createPlaceholderContextSnapshot(sessionID, ["Loading DCP context..."])) + setLoading(true) + void props.logger.debug("Sidebar entering loading state", { + sessionID, + hadCurrentSnapshot: !!current, + }) } - setLoading(true) - void props.logger.debug("Sidebar entering loading state", { - sessionID, - hadCurrentSnapshot: !!current, - preservedSnapshot: preserveSnapshot && current?.sessionID === sessionID, - }) } setError(undefined) - requestRender("refresh-start", { sessionID, reason }) + if (!silentRefresh) { + requestRender("refresh-start", { sessionID, reason }) + } const currentRequest = ++requestVersion void props.logger.debug("Sidebar refresh request issued", { @@ -357,44 +371,14 @@ const SidebarContext = (props: { ), ) - const prunedItems = createMemo(() => { - const value = snapshot() - const parts: string[] = [] - if (value.breakdown.prunedToolCount > 0) { - parts.push( - `${value.breakdown.prunedToolCount} tool${value.breakdown.prunedToolCount === 1 ? "" : "s"}`, - ) - } - if (value.breakdown.prunedMessageCount > 0) { - parts.push( - `${value.breakdown.prunedMessageCount} msg${value.breakdown.prunedMessageCount === 1 ? "" : "s"}`, - ) - } - return parts.length > 0 ? `${parts.join(", ")} pruned` : "No pruned items" - }) - const blockSummary = createMemo(() => { return `${snapshot().persisted.activeBlockCount}` }) - const topicLine = createMemo(() => { - const value = snapshot() - if (!value.persisted.activeBlockTopics.length) return "" - return `Topics: ${value.persisted.activeBlockTopics.join(" | ")}` - }) - - const noteLine = createMemo(() => { - const topic = topicLine() - if (topic) return topic - return snapshot().notes[0] ?? "" - }) - - const stateLine = createMemo(() => { - if (error() && snapshot().breakdown.total === 0) return "DCP context failed to load." - if (error()) return `Refresh failed: ${error()}` - if (loading()) return "Loading DCP context..." - return "DCP context loaded." - }) + const topics = createMemo(() => snapshot().persisted.activeBlockTopics) + const topicTotal = createMemo(() => snapshot().persisted.activeBlockTopicTotal) + const topicOverflow = createMemo(() => topicTotal() - topics().length) + const fallbackNote = createMemo(() => snapshot().notes[0] ?? "") const status = createMemo(() => { if (error() && snapshot().breakdown.total > 0) @@ -411,7 +395,7 @@ const SidebarContext = (props: { width="100%" flexDirection="column" gap={0} - backgroundColor={props.palette.base} + backgroundColor={props.palette.surface} border={{ type: "single" }} borderColor={props.palette.accent} paddingTop={1} @@ -427,87 +411,86 @@ const SidebarContext = (props: { {props.config.label} - click for more + click for more {status().label} - session {props.sessionID().slice(0, 18)} - - - - - {stateLine()} + + {props.sessionID().length > 27 + ? props.sessionID().slice(0, 27) + "..." + : props.sessionID()} + + + - - Current - - ~{compactTokenCount(snapshot().breakdown.total)} - - - - + + + - - - - - - - - - {prunedItems()} - {noteLine()} - + + {topics().length > 0 ? ( + <> + + Compressed Topics + + {topics().map((t) => ( + {truncate(t, CONTENT_WIDTH)} + ))} + {topicOverflow() > 0 ? ( + + ... {topicOverflow()} more topics + + ) : null} + + ) : fallbackNote() ? ( + {fallbackNote()} + ) : null} ) From 34741493a1fd6e282be894d7c32f4607fc25d462 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 15:25:15 -0400 Subject: [PATCH 08/40] refactor(lib): extract shared analyzeTokens module --- lib/analysis/tokens.ts | 225 ++++++++++++++++++++++++++++++++++++++++ lib/commands/context.ts | 181 +------------------------------- tui/data/context.ts | 163 +---------------------------- tui/shared/types.ts | 18 +--- 4 files changed, 239 insertions(+), 348 deletions(-) create mode 100644 lib/analysis/tokens.ts diff --git a/lib/analysis/tokens.ts b/lib/analysis/tokens.ts new file mode 100644 index 00000000..354ef83a --- /dev/null +++ b/lib/analysis/tokens.ts @@ -0,0 +1,225 @@ +/** + * Shared Token Analysis + * Computes a breakdown of token usage across categories for a session. + * + * TOKEN CALCULATION STRATEGY + * ========================== + * We minimize tokenizer estimation by leveraging API-reported values wherever possible. + * + * WHAT WE GET FROM THE API (exact): + * - tokens.input : Input tokens for each assistant response + * - tokens.output : Output tokens generated (includes text + tool calls) + * - tokens.reasoning: Reasoning tokens used + * - tokens.cache : Cache read/write tokens + * + * HOW WE CALCULATE EACH CATEGORY: + * + * SYSTEM = firstAssistant.input + cache.read + cache.write - tokenizer(firstUserMessage) + * The first response's total input (input + cache.read + cache.write) + * contains system + first user message. On the first request of a + * session, the system prompt appears in cache.write (cache creation), + * not cache.read. + * + * TOOLS = tokenizer(toolInputs + toolOutputs) - prunedTokens + * We must tokenize tools anyway for pruning decisions. + * + * USER = tokenizer(all user messages) + * User messages are typically small, so estimation is acceptable. + * + * ASSISTANT = total - system - user - tools + * Calculated as residual. This absorbs: + * - Assistant text output tokens + * - Reasoning tokens (if persisted by the model) + * - Any estimation errors + * + * TOTAL = input + output + reasoning + cache.read + cache.write + * Matches opencode's UI display. + * + * WHY ASSISTANT IS THE RESIDUAL: + * If reasoning tokens persist in context (model-dependent), they semantically + * belong with "Assistant" since reasoning IS assistant-generated content. + */ + +import type { AssistantMessage, TextPart, ToolPart } from "@opencode-ai/sdk/v2" +import type { SessionState, WithParts } from "../state" +import { isIgnoredUserMessage } from "../messages/query" +import { isMessageCompacted } from "../state/utils" +import { countTokens, extractCompletedToolOutput } from "../token-utils" + +export type MessageStatus = "active" | "pruned" + +export interface TokenBreakdown { + system: number + user: number + assistant: number + tools: number + toolCount: number + toolsInContextCount: number + prunedTokens: number + prunedToolCount: number + prunedMessageCount: number + total: number + messageCount: number +} + +export interface TokenAnalysis { + breakdown: TokenBreakdown + messageStatuses: MessageStatus[] +} + +export function emptyBreakdown(): TokenBreakdown { + return { + system: 0, + user: 0, + assistant: 0, + tools: 0, + toolCount: 0, + toolsInContextCount: 0, + prunedTokens: 0, + prunedToolCount: 0, + prunedMessageCount: 0, + total: 0, + messageCount: 0, + } +} + +export function analyzeTokens(state: SessionState, messages: WithParts[]): TokenAnalysis { + const breakdown = emptyBreakdown() + const messageStatuses: MessageStatus[] = [] + breakdown.prunedTokens = state.stats.totalPruneTokens + + let firstAssistant: AssistantMessage | undefined + for (const msg of messages) { + if (msg.info.role !== "assistant") continue + const assistantInfo = msg.info as AssistantMessage + if ( + assistantInfo.tokens?.input > 0 || + assistantInfo.tokens?.cache?.read > 0 || + assistantInfo.tokens?.cache?.write > 0 + ) { + firstAssistant = assistantInfo + break + } + } + + let lastAssistant: AssistantMessage | undefined + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i] + if (msg.info.role !== "assistant") continue + const assistantInfo = msg.info as AssistantMessage + if (assistantInfo.tokens?.output > 0) { + lastAssistant = assistantInfo + break + } + } + + const apiInput = lastAssistant?.tokens?.input || 0 + const apiOutput = lastAssistant?.tokens?.output || 0 + const apiReasoning = lastAssistant?.tokens?.reasoning || 0 + const apiCacheRead = lastAssistant?.tokens?.cache?.read || 0 + const apiCacheWrite = lastAssistant?.tokens?.cache?.write || 0 + breakdown.total = apiInput + apiOutput + apiReasoning + apiCacheRead + apiCacheWrite + + const userTextParts: string[] = [] + const toolInputParts: string[] = [] + const toolOutputParts: string[] = [] + const allToolIds = new Set() + const activeToolIds = new Set() + const prunedByMessageToolIds = new Set() + const allMessageIds = new Set() + + let firstUserText = "" + let foundFirstUser = false + + for (const msg of messages) { + const ignoredUser = msg.info.role === "user" && isIgnoredUserMessage(msg) + if (ignoredUser) continue + + allMessageIds.add(msg.info.id) + const parts = Array.isArray(msg.parts) ? msg.parts : [] + const compacted = isMessageCompacted(state, msg) + const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id) + const messagePruned = !!pruneEntry && pruneEntry.activeBlockIds.length > 0 + const messageActive = !compacted && !messagePruned + + breakdown.messageCount += 1 + messageStatuses.push(messageActive ? "active" : "pruned") + + for (const part of parts) { + if (part.type === "tool") { + const toolPart = part as ToolPart + if (toolPart.callID) { + allToolIds.add(toolPart.callID) + if (!compacted) activeToolIds.add(toolPart.callID) + if (messagePruned) prunedByMessageToolIds.add(toolPart.callID) + } + + const toolPruned = toolPart.callID && state.prune.tools.has(toolPart.callID) + if (!compacted && !toolPruned) { + if (toolPart.state?.input) { + const inputText = + typeof toolPart.state.input === "string" + ? toolPart.state.input + : JSON.stringify(toolPart.state.input) + toolInputParts.push(inputText) + } + const outputText = extractCompletedToolOutput(toolPart) + if (outputText !== undefined) { + toolOutputParts.push(outputText) + } + } + continue + } + + if (part.type === "text" && msg.info.role === "user" && !compacted) { + const textPart = part as TextPart + const text = textPart.text || "" + userTextParts.push(text) + if (!foundFirstUser) firstUserText += text + } + } + + if (msg.info.role === "user" && !foundFirstUser) { + foundFirstUser = true + } + } + + const prunedByToolIds = new Set() + for (const toolID of allToolIds) { + if (state.prune.tools.has(toolID)) prunedByToolIds.add(toolID) + } + + const prunedToolIds = new Set([...prunedByToolIds, ...prunedByMessageToolIds]) + breakdown.toolCount = allToolIds.size + breakdown.toolsInContextCount = [...activeToolIds].filter( + (id) => !prunedByToolIds.has(id), + ).length + breakdown.prunedToolCount = prunedToolIds.size + + for (const [messageID, entry] of state.prune.messages.byMessageId) { + if (allMessageIds.has(messageID) && entry.activeBlockIds.length > 0) { + breakdown.prunedMessageCount += 1 + } + } + + const firstUserTokens = countTokens(firstUserText) + breakdown.user = countTokens(userTextParts.join("\n")) + const toolInputTokens = countTokens(toolInputParts.join("\n")) + const toolOutputTokens = countTokens(toolOutputParts.join("\n")) + + if (firstAssistant) { + const firstInput = + (firstAssistant.tokens?.input || 0) + + (firstAssistant.tokens?.cache?.read || 0) + + (firstAssistant.tokens?.cache?.write || 0) + breakdown.system = Math.max(0, firstInput - firstUserTokens) + } + + breakdown.tools = toolInputTokens + toolOutputTokens + breakdown.assistant = Math.max( + 0, + breakdown.total - breakdown.system - breakdown.user - breakdown.tools, + ) + + return { breakdown, messageStatuses } +} diff --git a/lib/commands/context.ts b/lib/commands/context.ts index c4f0408e..b297c719 100644 --- a/lib/commands/context.ts +++ b/lib/commands/context.ts @@ -1,6 +1,7 @@ /** * DCP Context Command * Shows a visual breakdown of token usage in the current session. + * Token calculation logic lives in ../analysis/tokens.ts * * TOKEN CALCULATION STRATEGY * ========================== @@ -44,10 +45,8 @@ import type { Logger } from "../logger" import type { SessionState, WithParts } from "../state" import { sendIgnoredMessage } from "../ui/notification" import { formatTokenCount } from "../ui/utils" -import { isIgnoredUserMessage } from "../messages/query" -import { isMessageCompacted } from "../state/utils" -import { countTokens, extractCompletedToolOutput, getCurrentParams } from "../token-utils" -import type { AssistantMessage, TextPart, ToolPart } from "@opencode-ai/sdk/v2" +import { getCurrentParams } from "../token-utils" +import { analyzeTokens, type TokenBreakdown } from "../analysis/tokens" export interface ContextCommandContext { client: any @@ -56,178 +55,6 @@ export interface ContextCommandContext { sessionId: string messages: WithParts[] } - -interface TokenBreakdown { - system: number - user: number - assistant: number - tools: number - toolCount: number - toolsInContextCount: number - prunedTokens: number - prunedToolCount: number - prunedMessageCount: number - total: number -} - -function analyzeTokens(state: SessionState, messages: WithParts[]): TokenBreakdown { - const breakdown: TokenBreakdown = { - system: 0, - user: 0, - assistant: 0, - tools: 0, - toolCount: 0, - toolsInContextCount: 0, - prunedTokens: state.stats.totalPruneTokens, - prunedToolCount: 0, - prunedMessageCount: 0, - total: 0, - } - - let firstAssistant: AssistantMessage | undefined - for (const msg of messages) { - if (msg.info.role === "assistant") { - const assistantInfo = msg.info as AssistantMessage - if ( - assistantInfo.tokens?.input > 0 || - assistantInfo.tokens?.cache?.read > 0 || - assistantInfo.tokens?.cache?.write > 0 - ) { - firstAssistant = assistantInfo - break - } - } - } - - let lastAssistant: AssistantMessage | undefined - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg.info.role === "assistant") { - const assistantInfo = msg.info as AssistantMessage - if (assistantInfo.tokens?.output > 0) { - lastAssistant = assistantInfo - break - } - } - } - - const apiInput = lastAssistant?.tokens?.input || 0 - const apiOutput = lastAssistant?.tokens?.output || 0 - const apiReasoning = lastAssistant?.tokens?.reasoning || 0 - const apiCacheRead = lastAssistant?.tokens?.cache?.read || 0 - const apiCacheWrite = lastAssistant?.tokens?.cache?.write || 0 - breakdown.total = apiInput + apiOutput + apiReasoning + apiCacheRead + apiCacheWrite - - const userTextParts: string[] = [] - const toolInputParts: string[] = [] - const toolOutputParts: string[] = [] - let firstUserText = "" - let foundFirstUser = false - const allToolIds = new Set() - const activeToolIds = new Set() - const prunedByMessageToolIds = new Set() - const allMessageIds = new Set() - - for (const msg of messages) { - allMessageIds.add(msg.info.id) - const parts = Array.isArray(msg.parts) ? msg.parts : [] - const isCompacted = isMessageCompacted(state, msg) - const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id) - const isMessagePruned = !!pruneEntry && pruneEntry.activeBlockIds.length > 0 - const isIgnoredUser = isIgnoredUserMessage(msg) - - for (const part of parts) { - if (part.type === "tool") { - const toolPart = part as ToolPart - if (toolPart.callID) { - allToolIds.add(toolPart.callID) - if (!isCompacted) { - activeToolIds.add(toolPart.callID) - } - if (isMessagePruned) { - prunedByMessageToolIds.add(toolPart.callID) - } - } - - const isPruned = toolPart.callID && state.prune.tools.has(toolPart.callID) - if (!isCompacted && !isPruned) { - if (toolPart.state?.input) { - const inputStr = - typeof toolPart.state.input === "string" - ? toolPart.state.input - : JSON.stringify(toolPart.state.input) - toolInputParts.push(inputStr) - } - - const outputStr = extractCompletedToolOutput(toolPart) - if (outputStr !== undefined) { - toolOutputParts.push(outputStr) - } - } - } else if ( - part.type === "text" && - msg.info.role === "user" && - !isCompacted && - !isIgnoredUser - ) { - const textPart = part as TextPart - const text = textPart.text || "" - userTextParts.push(text) - if (!foundFirstUser) { - firstUserText += text - } - } - } - - if (msg.info.role === "user" && !isIgnoredUser && !foundFirstUser) { - foundFirstUser = true - } - } - - const prunedByToolIds = new Set() - for (const id of allToolIds) { - if (state.prune.tools.has(id)) { - prunedByToolIds.add(id) - } - } - - const prunedToolIds = new Set([...prunedByToolIds, ...prunedByMessageToolIds]) - const toolsInContextCount = [...activeToolIds].filter((id) => !prunedByToolIds.has(id)).length - - let prunedMessageCount = 0 - for (const [id, entry] of state.prune.messages.byMessageId) { - if (allMessageIds.has(id) && entry.activeBlockIds.length > 0) { - prunedMessageCount++ - } - } - - breakdown.toolCount = allToolIds.size - breakdown.toolsInContextCount = toolsInContextCount - breakdown.prunedToolCount = prunedToolIds.size - breakdown.prunedMessageCount = prunedMessageCount - - const firstUserTokens = countTokens(firstUserText) - breakdown.user = countTokens(userTextParts.join("\n")) - const toolInputTokens = countTokens(toolInputParts.join("\n")) - const toolOutputTokens = countTokens(toolOutputParts.join("\n")) - - if (firstAssistant) { - const firstInput = - (firstAssistant.tokens?.input || 0) + - (firstAssistant.tokens?.cache?.read || 0) + - (firstAssistant.tokens?.cache?.write || 0) - breakdown.system = Math.max(0, firstInput - firstUserTokens) - } - - breakdown.tools = toolInputTokens + toolOutputTokens - breakdown.assistant = Math.max( - 0, - breakdown.total - breakdown.system - breakdown.user - breakdown.tools, - ) - - return breakdown -} - function createBar(value: number, maxValue: number, width: number, char: string = "█"): string { if (maxValue === 0) return "" const filled = Math.round((value / maxValue) * width) @@ -296,7 +123,7 @@ function formatContextMessage(breakdown: TokenBreakdown): string { export async function handleContextCommand(ctx: ContextCommandContext): Promise { const { client, state, logger, sessionId, messages } = ctx - const breakdown = analyzeTokens(state, messages) + const { breakdown } = analyzeTokens(state, messages) const message = formatContextMessage(breakdown) diff --git a/tui/data/context.ts b/tui/data/context.ts index 71e6d689..b76eb673 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -1,7 +1,4 @@ -import type { AssistantMessage, TextPart, ToolPart } from "@opencode-ai/sdk/v2" import { Logger } from "../../lib/logger" -import { isIgnoredUserMessage } from "../../lib/messages/utils" -import { countTokens } from "../../lib/strategies/utils" import { createSessionState, loadSessionState, @@ -13,8 +10,8 @@ import { loadPruneMap, loadPruneMessagesState, } from "../../lib/state/utils" -import { isMessageCompacted } from "../../lib/shared-utils" -import type { DcpContextBreakdown, DcpContextSnapshot, DcpTuiClient } from "../shared/types" +import { analyzeTokens, emptyBreakdown } from "../../lib/analysis/tokens" +import type { DcpContextSnapshot, DcpTuiClient } from "../shared/types" let logger = new Logger(false, "TUI") const snapshotCache = new Map() @@ -34,20 +31,6 @@ export const setContextLogger = (nextLogger: Logger) => { logger = nextLogger } -const emptyBreakdown = (): DcpContextBreakdown => ({ - system: 0, - user: 0, - assistant: 0, - tools: 0, - toolCount: 0, - toolsInContextCount: 0, - prunedTokens: 0, - prunedToolCount: 0, - prunedMessageCount: 0, - total: 0, - messageCount: 0, -}) - export const createPlaceholderContextSnapshot = ( sessionID?: string, notes: string[] = [], @@ -60,6 +43,7 @@ export const createPlaceholderContextSnapshot = ( activeBlockTopics: [], activeBlockTopicTotal: 0, }, + messageStatuses: [], notes, loadedAt: Date.now(), }) @@ -84,144 +68,6 @@ const buildState = async ( } } -const analyzeTokens = (state: SessionState, messages: WithParts[]): DcpContextBreakdown => { - const breakdown = emptyBreakdown() - breakdown.prunedTokens = state.stats.totalPruneTokens - breakdown.messageCount = messages.length - - let firstAssistant: AssistantMessage | undefined - for (const msg of messages) { - if (msg.info.role !== "assistant") continue - const assistantInfo = msg.info as AssistantMessage - if ( - assistantInfo.tokens?.input > 0 || - assistantInfo.tokens?.cache?.read > 0 || - assistantInfo.tokens?.cache?.write > 0 - ) { - firstAssistant = assistantInfo - break - } - } - - let lastAssistant: AssistantMessage | undefined - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg.info.role !== "assistant") continue - const assistantInfo = msg.info as AssistantMessage - if (assistantInfo.tokens?.output > 0) { - lastAssistant = assistantInfo - break - } - } - - const apiInput = lastAssistant?.tokens?.input || 0 - const apiOutput = lastAssistant?.tokens?.output || 0 - const apiReasoning = lastAssistant?.tokens?.reasoning || 0 - const apiCacheRead = lastAssistant?.tokens?.cache?.read || 0 - const apiCacheWrite = lastAssistant?.tokens?.cache?.write || 0 - breakdown.total = apiInput + apiOutput + apiReasoning + apiCacheRead + apiCacheWrite - - const userTextParts: string[] = [] - const toolInputParts: string[] = [] - const toolOutputParts: string[] = [] - const allToolIds = new Set() - const activeToolIds = new Set() - const prunedByMessageToolIds = new Set() - const allMessageIds = new Set() - - let firstUserText = "" - let foundFirstUser = false - - for (const msg of messages) { - allMessageIds.add(msg.info.id) - const parts = Array.isArray(msg.parts) ? msg.parts : [] - const compacted = isMessageCompacted(state, msg) - const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id) - const messagePruned = !!pruneEntry && pruneEntry.activeBlockIds.length > 0 - const ignoredUser = msg.info.role === "user" && isIgnoredUserMessage(msg) - - for (const part of parts) { - if (part.type === "tool") { - const toolPart = part as ToolPart - if (toolPart.callID) { - allToolIds.add(toolPart.callID) - if (!compacted) activeToolIds.add(toolPart.callID) - if (messagePruned) prunedByMessageToolIds.add(toolPart.callID) - } - - const toolPruned = toolPart.callID && state.prune.tools.has(toolPart.callID) - if (!compacted && !toolPruned) { - if (toolPart.state?.input) { - const inputText = - typeof toolPart.state.input === "string" - ? toolPart.state.input - : JSON.stringify(toolPart.state.input) - toolInputParts.push(inputText) - } - if (toolPart.state?.status === "completed" && toolPart.state?.output) { - const outputText = - typeof toolPart.state.output === "string" - ? toolPart.state.output - : JSON.stringify(toolPart.state.output) - toolOutputParts.push(outputText) - } - } - continue - } - - if (part.type === "text" && msg.info.role === "user" && !compacted && !ignoredUser) { - const textPart = part as TextPart - const text = textPart.text || "" - userTextParts.push(text) - if (!foundFirstUser) firstUserText += text - } - } - - if (msg.info.role === "user" && !ignoredUser && !foundFirstUser) { - foundFirstUser = true - } - } - - const prunedByToolIds = new Set() - for (const toolID of allToolIds) { - if (state.prune.tools.has(toolID)) prunedByToolIds.add(toolID) - } - - const prunedToolIds = new Set([...prunedByToolIds, ...prunedByMessageToolIds]) - breakdown.toolCount = allToolIds.size - breakdown.toolsInContextCount = [...activeToolIds].filter( - (id) => !prunedByToolIds.has(id), - ).length - breakdown.prunedToolCount = prunedToolIds.size - - for (const [messageID, entry] of state.prune.messages.byMessageId) { - if (allMessageIds.has(messageID) && entry.activeBlockIds.length > 0) { - breakdown.prunedMessageCount += 1 - } - } - - const firstUserTokens = countTokens(firstUserText) - breakdown.user = countTokens(userTextParts.join("\n")) - const toolInputTokens = countTokens(toolInputParts.join("\n")) - const toolOutputTokens = countTokens(toolOutputParts.join("\n")) - - if (firstAssistant) { - const firstInput = - (firstAssistant.tokens?.input || 0) + - (firstAssistant.tokens?.cache?.read || 0) + - (firstAssistant.tokens?.cache?.write || 0) - breakdown.system = Math.max(0, firstInput - firstUserTokens) - } - - breakdown.tools = toolInputTokens + toolOutputTokens - breakdown.assistant = Math.max( - 0, - breakdown.total - breakdown.system - breakdown.user - breakdown.tools, - ) - - return breakdown -} - export const loadContextSnapshot = async ( client: DcpTuiClient, sessionID?: string, @@ -244,7 +90,7 @@ export const loadContextSnapshot = async ( }) const { state, persisted } = await buildState(sessionID, messages) - const breakdown = analyzeTokens(state, messages) + const { breakdown, messageStatuses } = analyzeTokens(state, messages) const allTopics = Array.from(state.prune.messages.activeBlockIds) .map((blockID) => state.prune.messages.blocksById.get(blockID)) @@ -274,6 +120,7 @@ export const loadContextSnapshot = async ( activeBlockTopicTotal: topicTotal, lastUpdated: persisted?.lastUpdated, }, + messageStatuses, notes, loadedAt: Date.now(), } diff --git a/tui/shared/types.ts b/tui/shared/types.ts index c67c8484..3264dc77 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -24,19 +24,10 @@ export interface DcpRouteParams { source?: string } -export interface DcpContextBreakdown { - system: number - user: number - assistant: number - tools: number - toolCount: number - toolsInContextCount: number - prunedTokens: number - prunedToolCount: number - prunedMessageCount: number - total: number - messageCount: number -} +export { + type TokenBreakdown as DcpContextBreakdown, + type MessageStatus as DcpMessageStatus, +} from "../../lib/analysis/tokens" export interface DcpPersistedSummary { available: boolean @@ -50,6 +41,7 @@ export interface DcpContextSnapshot { sessionID?: string breakdown: DcpContextBreakdown persisted: DcpPersistedSummary + messageStatuses: DcpMessageStatus[] notes: string[] loadedAt: number } From a34a0c82ebc6388ab8efe4714d512a7df5573b65 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 15:25:23 -0400 Subject: [PATCH 09/40] feat(tui): add message bar, graph improvements, and compact token format --- tui/slots/sidebar-top.tsx | 113 +++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 07bf2d0d..b0c4c9e6 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -10,8 +10,8 @@ import { peekContextSnapshot, } from "../data/context" import { openPanel } from "../shared/navigation" -import { getPalette, type DcpPalette } from "../shared/theme" -import type { DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" +import { getPalette, type DcpColor, type DcpPalette } from "../shared/theme" +import type { DcpMessageStatus, DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" const BAR_WIDTH = 12 // Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5) @@ -32,12 +32,53 @@ const toneColor = ( return palette.text } -const compactTokenCount = (value: number) => formatTokenCount(value).replace(/ tokens$/, "") +const compactTokenCount = (value: number): string => { + if (value >= 100_000) return `${Math.round(value / 1000)}K` + if (value >= 1_000) { + const k = (value / 1000).toFixed(1) + return k.endsWith(".0") ? `${Math.round(value / 1000)}K` : `${k}K` + } + const d = Math.round(value / 100) + if (d >= 10) return `${Math.round(value / 1000)}K` + if (d > 0) return `.${d}K` + return "0" +} -const buildBar = (value: number, total: number, char: string) => { +const buildBar = (value: number, total: number) => { if (total <= 0) return " ".repeat(BAR_WIDTH) const filled = Math.max(0, Math.round((value / total) * BAR_WIDTH)) - return char.repeat(filled).padEnd(BAR_WIDTH, " ") + return "█".repeat(filled).padEnd(BAR_WIDTH, " ") +} + +const buildMessageBar = ( + statuses: DcpMessageStatus[], + width: number = CONTENT_WIDTH, +): { text: string; status: DcpMessageStatus }[] => { + const ACTIVE = "█" + const PRUNED = "░" + if (statuses.length === 0) return [{ text: PRUNED.repeat(width), status: "pruned" }] + + // Map each bar position to a message status + const bar: DcpMessageStatus[] = new Array(width).fill("active") + for (let m = 0; m < statuses.length; m++) { + const start = Math.floor((m / statuses.length) * width) + const end = Math.floor(((m + 1) / statuses.length) * width) + for (let i = start; i < end; i++) { + bar[i] = statuses[m] + } + } + + // Group consecutive same-status positions into runs + const runs: { text: string; status: DcpMessageStatus }[] = [] + let runStart = 0 + for (let i = 1; i <= width; i++) { + if (i === width || bar[i] !== bar[runStart]) { + const char = bar[runStart] === "pruned" ? PRUNED : ACTIVE + runs.push({ text: char.repeat(i - runStart), status: bar[runStart] }) + runStart = i + } + } + return runs } const SummaryRow = (props: { @@ -45,6 +86,7 @@ const SummaryRow = (props: { label: string value: string tone?: "text" | "muted" | "accent" | "success" | "warning" + swatch?: DcpColor marginTop?: number }) => { return ( @@ -54,7 +96,10 @@ const SummaryRow = (props: { justifyContent="space-between" marginTop={props.marginTop} > - {props.label} + + {props.swatch && {"█ "}} + {props.label} + {props.value} @@ -67,20 +112,23 @@ const SidebarContextBar = (props: { label: string value: number total: number - char: string tone?: "text" | "muted" | "accent" | "success" | "warning" }) => { const percent = createMemo(() => props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%", ) const label = createMemo(() => props.label.padEnd(9, " ")) - const bar = createMemo(() => buildBar(props.value, props.total, props.char)) + const bar = createMemo(() => buildBar(props.value, props.total)) return ( - {label()} - - {` ${percent().padStart(4, " ")} |${bar()}| ${compactTokenCount(props.value).padStart(5, " ")}`} + + {label()} + {` ${percent().padStart(4, " ")} |`} + {bar()} + {`| ${compactTokenCount(props.value).padStart(5, " ")}`} ) } @@ -371,15 +419,13 @@ const SidebarContext = (props: { ), ) - const blockSummary = createMemo(() => { - return `${snapshot().persisted.activeBlockCount}` - }) - const topics = createMemo(() => snapshot().persisted.activeBlockTopics) const topicTotal = createMemo(() => snapshot().persisted.activeBlockTopicTotal) const topicOverflow = createMemo(() => topicTotal() - topics().length) const fallbackNote = createMemo(() => snapshot().notes[0] ?? "") + const messageBarRuns = createMemo(() => buildMessageBar(snapshot().messageStatuses)) + const status = createMemo(() => { if (error() && snapshot().breakdown.total > 0) return { label: "cached", tone: "warning" as const } @@ -394,7 +440,6 @@ const SidebarContext = (props: { {status().label} - - - {props.sessionID().length > 27 - ? props.sessionID().slice(0, 27) + "..." - : props.sessionID()} - - - - + {snapshot().messageStatuses.length > 0 && ( + + {messageBarRuns().map((run) => ( + + {run.text} + + ))} + + )} + + From a65be995fe620e43db73a8cb6898da12420a18ef Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 15:38:53 -0400 Subject: [PATCH 10/40] refactor(tui): consolidate sidebar helpers --- tui/components/metric-row.tsx | 17 +++++++---------- tui/data/context.ts | 2 +- tui/shared/theme.ts | 10 ++++++++++ tui/slots/sidebar-top.tsx | 17 ++--------------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/tui/components/metric-row.tsx b/tui/components/metric-row.tsx index 2474905d..c3b03bf9 100644 --- a/tui/components/metric-row.tsx +++ b/tui/components/metric-row.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource @opentui/solid */ -import type { DcpPalette } from "../shared/theme" +import { toneColor, type DcpPalette, type DcpTone } from "../shared/theme" const pad = (value: string, width: number) => { if (value.length >= width) return value @@ -10,14 +10,11 @@ export const MetricRow = (props: { palette: DcpPalette label: string value: string - tone?: "text" | "muted" | "accent" + tone?: DcpTone }) => { - const fg = - props.tone === "accent" - ? props.palette.accent - : props.tone === "muted" - ? props.palette.muted - : props.palette.text - - return {`${pad(props.label, 18)} ${props.value}`} + return ( + {`${pad(props.label, 18)} ${props.value}`} + ) } diff --git a/tui/data/context.ts b/tui/data/context.ts index b76eb673..d55a686f 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -68,7 +68,7 @@ const buildState = async ( } } -export const loadContextSnapshot = async ( +const loadContextSnapshot = async ( client: DcpTuiClient, sessionID?: string, ): Promise => { diff --git a/tui/shared/theme.ts b/tui/shared/theme.ts index 10da6467..fa73b106 100644 --- a/tui/shared/theme.ts +++ b/tui/shared/theme.ts @@ -14,6 +14,8 @@ export interface DcpPalette { warning: DcpColor } +export type DcpTone = "text" | "muted" | "accent" | "success" | "warning" + const defaults = { panel: "#111111", base: "#1d1d1d", @@ -46,3 +48,11 @@ export const getPalette = (theme: Record): DcpPalette => { warning: get("warning", defaults.warning), } } + +export const toneColor = (palette: DcpPalette, tone: DcpTone = "text") => { + if (tone === "accent") return palette.accent + if (tone === "success") return palette.success + if (tone === "warning") return palette.warning + if (tone === "muted") return palette.muted + return palette.text +} diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index b0c4c9e6..6d4b8f3d 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -2,7 +2,7 @@ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" import { Logger } from "../../lib/logger" -import { formatTokenCount } from "../../lib/ui/utils" +import { truncate } from "../../lib/ui/utils" import { createPlaceholderContextSnapshot, invalidateContextSnapshot, @@ -10,28 +10,15 @@ import { peekContextSnapshot, } from "../data/context" import { openPanel } from "../shared/navigation" -import { getPalette, type DcpColor, type DcpPalette } from "../shared/theme" +import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared/theme" import type { DcpMessageStatus, DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" const BAR_WIDTH = 12 // Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5) const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5 -const truncate = (text: string, max: number) => - text.length > max ? text.slice(0, max - 3) + "..." : text const REFRESH_DEBOUNCE_MS = 100 -const toneColor = ( - palette: DcpPalette, - tone: "text" | "muted" | "accent" | "success" | "warning" = "text", -) => { - if (tone === "accent") return palette.accent - if (tone === "success") return palette.success - if (tone === "warning") return palette.warning - if (tone === "muted") return palette.muted - return palette.text -} - const compactTokenCount = (value: number): string => { if (value >= 100_000) return `${Math.round(value / 1000)}K` if (value >= 1_000) { From f54fd81d984e03fdc780e6a678770333c8282912 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 18:36:31 -0400 Subject: [PATCH 11/40] feat(tui): add all-time stats and rename context label --- tui/data/context.ts | 11 ++++++++++- tui/shared/types.ts | 6 ++++++ tui/slots/sidebar-top.tsx | 26 +++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/tui/data/context.ts b/tui/data/context.ts index d55a686f..4c55dcd6 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -10,6 +10,7 @@ import { loadPruneMap, loadPruneMessagesState, } from "../../lib/state/utils" +import { loadAllSessionStats } from "../../lib/state/persistence" import { analyzeTokens, emptyBreakdown } from "../../lib/analysis/tokens" import type { DcpContextSnapshot, DcpTuiClient } from "../shared/types" @@ -44,6 +45,7 @@ export const createPlaceholderContextSnapshot = ( activeBlockTopicTotal: 0, }, messageStatuses: [], + allTimeStats: { totalTokensSaved: 0, sessionCount: 0 }, notes, loadedAt: Date.now(), }) @@ -90,7 +92,10 @@ const loadContextSnapshot = async ( }) const { state, persisted } = await buildState(sessionID, messages) - const { breakdown, messageStatuses } = analyzeTokens(state, messages) + const [{ breakdown, messageStatuses }, aggregated] = await Promise.all([ + Promise.resolve(analyzeTokens(state, messages)), + loadAllSessionStats(logger), + ]) const allTopics = Array.from(state.prune.messages.activeBlockIds) .map((blockID) => state.prune.messages.blocksById.get(blockID)) @@ -121,6 +126,10 @@ const loadContextSnapshot = async ( lastUpdated: persisted?.lastUpdated, }, messageStatuses, + allTimeStats: { + totalTokensSaved: aggregated.totalTokens, + sessionCount: aggregated.sessionCount, + }, notes, loadedAt: Date.now(), } diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 3264dc77..dc5e5540 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -37,11 +37,17 @@ export interface DcpPersistedSummary { lastUpdated?: string } +export interface DcpAllTimeStats { + totalTokensSaved: number + sessionCount: number +} + export interface DcpContextSnapshot { sessionID?: string breakdown: DcpContextBreakdown persisted: DcpPersistedSummary messageStatuses: DcpMessageStatus[] + allTimeStats: DcpAllTimeStats notes: string[] loadedAt: number } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 6d4b8f3d..ff44b359 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -20,6 +20,10 @@ const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5 const REFRESH_DEBOUNCE_MS = 100 const compactTokenCount = (value: number): string => { + if (value >= 1_000_000) { + const m = (value / 1_000_000).toFixed(2) + return `${m}M` + } if (value >= 100_000) return `${Math.round(value / 1000)}K` if (value >= 1_000) { const k = (value / 1000).toFixed(1) @@ -458,7 +462,7 @@ const SidebarContext = (props: { /> {fallbackNote()} ) : null} + + {snapshot().allTimeStats.sessionCount > 0 && ( + + + All Time + + + + + )} ) } From 2cc2431619d7bee58c7c2e8bdd527a7619eb686f Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 18:54:17 -0400 Subject: [PATCH 12/40] fix(tui): remove ctrl+h keybind from panel --- tui/commands.ts | 2 +- tui/routes/panel.tsx | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tui/commands.ts b/tui/commands.ts index af6a792d..1098de57 100644 --- a/tui/commands.ts +++ b/tui/commands.ts @@ -3,7 +3,7 @@ import { openPanel } from "./shared/navigation" import type { DcpRouteNames, DcpTuiConfig } from "./shared/types" export const registerCommands = (api: TuiApi, config: DcpTuiConfig, names: DcpRouteNames) => { - const keys = api.keybind?.create({ close: "escape,ctrl+h" }) + const keys = api.keybind?.create({ close: "escape" }) api.command.register(() => [ { title: `${config.label} panel`, diff --git a/tui/routes/panel.tsx b/tui/routes/panel.tsx index 64f7b2f7..f8ae7b57 100644 --- a/tui/routes/panel.tsx +++ b/tui/routes/panel.tsx @@ -18,14 +18,12 @@ const PanelScreen = (props: { const palette = createMemo(() => getPalette(props.api.theme.current as Record)) const sessionID = () => getSessionIDFromParams(props.params) const source = () => getRouteSource(props.params) - const keys = props.api.keybind?.create({ close: "escape,ctrl+h" }) + const keys = props.api.keybind?.create({ close: "escape" }) useKeyboard((evt) => { if (props.api.route.current.name !== props.names.routes.panel) return if (props.api.ui?.dialog?.open) return - const matched = keys - ? keys.match("close", evt) - : evt.name === "escape" || (evt.ctrl && evt.name === "h") + const matched = keys ? keys.match("close", evt) : evt.name === "escape" if (!matched) return evt.preventDefault() evt.stopPropagation() From 420d45f92ef776299301d1b8b4af8c0f47762edd Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Tue, 10 Mar 2026 21:26:34 -0400 Subject: [PATCH 13/40] feat(tui): load tui config from dcp.jsonc Unify TUI behavior with file-based plugin config and ship the TUI entrypoint so sidebar updates actually reach users. --- dcp.schema.json | 21 + index.ts | 8 +- lib/config.ts | 153 +- package-lock.json | 2751 +++++++++++++++++++++++++++- package.json | 6 +- tsconfig.json | 3 +- tui/commands.ts | 8 +- tui/components/screen.tsx | 4 +- tui/components/section.tsx | 4 +- tui/data/context.ts | 64 +- tui/index.tsx | 57 +- tui/routes/panel.tsx | 15 +- tui/shared/config.ts | 20 - tui/shared/names.ts | 24 +- tui/shared/navigation.ts | 3 +- tui/shared/types.ts | 25 +- tui/slots/sidebar-top.tsx | 193 +- tui/types/opencode-plugin-tui.d.ts | 73 + 18 files changed, 3014 insertions(+), 418 deletions(-) delete mode 100644 tui/shared/config.ts create mode 100644 tui/types/opencode-plugin-tui.d.ts diff --git a/dcp.schema.json b/dcp.schema.json index 58495e64..0346231d 100644 --- a/dcp.schema.json +++ b/dcp.schema.json @@ -94,6 +94,27 @@ } } }, + "tui": { + "type": "object", + "description": "Configuration for the DCP TUI integration", + "additionalProperties": false, + "properties": { + "sidebar": { + "type": "boolean", + "default": true, + "description": "Show the DCP sidebar widget in the TUI" + }, + "debug": { + "type": "boolean", + "default": false, + "description": "Enable debug/error logging for the DCP TUI" + } + }, + "default": { + "sidebar": true, + "debug": false + } + }, "experimental": { "type": "object", "description": "Experimental settings that may change in future releases", diff --git a/index.ts b/index.ts index 8bcd8e34..eda40940 100644 --- a/index.ts +++ b/index.ts @@ -9,6 +9,7 @@ import { import { Logger } from "./lib/logger" import { createSessionState } from "./lib/state" import { PromptStore } from "./lib/prompts/store" +import tuiPlugin from "./tui/index" import { createChatMessageHandler, createChatMessageTransformHandler, @@ -18,7 +19,7 @@ import { } from "./lib/hooks" import { configureClientAuth, isSecureMode } from "./lib/auth" -const plugin: Plugin = (async (ctx) => { +const server: Plugin = (async (ctx) => { const config = getConfig(ctx) if (!config.enabled) { @@ -132,4 +133,7 @@ const plugin: Plugin = (async (ctx) => { } }) satisfies Plugin -export default plugin +export default { + server, + ...tuiPlugin, +} diff --git a/lib/config.ts b/lib/config.ts index d488f8dc..046ce2cb 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -49,6 +49,11 @@ export interface TurnProtection { turns: number } +export interface TuiConfig { + sidebar: boolean + debug: boolean +} + export interface ExperimentalConfig { allowSubAgents: boolean customPrompts: boolean @@ -62,6 +67,7 @@ export interface PluginConfig { commands: Commands manualMode: ManualModeConfig turnProtection: TurnProtection + tui: TuiConfig experimental: ExperimentalConfig protectedFilePatterns: string[] compress: CompressConfig @@ -98,6 +104,9 @@ export const VALID_CONFIG_KEYS = new Set([ "turnProtection", "turnProtection.enabled", "turnProtection.turns", + "tui", + "tui.sidebar", + "tui.debug", "experimental", "experimental.allowSubAgents", "experimental.customPrompts", @@ -161,6 +170,13 @@ interface ValidationError { actual: string } +type ConfigWarningNotifier = (title: string, message: string) => void + +interface ConfigWarningCallbacks { + onParseWarning?: (title: string, message: string) => void + onConfigWarning?: (configPath: string, data: Record, isProject: boolean) => void +} + export function validateConfigTypes(config: Record): ValidationError[] { const errors: ValidationError[] = [] @@ -278,6 +294,32 @@ export function validateConfigTypes(config: Record): ValidationErro } } + const tui = config.tui + if (tui !== undefined) { + if (typeof tui !== "object" || tui === null || Array.isArray(tui)) { + errors.push({ + key: "tui", + expected: "object", + actual: typeof tui, + }) + } else { + if (tui.sidebar !== undefined && typeof tui.sidebar !== "boolean") { + errors.push({ + key: "tui.sidebar", + expected: "boolean", + actual: typeof tui.sidebar, + }) + } + if (tui.debug !== undefined && typeof tui.debug !== "boolean") { + errors.push({ + key: "tui.debug", + expected: "boolean", + actual: typeof tui.debug, + }) + } + } + } + const commands = config.commands if (commands !== undefined) { if (typeof commands !== "object" || commands === null || Array.isArray(commands)) { @@ -592,8 +634,21 @@ export function validateConfigTypes(config: Record): ValidationErro return errors } +function scheduleConfigWarning( + notify: ConfigWarningNotifier | undefined, + title: string, + message: string, +): void { + setTimeout(() => { + if (!notify) return + try { + notify(title, message) + } catch {} + }, 7000) +} + function showConfigWarnings( - ctx: PluginInput, + notify: ConfigWarningNotifier | undefined, configPath: string, configData: Record, isProject: boolean, @@ -623,18 +678,11 @@ function showConfigWarnings( } } - setTimeout(() => { - try { - ctx.client.tui.showToast({ - body: { - title: `DCP: ${configType} warning`, - message: `${configPath}\n${messages.join("\n")}`, - variant: "warning", - duration: 7000, - }, - }) - } catch {} - }, 7000) + scheduleConfigWarning( + notify, + `DCP: ${configType} warning`, + `${configPath}\n${messages.join("\n")}`, + ) } const defaultConfig: PluginConfig = { @@ -650,6 +698,10 @@ const defaultConfig: PluginConfig = { enabled: false, automaticStrategies: true, }, + tui: { + sidebar: true, + debug: false, + }, turnProtection: { enabled: false, turns: 4, @@ -707,7 +759,7 @@ function findOpencodeDir(startDir: string): string | null { return null } -function getConfigPaths(ctx?: PluginInput): { +function getConfigPaths(directory?: string): { global: string | null configDir: string | null project: string | null @@ -731,8 +783,8 @@ function getConfigPaths(ctx?: PluginInput): { } let project: string | null = null - if (ctx?.directory) { - const opencodeDir = findOpencodeDir(ctx.directory) + if (directory) { + const opencodeDir = findOpencodeDir(directory) if (opencodeDir) { const projectJsonc = join(opencodeDir, "dcp.jsonc") const projectJson = join(opencodeDir, "dcp.json") @@ -865,6 +917,18 @@ function mergeManualMode( } } +function mergeTui( + base: PluginConfig["tui"], + override?: Partial, +): PluginConfig["tui"] { + if (override === undefined) return base + + return { + sidebar: override.sidebar ?? base.sidebar, + debug: override.debug ?? base.debug, + } +} + function mergeExperimental( base: PluginConfig["experimental"], override?: Partial, @@ -888,6 +952,7 @@ function deepCloneConfig(config: PluginConfig): PluginConfig { enabled: config.manualMode.enabled, automaticStrategies: config.manualMode.automaticStrategies, }, + tui: { ...config.tui }, turnProtection: { ...config.turnProtection }, experimental: { ...config.experimental }, protectedFilePatterns: [...config.protectedFilePatterns], @@ -916,6 +981,7 @@ function mergeLayer(config: PluginConfig, data: Record): PluginConf debug: data.debug ?? config.debug, pruneNotification: data.pruneNotification ?? config.pruneNotification, pruneNotificationType: data.pruneNotificationType ?? config.pruneNotificationType, + tui: mergeTui(config.tui, data.tui as any), commands: mergeCommands(config.commands, data.commands as any), manualMode: mergeManualMode(config.manualMode, data.manualMode as any), turnProtection: { @@ -931,24 +997,21 @@ function mergeLayer(config: PluginConfig, data: Record): PluginConf } } -function scheduleParseWarning(ctx: PluginInput, title: string, message: string): void { - setTimeout(() => { - try { - ctx.client.tui.showToast({ - body: { - title, - message, - variant: "warning", - duration: 7000, - }, - }) - } catch {} - }, 7000) +function createConfigWarningCallbacks( + notify?: ConfigWarningNotifier, +): ConfigWarningCallbacks | undefined { + if (!notify) return undefined + + return { + onParseWarning: (title, message) => scheduleConfigWarning(notify, title, message), + onConfigWarning: (configPath, data, isProject) => + showConfigWarnings(notify, configPath, data, isProject), + } } -export function getConfig(ctx: PluginInput): PluginConfig { +function loadMergedConfig(directory?: string, callbacks?: ConfigWarningCallbacks): PluginConfig { let config = deepCloneConfig(defaultConfig) - const configPaths = getConfigPaths(ctx) + const configPaths = getConfigPaths(directory) if (!configPaths.global) { createDefaultConfig() @@ -967,8 +1030,7 @@ export function getConfig(ctx: PluginInput): PluginConfig { const result = loadConfigFile(layer.path) if (result.parseError) { - scheduleParseWarning( - ctx, + callbacks?.onParseWarning?.( `DCP: Invalid ${layer.name}`, `${layer.path}\n${result.parseError}\nUsing previous/default values`, ) @@ -979,9 +1041,32 @@ export function getConfig(ctx: PluginInput): PluginConfig { continue } - showConfigWarnings(ctx, layer.path, result.data, layer.isProject) + callbacks?.onConfigWarning?.(layer.path, result.data, layer.isProject) config = mergeLayer(config, result.data) } return config } + +export function getConfigForDirectory( + directory?: string, + notify?: ConfigWarningNotifier, +): PluginConfig { + return loadMergedConfig(directory, createConfigWarningCallbacks(notify)) +} + +export function getConfig(ctx: PluginInput): PluginConfig { + return loadMergedConfig( + ctx.directory, + createConfigWarningCallbacks((title, message) => { + ctx.client.tui.showToast({ + body: { + title, + message, + variant: "warning", + duration: 7000, + }, + }) + }), + ) +} diff --git a/package-lock.json b/package-lock.json index 5601ff81..8e9bd0b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,13 +17,31 @@ }, "devDependencies": { "@opencode-ai/plugin": "^1.3.2", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^6.0.2" }, "peerDependencies": { - "@opencode-ai/plugin": ">=0.13.7" + "@opencode-ai/plugin": ">=1.2.0", + "@opentui/core": ">=0.1.87", + "@opentui/solid": ">=0.0.0-20260307" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@anthropic-ai/tokenizer": { @@ -51,6 +69,447 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier2d-simd-compat": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@dimforge/rapier2d-simd-compat/-/rapier2d-simd-compat-0.17.3.tgz", + "integrity": "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", @@ -493,56 +952,1234 @@ "node": ">=18" } }, - "node_modules/@opencode-ai/plugin": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.2.tgz", - "integrity": "sha512-eT0ZovMCOQlfTdAnfbEWgW343mJ9SHgEVfdiOSX1NMIVXac6hxE2xwUsRVTV3wLvfA6dKZhN800f8wLUEyPlyg==", + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", "dev": true, "license": "MIT", "dependencies": { - "@opencode-ai/sdk": "1.3.2", - "zod": "4.1.8" + "@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.0.0", + "mime": "3" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@opencode-ai/plugin/node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@opencode-ai/sdk": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.2.tgz", - "integrity": "sha512-u7sXVKn0kyAA5vVVHuHQfq3+3UGWOU1Sh6d/e+aS4zO8AwriTSWNQ9r8Qy5yxBH+PoeOGl5WIVdp+s2Ea2zuAg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" + "engines": { + "node": ">=18" } }, - "node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" }, "engines": { "node": ">=18" - }, + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "dev": true, + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "dev": true, + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "dev": true, + "license": "MIT", + "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.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "dev": true, + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "dev": true, + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.2.tgz", + "integrity": "sha512-eT0ZovMCOQlfTdAnfbEWgW343mJ9SHgEVfdiOSX1NMIVXac6hxE2xwUsRVTV3wLvfA6dKZhN800f8wLUEyPlyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opencode-ai/sdk": "1.3.2", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/plugin/node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.2.tgz", + "integrity": "sha512-u7sXVKn0kyAA5vVVHuHQfq3+3UGWOU1Sh6d/e+aS4zO8AwriTSWNQ9r8Qy5yxBH+PoeOGl5WIVdp+s2Ea2zuAg==", + "license": "MIT" + }, + "node_modules/@opentui/core": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-e/n7hCtpOzS57X9llODu0SUXCQBWSxHQeTA0iuL7j0nhSFgM6KpL8kJ7VQBU1EEn33pytA0udbfKSJ6sqWmEJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.1.2", + "diff": "8.0.2", + "jimp": "1.6.0", + "marked": "17.0.1", + "yoga-layout": "3.2.1" + }, + "optionalDependencies": { + "@dimforge/rapier2d-simd-compat": "^0.17.3", + "@opentui/core-darwin-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-darwin-x64": "0.0.0-20260307-536c401b", + "@opentui/core-linux-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-linux-x64": "0.0.0-20260307-536c401b", + "@opentui/core-win32-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-win32-x64": "0.0.0-20260307-536c401b", + "bun-webgpu": "0.1.5", + "planck": "^1.4.2", + "three": "0.177.0" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-y46MUgcjkIqC/IBxErchM51KmLARxudrKqr09Gyy25ry+GUE8gzaEIx6EeMAUnWDWvetMacKgEaNCjtdkfGgDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-darwin-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-USf14JkFaCyKvn9FfLn6AZv14o5ED7uHBNq4kCmggD28HmqHsklDhGNyDnswUggCworJ6xz7jICZTiKKrSwRbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-fzNf0Mv7OjNktJFg17WsvdDD5Ej12eSwPVMProlQFbklC8qCEsZfLJKYq9ExYLRoxHX7wFm9Eq6L7hVaGcn2sA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-+80TgK5ZhdJvM2+fiCbeCJvXk9De3oNB42wcCtGcwt3x1wyPYAbWIetw6dIGqXIbica/El+7+6Y2DMV06PUUug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-win32-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-SBeHYwNpWJlHxMX6+aO8KsatWpMMxOs+LpFA7M2PTV0g81WUHPlxm6kHi6UHpjwYuslvtcYKgUL0IyQs1jbdNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-QIU/s6NrXJLRlTyLJZ/41E3MhVGGZazPrwv6MnMx7LOE/uBQo4OGjcRdsIIkhXYIqNRUIH/Yfd5Hyf6twJpBBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core/node_modules/bun-ffi-structs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.1.2.tgz", + "integrity": "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, + "node_modules/@opentui/core/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@opentui/solid": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-wfItFCVBsP2iWvFgj2/lVRN7/O7R5eu9NReY4Wl34Z+c9d7P6FVSa1xOriziTPysukW1OhFe8MNN7MIaggYdHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.0.0-20260307-536c401b", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.9", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.9" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.6.tgz", + "integrity": "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", + "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", + "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.8" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.11.tgz", + "integrity": "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bun-webgpu": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bun-webgpu/-/bun-webgpu-0.1.5.tgz", + "integrity": "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@webgpu/types": "^0.1.60" + }, + "optionalDependencies": { + "bun-webgpu-darwin-arm64": "^0.1.5", + "bun-webgpu-darwin-x64": "^0.1.5", + "bun-webgpu-linux-x64": "^0.1.5", + "bun-webgpu-win32-x64": "^0.1.5" + } + }, + "node_modules/bun-webgpu-darwin-arm64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-arm64/-/bun-webgpu-darwin-arm64-0.1.6.tgz", + "integrity": "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-darwin-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-x64/-/bun-webgpu-darwin-x64-0.1.6.tgz", + "integrity": "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-linux-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-linux-x64/-/bun-webgpu-linux-x64-0.1.6.tgz", + "integrity": "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/bun-webgpu-win32-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-win32-x64/-/bun-webgpu-win32-x64-0.1.6.tgz", + "integrity": "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.328", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", + "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", @@ -572,62 +2209,676 @@ "@esbuild/win32-x64": "0.27.0" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", + "dev": true + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzball": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fuzzball/-/fuzzball-2.2.3.tgz", + "integrity": "sha512-sQDb3kjI7auA4YyE1YgEW85MTparcSgRgcCweUK06Cn0niY5lN+uhFiRUZKN4MQVGGiHxlbrYCA4nL1QjOXBLQ==", + "license": "MIT", + "dependencies": { + "heap": ">=0.2.0", + "lodash": "^4.17.21", + "setimmediate": "^1.0.5" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "license": "MIT" + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/fuzzball": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fuzzball/-/fuzzball-2.2.3.tgz", - "integrity": "sha512-sQDb3kjI7auA4YyE1YgEW85MTparcSgRgcCweUK06Cn0niY5lN+uhFiRUZKN4MQVGGiHxlbrYCA4nL1QjOXBLQ==", + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "heap": ">=0.2.0", - "lodash": "^4.17.21", - "setimmediate": "^1.0.5" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", "dev": true, "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "license": "MIT" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/planck": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/planck/-/planck-1.4.3.tgz", + "integrity": "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=24.0" + }, + "peerDependencies": { + "stage-js": "^1.0.0-alpha.12" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } }, "node_modules/prettier": { "version": "3.8.1", @@ -645,6 +2896,78 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -655,18 +2978,200 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/s-js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", + "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "license": "MIT" }, + "node_modules/simple-xml-to-json": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.4.tgz", + "integrity": "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/solid-js": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", + "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/stage-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stage-js/-/stage-js-1.0.1.tgz", + "integrity": "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/three": { + "version": "0.177.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", + "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/tiktoken": { "version": "1.0.22", "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.22.tgz", "integrity": "sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==", "license": "MIT" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -708,6 +3213,108 @@ "dev": true, "license": "MIT" }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", + "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/emscripten": "^1.40.0" + }, + "peerDependenciesMeta": { + "@types/emscripten": { + "optional": true + } + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index 43e6cb8a..bf5ff6e1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,9 @@ "author": "tarquinen", "license": "AGPL-3.0-or-later", "peerDependencies": { - "@opencode-ai/plugin": ">=0.13.7" + "@opencode-ai/plugin": ">=1.2.0", + "@opentui/core": ">=0.1.87", + "@opentui/solid": ">=0.0.0-20260307" }, "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", @@ -49,6 +51,8 @@ }, "devDependencies": { "@opencode-ai/plugin": "^1.3.2", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", "tsx": "^4.21.0", diff --git a/tsconfig.json b/tsconfig.json index c20d8a54..3eda4128 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "target": "ES2022", "module": "ESNext", "lib": ["ES2023"], + "jsx": "react-jsx", "moduleResolution": "bundler", "resolveJsonModule": true, "allowJs": true, @@ -19,6 +20,6 @@ "sourceMap": true, "types": ["node"] }, - "include": ["index.ts", "lib/**/*"], + "include": ["index.ts", "lib/**/*", "tui/**/*"], "exclude": ["node_modules", "dist", "logs"] } diff --git a/tui/commands.ts b/tui/commands.ts index 1098de57..ec9af211 100644 --- a/tui/commands.ts +++ b/tui/commands.ts @@ -1,15 +1,15 @@ import type { TuiApi } from "@opencode-ai/plugin/tui" import { openPanel } from "./shared/navigation" -import type { DcpRouteNames, DcpTuiConfig } from "./shared/types" +import { LABEL, type DcpRouteNames } from "./shared/names" -export const registerCommands = (api: TuiApi, config: DcpTuiConfig, names: DcpRouteNames) => { +export const registerCommands = (api: TuiApi, names: DcpRouteNames) => { const keys = api.keybind?.create({ close: "escape" }) api.command.register(() => [ { - title: `${config.label} panel`, + title: `${LABEL} panel`, value: names.commands.panel, description: "Open the DCP placeholder panel", - category: config.label, + category: LABEL, ...(keys ? { keybind: keys.get("close") } : {}), slash: { name: "dcp-panel", diff --git a/tui/components/screen.tsx b/tui/components/screen.tsx index 22f48801..d8fbbfca 100644 --- a/tui/components/screen.tsx +++ b/tui/components/screen.tsx @@ -2,6 +2,8 @@ import type { JSX } from "solid-js" import type { DcpPalette } from "../shared/theme" +const SINGLE_BORDER = { type: "single" } as any + export const Screen = (props: { palette: DcpPalette title: string @@ -18,7 +20,7 @@ export const Screen = (props: { gap={1} padding={1} backgroundColor={props.palette.base} - border={{ type: "single" }} + border={SINGLE_BORDER} borderColor={props.palette.accent} > diff --git a/tui/components/section.tsx b/tui/components/section.tsx index 6c95e95a..1508123f 100644 --- a/tui/components/section.tsx +++ b/tui/components/section.tsx @@ -2,6 +2,8 @@ import type { JSX } from "solid-js" import type { DcpPalette } from "../shared/theme" +const SINGLE_BORDER = { type: "single" } as any + export const Section = (props: { palette: DcpPalette title: string @@ -15,7 +17,7 @@ export const Section = (props: { gap={1} padding={1} backgroundColor={props.palette.base} - border={{ type: "single" }} + border={SINGLE_BORDER} borderColor={props.palette.border} > diff --git a/tui/data/context.ts b/tui/data/context.ts index 4c55dcd6..914eb088 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -14,24 +14,10 @@ import { loadAllSessionStats } from "../../lib/state/persistence" import { analyzeTokens, emptyBreakdown } from "../../lib/analysis/tokens" import type { DcpContextSnapshot, DcpTuiClient } from "../shared/types" -let logger = new Logger(false, "TUI") const snapshotCache = new Map() const inflightSnapshots = new Map>() const CACHE_TTL_MS = 5000 -const summarizeSnapshot = (snapshot: DcpContextSnapshot) => ({ - sessionID: snapshot.sessionID, - totalTokens: snapshot.breakdown.total, - messageCount: snapshot.breakdown.messageCount, - prunedTokens: snapshot.breakdown.prunedTokens, - activeBlockCount: snapshot.persisted.activeBlockCount, - loadedAt: snapshot.loadedAt, -}) - -export const setContextLogger = (nextLogger: Logger) => { - logger = nextLogger -} - export const createPlaceholderContextSnapshot = ( sessionID?: string, notes: string[] = [], @@ -53,6 +39,7 @@ export const createPlaceholderContextSnapshot = ( const buildState = async ( sessionID: string, messages: WithParts[], + logger: Logger, ): Promise<{ state: SessionState; persisted: Awaited> }> => { const state = createSessionState() const persisted = await loadSessionState(sessionID, logger) @@ -72,26 +59,21 @@ const buildState = async ( const loadContextSnapshot = async ( client: DcpTuiClient, + logger: Logger, sessionID?: string, ): Promise => { if (!sessionID) { - void logger.debug("Context snapshot requested without session") return createPlaceholderContextSnapshot(undefined, [ "Open this panel from a session to inspect DCP context.", ]) } - void logger.debug("Loading context snapshot", { sessionID }) const messagesResult = await client.session.messages({ sessionID }) const messages = Array.isArray(messagesResult.data) ? (messagesResult.data as WithParts[]) : ([] as WithParts[]) - void logger.debug("Fetched session messages for context snapshot", { - sessionID, - messageCount: messages.length, - }) - const { state, persisted } = await buildState(sessionID, messages) + const { state, persisted } = await buildState(sessionID, messages, logger) const [{ breakdown, messageStatuses }, aggregated] = await Promise.all([ Promise.resolve(analyzeTokens(state, messages)), loadAllSessionStats(logger), @@ -115,7 +97,7 @@ const loadContextSnapshot = async ( notes.push("This session does not have any messages yet.") } - const snapshot = { + return { sessionID, breakdown, persisted: { @@ -133,13 +115,6 @@ const loadContextSnapshot = async ( notes, loadedAt: Date.now(), } - - void logger.debug("Loaded context snapshot", { - ...summarizeSnapshot(snapshot), - persisted: !!persisted, - }) - - return snapshot } export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | undefined => { @@ -149,23 +124,20 @@ export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | un export const invalidateContextSnapshot = (sessionID?: string) => { if (!sessionID) { - void logger.debug("Invalidating all context snapshots") snapshotCache.clear() inflightSnapshots.clear() return } - - void logger.debug("Invalidating context snapshot", { sessionID }) snapshotCache.delete(sessionID) inflightSnapshots.delete(sessionID) } export const loadContextSnapshotCached = async ( client: DcpTuiClient, + logger: Logger, sessionID?: string, ): Promise => { if (!sessionID) { - void logger.debug("Cached context snapshot requested without session") return createPlaceholderContextSnapshot(undefined, [ "Open this panel from a session to inspect DCP context.", ]) @@ -173,44 +145,28 @@ export const loadContextSnapshotCached = async ( const cached = snapshotCache.get(sessionID) if (cached && Date.now() - cached.loadedAt < CACHE_TTL_MS) { - void logger.debug("Context snapshot cache hit", { - sessionID, - cacheAgeMs: Date.now() - cached.loadedAt, - }) return cached } - if (cached) { - void logger.debug("Context snapshot cache stale", { - sessionID, - cacheAgeMs: Date.now() - cached.loadedAt, - }) - } else { - void logger.debug("Context snapshot cache miss", { sessionID }) - } - const inflight = inflightSnapshots.get(sessionID) if (inflight) { - void logger.debug("Reusing inflight context snapshot request", { sessionID }) return inflight } - const request = loadContextSnapshot(client, sessionID) + const request = loadContextSnapshot(client, logger, sessionID) .then((snapshot) => { snapshotCache.set(sessionID, snapshot) - void logger.debug("Stored context snapshot in cache", summarizeSnapshot(snapshot)) return snapshot }) - .catch((cause) => { - void logger.error("Context snapshot request failed", { + .catch((error) => { + logger.error("Failed to load TUI context snapshot", { sessionID, - error: cause instanceof Error ? cause.message : String(cause), + error: error instanceof Error ? error.message : String(error), }) - throw cause + throw error }) .finally(() => { inflightSnapshots.delete(sessionID) - void logger.debug("Cleared inflight context snapshot request", { sessionID }) }) inflightSnapshots.set(sessionID, request) diff --git a/tui/index.tsx b/tui/index.tsx index 24b22707..02a8e8a7 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -1,47 +1,46 @@ /** @jsxImportSource @opentui/solid */ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import { getConfigForDirectory } from "../lib/config" import { Logger } from "../lib/logger" import { registerCommands } from "./commands" -import { setContextLogger } from "./data/context" import { createPanelRoute } from "./routes/panel" import { createSidebarTopSlot } from "./slots/sidebar-top" -import { readConfig } from "./shared/config" -import { createNames } from "./shared/names" +import { NAMES } from "./shared/names" -const tui = async (input: TuiPluginInput, options?: Record) => { - if (options?.enabled === false) return - - const config = readConfig(options) - const names = createNames(config) - const logger = new Logger(config.debug, "TUI") - - setContextLogger(logger) - void logger.info("DCP TUI initialized", { - debug: config.debug, - label: config.label, - route: config.route, +const tui = async (input: TuiPluginInput) => { + const config = getConfigForDirectory(process.cwd(), (title, message) => { + input.api.ui.toast({ + title, + message, + variant: "warning", + duration: 7000, + }) }) + if (!config.enabled) return + + const logger = new Logger(config.tui.debug, "tui") input.api.route.register([ createPanelRoute({ api: input.api, - config, - names, + names: NAMES, }), ]) - registerCommands(input.api, config, names) - input.slots.register( - createSidebarTopSlot( - input.api, - input.client, - input.event, - input.renderer, - logger, - config, - names, - ), - ) + registerCommands(input.api, NAMES) + + if (config.tui.sidebar) { + input.slots.register( + createSidebarTopSlot( + input.api, + input.client, + input.event, + input.renderer, + NAMES, + logger, + ), + ) + } } export default { diff --git a/tui/routes/panel.tsx b/tui/routes/panel.tsx index f8ae7b57..e2d34572 100644 --- a/tui/routes/panel.tsx +++ b/tui/routes/panel.tsx @@ -7,11 +7,10 @@ import { Screen } from "../components/screen" import { Section } from "../components/section" import { getRouteSource, getSessionIDFromParams, goBack } from "../shared/navigation" import { getPalette } from "../shared/theme" -import type { DcpRouteNames, DcpTuiConfig } from "../shared/types" +import { LABEL, type DcpRouteNames } from "../shared/names" const PanelScreen = (props: { api: TuiApi - config: DcpTuiConfig names: DcpRouteNames params?: Record }) => { @@ -33,7 +32,7 @@ const PanelScreen = (props: { return ( @@ -68,18 +67,10 @@ const PanelScreen = (props: { export const createPanelRoute = (input: { api: TuiApi - config: DcpTuiConfig names: DcpRouteNames }): TuiRouteDefinition => { return { name: input.names.routes.panel, - render: ({ params }) => ( - - ), + render: ({ params }) => , } } diff --git a/tui/shared/config.ts b/tui/shared/config.ts deleted file mode 100644 index 7db8561a..00000000 --- a/tui/shared/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { DcpTuiConfig } from "./types" - -const pick = (value: unknown, fallback: string) => { - if (typeof value !== "string") return fallback - if (!value.trim()) return fallback - return value -} - -const pickBoolean = (value: unknown, fallback: boolean) => { - if (typeof value !== "boolean") return fallback - return value -} - -export const readConfig = (options: Record | undefined): DcpTuiConfig => { - return { - debug: pickBoolean(options?.debug, false), - label: pick(options?.label, "DCP"), - route: pick(options?.route, "dcp"), - } -} diff --git a/tui/shared/names.ts b/tui/shared/names.ts index 84cb8102..2457e6a0 100644 --- a/tui/shared/names.ts +++ b/tui/shared/names.ts @@ -1,13 +1,13 @@ -import type { DcpRouteNames, DcpTuiConfig } from "./types" +export const LABEL = "DCP" -export const createNames = (config: DcpTuiConfig): DcpRouteNames => { - return { - slot: `${config.route}.sidebar`, - routes: { - panel: `${config.route}.panel`, - }, - commands: { - panel: `plugin.${config.route}.panel`, - }, - } -} +export const NAMES = { + slot: "dcp.sidebar", + routes: { + panel: "dcp.panel", + }, + commands: { + panel: "plugin.dcp.panel", + }, +} as const + +export type DcpRouteNames = typeof NAMES diff --git a/tui/shared/navigation.ts b/tui/shared/navigation.ts index 2c14d727..6160294e 100644 --- a/tui/shared/navigation.ts +++ b/tui/shared/navigation.ts @@ -1,5 +1,6 @@ import type { TuiApi } from "@opencode-ai/plugin/tui" -import type { DcpRouteNames, DcpRouteParams, DcpRouteSource } from "./types" +import type { DcpRouteNames } from "./names" +import type { DcpRouteParams, DcpRouteSource } from "./types" export const getSessionIDFromParams = (params?: Record) => { if (typeof params?.session_id === "string") return params.session_id diff --git a/tui/shared/types.ts b/tui/shared/types.ts index dc5e5540..2a92cec5 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -1,33 +1,18 @@ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { + MessageStatus as DcpMessageStatus, + TokenBreakdown as DcpContextBreakdown, +} from "../../lib/analysis/tokens" export type DcpTuiClient = TuiPluginInput["client"] export type DcpRouteSource = "sidebar" | "command" -export interface DcpTuiConfig { - debug: boolean - label: string - route: string -} - -export interface DcpRouteNames { - slot: string - routes: { - panel: string - } - commands: { - panel: string - } -} - export interface DcpRouteParams { session_id?: string source?: string } -export { - type TokenBreakdown as DcpContextBreakdown, - type MessageStatus as DcpMessageStatus, -} from "../../lib/analysis/tokens" +export type { DcpContextBreakdown, DcpMessageStatus } export interface DcpPersistedSummary { available: boolean diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index ff44b359..e8754e7b 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -11,9 +11,12 @@ import { } from "../data/context" import { openPanel } from "../shared/navigation" import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared/theme" -import type { DcpMessageStatus, DcpRouteNames, DcpTuiClient, DcpTuiConfig } from "../shared/types" +import { LABEL, type DcpRouteNames } from "../shared/names" +import type { DcpMessageStatus, DcpTuiClient } from "../shared/types" const BAR_WIDTH = 12 +const SINGLE_BORDER = { type: "single" } as any +const DIM_TEXT = { dim: true } as any // Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5) const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5 @@ -129,11 +132,10 @@ const SidebarContext = (props: { client: DcpTuiClient event: TuiPluginInput["event"] renderer: TuiPluginInput["renderer"] - logger: Logger - config: DcpTuiConfig names: DcpRouteNames palette: DcpPalette sessionID: () => string + logger: Logger }) => { const initialSnapshot = peekContextSnapshot(props.sessionID()) const [snapshot, setSnapshot] = createSignal( @@ -144,29 +146,15 @@ const SidebarContext = (props: { let requestVersion = 0 let renderTimeout: ReturnType | undefined - const requestRender = (reason: string, data?: Record) => { - const activeSessionID = untrack(() => props.sessionID()) - void props.logger.debug("Sidebar requested renderer refresh", { - activeSessionID, - reason, - ...data, - }) + const requestRender = () => { if (renderTimeout) clearTimeout(renderTimeout) renderTimeout = setTimeout(() => { renderTimeout = undefined try { - void props.logger.debug("Sidebar renderer refresh dispatched", { - activeSessionID, - reason, - ...data, - }) props.renderer.requestRender() - } catch (cause) { - void props.logger.warn("Sidebar renderer refresh failed", { - activeSessionID, - reason, - error: cause instanceof Error ? cause.message : String(cause), - ...data, + } catch (error) { + props.logger.warn("Failed to request TUI render", { + error: error instanceof Error ? error.message : String(error), }) } }, 0) @@ -178,18 +166,8 @@ const SidebarContext = (props: { const refreshSnapshot = async ( sessionID: string, - options?: { invalidate?: boolean; preserveSnapshot?: boolean; reason?: string }, + options?: { invalidate?: boolean; preserveSnapshot?: boolean }, ) => { - const preserveSnapshot = options?.preserveSnapshot ?? false - const reason = options?.reason ?? "unspecified" - - void props.logger.debug("Sidebar refresh start", { - sessionID, - invalidate: !!options?.invalidate, - preserveSnapshot, - reason, - }) - if (options?.invalidate) { invalidateContextSnapshot(sessionID) } @@ -197,84 +175,43 @@ const SidebarContext = (props: { const cached = peekContextSnapshot(sessionID) let silentRefresh = false if (cached) { - void props.logger.debug("Sidebar using cached snapshot before reload", { - sessionID, - loadedAt: cached.loadedAt, - totalTokens: cached.breakdown.total, - }) setSnapshot(cached) setLoading(false) } else { const current = untrack(snapshot) - if (preserveSnapshot && current?.sessionID === sessionID) { + if (options?.preserveSnapshot && current?.sessionID === sessionID) { silentRefresh = true - void props.logger.debug("Sidebar silent refresh, keeping current snapshot", { - sessionID, - }) } else { setSnapshot(createPlaceholderContextSnapshot(sessionID, ["Loading DCP context..."])) setLoading(true) - void props.logger.debug("Sidebar entering loading state", { - sessionID, - hadCurrentSnapshot: !!current, - }) } } setError(undefined) if (!silentRefresh) { - requestRender("refresh-start", { sessionID, reason }) + requestRender() } const currentRequest = ++requestVersion - void props.logger.debug("Sidebar refresh request issued", { - sessionID, - requestVersion: currentRequest, - reason, - }) try { - const value = await loadContextSnapshotCached(props.client, sessionID) + const value = await loadContextSnapshotCached(props.client, props.logger, sessionID) if (currentRequest !== requestVersion || props.sessionID() !== sessionID) { - void props.logger.debug("Sidebar refresh result ignored as stale", { - sessionID, - requestVersion: currentRequest, - activeRequestVersion: requestVersion, - activeSessionID: props.sessionID(), - reason, - }) return } setSnapshot(value) setLoading(false) - void props.logger.debug("Sidebar refresh succeeded", { - sessionID, - requestVersion: currentRequest, - totalTokens: value.breakdown.total, - messageCount: value.breakdown.messageCount, - activeBlockCount: value.persisted.activeBlockCount, - reason, - }) - requestRender("refresh-success", { sessionID, reason, requestVersion: currentRequest }) + requestRender() } catch (cause) { if (currentRequest !== requestVersion || props.sessionID() !== sessionID) { - void props.logger.debug("Sidebar refresh error ignored as stale", { - sessionID, - requestVersion: currentRequest, - activeRequestVersion: requestVersion, - activeSessionID: props.sessionID(), - reason, - }) return } - setError(cause instanceof Error ? cause.message : String(cause)) - setLoading(false) - void props.logger.error("Sidebar refresh failed", { + props.logger.warn("Failed to refresh sidebar snapshot", { sessionID, - requestVersion: currentRequest, error: cause instanceof Error ? cause.message : String(cause), - reason, }) - requestRender("refresh-error", { sessionID, reason, requestVersion: currentRequest }) + setError(cause instanceof Error ? cause.message : String(cause)) + setLoading(false) + requestRender() } } @@ -282,8 +219,7 @@ const SidebarContext = (props: { on( props.sessionID, (sessionID) => { - void props.logger.info("Sidebar active session changed", { sessionID }) - void refreshSnapshot(sessionID, { reason: "session-change" }) + void refreshSnapshot(sessionID) }, { defer: false }, ), @@ -294,32 +230,15 @@ const SidebarContext = (props: { props.sessionID, (sessionID) => { let timeout: ReturnType | undefined - let pendingReason: string | undefined - void props.logger.debug("Sidebar event subscriptions armed", { sessionID }) - - const scheduleRefresh = (reason: string, data?: Record) => { + const scheduleRefresh = () => { if (!sessionID) return if (timeout) clearTimeout(timeout) - pendingReason = reason - void props.logger.debug("Sidebar refresh scheduled", { - sessionID, - debounceMs: REFRESH_DEBOUNCE_MS, - reason, - ...data, - }) timeout = setTimeout(() => { - const flushReason = pendingReason ?? reason - pendingReason = undefined timeout = undefined - void props.logger.debug("Sidebar refresh debounce fired", { - sessionID, - reason: flushReason, - }) void refreshSnapshot(sessionID, { invalidate: true, preserveSnapshot: true, - reason: flushReason, }) }, REFRESH_DEBOUNCE_MS) } @@ -327,80 +246,48 @@ const SidebarContext = (props: { const unsubs = [ props.event.on("message.updated", (event) => { if (event.properties.info.sessionID !== sessionID) return - scheduleRefresh("message.updated", { - eventSessionID: event.properties.info.sessionID, - messageID: event.properties.info.id, - }) + scheduleRefresh() }), props.event.on("message.removed", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("message.removed", { - eventSessionID: event.properties.sessionID, - messageID: event.properties.messageID, - }) + scheduleRefresh() }), props.event.on("message.part.updated", (event) => { if (event.properties.part.sessionID !== sessionID) return - scheduleRefresh("message.part.updated", { - eventSessionID: event.properties.part.sessionID, - messageID: event.properties.part.messageID, - partID: event.properties.part.id, - }) + scheduleRefresh() }), props.event.on("message.part.delta", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("message.part.delta", { - eventSessionID: event.properties.sessionID, - messageID: event.properties.messageID, - partID: event.properties.partID, - field: event.properties.field, - }) + scheduleRefresh() }), props.event.on("message.part.removed", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("message.part.removed", { - eventSessionID: event.properties.sessionID, - messageID: event.properties.messageID, - partID: event.properties.partID, - }) + scheduleRefresh() }), props.event.on("session.updated", (event) => { if (event.properties.info.id !== sessionID) return - scheduleRefresh("session.updated", { - eventSessionID: event.properties.info.id, - }) + scheduleRefresh() }), props.event.on("session.deleted", (event) => { if (event.properties.info.id !== sessionID) return - scheduleRefresh("session.deleted", { - eventSessionID: event.properties.info.id, - }) + scheduleRefresh() }), props.event.on("session.diff", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("session.diff", { - eventSessionID: event.properties.sessionID, - }) + scheduleRefresh() }), props.event.on("session.error", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("session.error", { - eventSessionID: event.properties.sessionID, - error: event.properties.error, - }) + scheduleRefresh() }), props.event.on("session.status", (event) => { if (event.properties.sessionID !== sessionID) return - scheduleRefresh("session.status", { - eventSessionID: event.properties.sessionID, - status: event.properties.status, - }) + scheduleRefresh() }), ] onCleanup(() => { if (timeout) clearTimeout(timeout) - void props.logger.debug("Sidebar event subscriptions cleaned up", { sessionID }) for (const unsub of unsubs) { unsub() } @@ -432,7 +319,7 @@ const SidebarContext = (props: { width="100%" flexDirection="column" backgroundColor={props.palette.surface} - border={{ type: "single" }} + border={SINGLE_BORDER} borderColor={props.palette.accent} paddingTop={1} paddingBottom={1} @@ -444,7 +331,7 @@ const SidebarContext = (props: { - {props.config.label} + {LABEL} click for more @@ -523,7 +410,7 @@ const SidebarContext = (props: { {truncate(t, CONTENT_WIDTH)} ))} {topicOverflow() > 0 ? ( - + ... {topicOverflow()} more topics ) : null} @@ -561,16 +448,15 @@ export const createSidebarTopSlot = ( client: DcpTuiClient, event: TuiPluginInput["event"], renderer: TuiPluginInput["renderer"], - logger: Logger, - config: DcpTuiConfig, names: DcpRouteNames, + logger: Logger, ) => ({ id: names.slot, slots: { - sidebar_top(ctx, value: { session_id: string }) { - // value is a reactive proxy from @opentui/solid splitProps — - // value.session_id updates automatically when the host navigates - // to a different session (no event subscription needed). + sidebar_top( + ctx: { theme: { current: Record } }, + value: { session_id: string }, + ) { const palette = createMemo(() => getPalette(ctx.theme.current as Record), ) @@ -580,11 +466,10 @@ export const createSidebarTopSlot = ( client={client} event={event} renderer={renderer} - logger={logger} - config={config} names={names} palette={palette()} sessionID={() => value.session_id} + logger={logger} /> ) }, diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts new file mode 100644 index 00000000..5df963d7 --- /dev/null +++ b/tui/types/opencode-plugin-tui.d.ts @@ -0,0 +1,73 @@ +declare module "@opencode-ai/plugin/tui" { + export interface TuiCommand { + title: string + value: string + description?: string + category?: string + keybind?: unknown + slash?: { + name: string + } + onSelect?: () => void + } + + export interface TuiRouteDefinition { + name: string + render: (input: { params?: Record }) => Node + } + + export interface TuiKeybindSet { + get: (key: string) => unknown + match: (key: string, evt: unknown) => boolean + } + + export interface TuiApi { + command: { + register: (cb: () => TuiCommand[]) => void + trigger: (value: string) => void + } + route: { + register: (routes: TuiRouteDefinition[]) => () => void + navigate: (name: string, params?: any) => void + current: { + name: string + params?: any + } + } + ui: { + toast: (input: { + title: string + message: string + variant?: string + duration?: number + }) => void + dialog: { + open?: boolean + } + } + keybind?: { + create: ( + defaults: Record, + overrides?: Record, + ) => TuiKeybindSet + } + theme: { + current: unknown + } + } + + export interface TuiPluginInput< + Renderer extends { requestRender: () => void } = { requestRender: () => void }, + Node = unknown, + > { + client: any + event: { + on: (name: string, cb: (event: any) => void) => () => void + } + renderer: Renderer + slots: { + register: (slot: any) => () => void + } + api: TuiApi + } +} From d7c1f31d13478c76e6c20f3f2890c5e3bc0e4ab6 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 11 Mar 2026 22:17:22 -0400 Subject: [PATCH 14/40] refactor(tui): remove panel page and keep only sidebar widget --- tui/commands.ts | 20 --------- tui/components/metric-row.tsx | 20 --------- tui/components/screen.tsx | 38 ------------------ tui/components/section.tsx | 32 --------------- tui/data/context.ts | 8 +--- tui/index.tsx | 20 +-------- tui/routes/panel.tsx | 76 ----------------------------------- tui/shared/names.ts | 6 --- tui/shared/navigation.ts | 47 ---------------------- tui/shared/types.ts | 6 --- tui/slots/sidebar-top.tsx | 10 +---- 11 files changed, 4 insertions(+), 279 deletions(-) delete mode 100644 tui/commands.ts delete mode 100644 tui/components/metric-row.tsx delete mode 100644 tui/components/screen.tsx delete mode 100644 tui/components/section.tsx delete mode 100644 tui/routes/panel.tsx delete mode 100644 tui/shared/navigation.ts diff --git a/tui/commands.ts b/tui/commands.ts deleted file mode 100644 index ec9af211..00000000 --- a/tui/commands.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { TuiApi } from "@opencode-ai/plugin/tui" -import { openPanel } from "./shared/navigation" -import { LABEL, type DcpRouteNames } from "./shared/names" - -export const registerCommands = (api: TuiApi, names: DcpRouteNames) => { - const keys = api.keybind?.create({ close: "escape" }) - api.command.register(() => [ - { - title: `${LABEL} panel`, - value: names.commands.panel, - description: "Open the DCP placeholder panel", - category: LABEL, - ...(keys ? { keybind: keys.get("close") } : {}), - slash: { - name: "dcp-panel", - }, - onSelect: () => openPanel(api, names, "command"), - }, - ]) -} diff --git a/tui/components/metric-row.tsx b/tui/components/metric-row.tsx deleted file mode 100644 index c3b03bf9..00000000 --- a/tui/components/metric-row.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/** @jsxImportSource @opentui/solid */ -import { toneColor, type DcpPalette, type DcpTone } from "../shared/theme" - -const pad = (value: string, width: number) => { - if (value.length >= width) return value - return value.padEnd(width, " ") -} - -export const MetricRow = (props: { - palette: DcpPalette - label: string - value: string - tone?: DcpTone -}) => { - return ( - {`${pad(props.label, 18)} ${props.value}`} - ) -} diff --git a/tui/components/screen.tsx b/tui/components/screen.tsx deleted file mode 100644 index d8fbbfca..00000000 --- a/tui/components/screen.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** @jsxImportSource @opentui/solid */ -import type { JSX } from "solid-js" -import type { DcpPalette } from "../shared/theme" - -const SINGLE_BORDER = { type: "single" } as any - -export const Screen = (props: { - palette: DcpPalette - title: string - subtitle?: string - footer?: string - children?: JSX.Element -}) => { - return ( - - - - - {props.title} - - {props.subtitle && {props.subtitle}} - - - {props.children} - - {props.footer && {props.footer}} - - - ) -} diff --git a/tui/components/section.tsx b/tui/components/section.tsx deleted file mode 100644 index 1508123f..00000000 --- a/tui/components/section.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** @jsxImportSource @opentui/solid */ -import type { JSX } from "solid-js" -import type { DcpPalette } from "../shared/theme" - -const SINGLE_BORDER = { type: "single" } as any - -export const Section = (props: { - palette: DcpPalette - title: string - subtitle?: string - children?: JSX.Element -}) => { - return ( - - - {props.title} - - {props.subtitle && {props.subtitle}} - - {props.children} - - - ) -} diff --git a/tui/data/context.ts b/tui/data/context.ts index 914eb088..a41a84ba 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -63,9 +63,7 @@ const loadContextSnapshot = async ( sessionID?: string, ): Promise => { if (!sessionID) { - return createPlaceholderContextSnapshot(undefined, [ - "Open this panel from a session to inspect DCP context.", - ]) + return createPlaceholderContextSnapshot(undefined, ["No active session."]) } const messagesResult = await client.session.messages({ sessionID }) @@ -138,9 +136,7 @@ export const loadContextSnapshotCached = async ( sessionID?: string, ): Promise => { if (!sessionID) { - return createPlaceholderContextSnapshot(undefined, [ - "Open this panel from a session to inspect DCP context.", - ]) + return createPlaceholderContextSnapshot(undefined, ["No active session."]) } const cached = snapshotCache.get(sessionID) diff --git a/tui/index.tsx b/tui/index.tsx index 02a8e8a7..a28d1121 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -2,8 +2,6 @@ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" import { getConfigForDirectory } from "../lib/config" import { Logger } from "../lib/logger" -import { registerCommands } from "./commands" -import { createPanelRoute } from "./routes/panel" import { createSidebarTopSlot } from "./slots/sidebar-top" import { NAMES } from "./shared/names" @@ -20,25 +18,9 @@ const tui = async (input: TuiPluginInput) => { const logger = new Logger(config.tui.debug, "tui") - input.api.route.register([ - createPanelRoute({ - api: input.api, - names: NAMES, - }), - ]) - - registerCommands(input.api, NAMES) - if (config.tui.sidebar) { input.slots.register( - createSidebarTopSlot( - input.api, - input.client, - input.event, - input.renderer, - NAMES, - logger, - ), + createSidebarTopSlot(input.client, input.event, input.renderer, NAMES, logger), ) } } diff --git a/tui/routes/panel.tsx b/tui/routes/panel.tsx deleted file mode 100644 index e2d34572..00000000 --- a/tui/routes/panel.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/** @jsxImportSource @opentui/solid */ -import { createMemo } from "solid-js" -import { useKeyboard } from "@opentui/solid" -import type { TuiApi, TuiRouteDefinition } from "@opencode-ai/plugin/tui" -import { MetricRow } from "../components/metric-row" -import { Screen } from "../components/screen" -import { Section } from "../components/section" -import { getRouteSource, getSessionIDFromParams, goBack } from "../shared/navigation" -import { getPalette } from "../shared/theme" -import { LABEL, type DcpRouteNames } from "../shared/names" - -const PanelScreen = (props: { - api: TuiApi - names: DcpRouteNames - params?: Record -}) => { - const palette = createMemo(() => getPalette(props.api.theme.current as Record)) - const sessionID = () => getSessionIDFromParams(props.params) - const source = () => getRouteSource(props.params) - const keys = props.api.keybind?.create({ close: "escape" }) - - useKeyboard((evt) => { - if (props.api.route.current.name !== props.names.routes.panel) return - if (props.api.ui?.dialog?.open) return - const matched = keys ? keys.match("close", evt) : evt.name === "escape" - if (!matched) return - evt.preventDefault() - evt.stopPropagation() - goBack(props.api, sessionID()) - }) - - return ( - -
- - Use this page as the home for future DCP-specific TUI work. - - - The live context breakdown now lives directly in the session sidebar. - -
- -
- - - -
- -
- - block explorer - - prune history and diagnostics - - manual DCP actions -
-
- ) -} - -export const createPanelRoute = (input: { - api: TuiApi - names: DcpRouteNames -}): TuiRouteDefinition => { - return { - name: input.names.routes.panel, - render: ({ params }) => , - } -} diff --git a/tui/shared/names.ts b/tui/shared/names.ts index 2457e6a0..a2187087 100644 --- a/tui/shared/names.ts +++ b/tui/shared/names.ts @@ -2,12 +2,6 @@ export const LABEL = "DCP" export const NAMES = { slot: "dcp.sidebar", - routes: { - panel: "dcp.panel", - }, - commands: { - panel: "plugin.dcp.panel", - }, } as const export type DcpRouteNames = typeof NAMES diff --git a/tui/shared/navigation.ts b/tui/shared/navigation.ts deleted file mode 100644 index 6160294e..00000000 --- a/tui/shared/navigation.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { TuiApi } from "@opencode-ai/plugin/tui" -import type { DcpRouteNames } from "./names" -import type { DcpRouteParams, DcpRouteSource } from "./types" - -export const getSessionIDFromParams = (params?: Record) => { - if (typeof params?.session_id === "string") return params.session_id - return undefined -} - -export const getRouteSource = (params?: Record) => { - if (typeof params?.source === "string") return params.source - return "unknown" -} - -export const getCurrentSessionID = (api: TuiApi) => { - const current = api.route.current - if (current.name === "session") return current.params.sessionID - if ("params" in current && current.params && typeof current.params === "object") { - return getSessionIDFromParams(current.params) - } - return undefined -} - -const navigate = (api: TuiApi, routeName: string, source: DcpRouteSource, sessionID?: string) => { - const params: DcpRouteParams = { - source, - session_id: sessionID ?? getCurrentSessionID(api), - } - api.route.navigate(routeName, params) -} - -export const openPanel = ( - api: TuiApi, - names: DcpRouteNames, - source: DcpRouteSource, - sessionID?: string, -) => { - navigate(api, names.routes.panel, source, sessionID) -} - -export const goBack = (api: TuiApi, sessionID?: string) => { - if (sessionID) { - api.route.navigate("session", { sessionID }) - return - } - api.route.navigate("home") -} diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 2a92cec5..bae36262 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -5,12 +5,6 @@ import type { } from "../../lib/analysis/tokens" export type DcpTuiClient = TuiPluginInput["client"] -export type DcpRouteSource = "sidebar" | "command" - -export interface DcpRouteParams { - session_id?: string - source?: string -} export type { DcpContextBreakdown, DcpMessageStatus } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index e8754e7b..9af6fa12 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @opentui/solid */ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" -import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { TuiPluginInput } from "@opencode-ai/plugin/tui" import { Logger } from "../../lib/logger" import { truncate } from "../../lib/ui/utils" import { @@ -9,7 +9,6 @@ import { loadContextSnapshotCached, peekContextSnapshot, } from "../data/context" -import { openPanel } from "../shared/navigation" import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared/theme" import { LABEL, type DcpRouteNames } from "../shared/names" import type { DcpMessageStatus, DcpTuiClient } from "../shared/types" @@ -128,11 +127,9 @@ const SidebarContextBar = (props: { } const SidebarContext = (props: { - api: TuiApi client: DcpTuiClient event: TuiPluginInput["event"] renderer: TuiPluginInput["renderer"] - names: DcpRouteNames palette: DcpPalette sessionID: () => string logger: Logger @@ -325,7 +322,6 @@ const SidebarContext = (props: { paddingBottom={1} paddingLeft={1} paddingRight={1} - onMouseUp={() => openPanel(props.api, props.names, "sidebar", props.sessionID())} > @@ -334,7 +330,6 @@ const SidebarContext = (props: { {LABEL}
- click for more {status().label} @@ -444,7 +439,6 @@ const SidebarContext = (props: { } export const createSidebarTopSlot = ( - api: TuiApi, client: DcpTuiClient, event: TuiPluginInput["event"], renderer: TuiPluginInput["renderer"], @@ -462,11 +456,9 @@ export const createSidebarTopSlot = ( ) return ( value.session_id} logger={logger} From 0d940895234e597d16dad2189a631ebc2dc7d263 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 11 Mar 2026 22:38:54 -0400 Subject: [PATCH 15/40] fix(tui): use flexbox layout for sidebar bars to adapt to scrollbar width --- tui/slots/sidebar-top.tsx | 74 +++++++++++++++------------------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 9af6fa12..334b3300 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -2,7 +2,6 @@ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" import type { TuiPluginInput } from "@opencode-ai/plugin/tui" import { Logger } from "../../lib/logger" -import { truncate } from "../../lib/ui/utils" import { createPlaceholderContextSnapshot, invalidateContextSnapshot, @@ -13,11 +12,8 @@ import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared import { LABEL, type DcpRouteNames } from "../shared/names" import type { DcpMessageStatus, DcpTuiClient } from "../shared/types" -const BAR_WIDTH = 12 const SINGLE_BORDER = { type: "single" } as any const DIM_TEXT = { dim: true } as any -// Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5) -const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5 const REFRESH_DEBOUNCE_MS = 100 @@ -37,37 +33,17 @@ const compactTokenCount = (value: number): string => { return "0" } -const buildBar = (value: number, total: number) => { - if (total <= 0) return " ".repeat(BAR_WIDTH) - const filled = Math.max(0, Math.round((value / total) * BAR_WIDTH)) - return "█".repeat(filled).padEnd(BAR_WIDTH, " ") -} - -const buildMessageBar = ( +const buildMessageRuns = ( statuses: DcpMessageStatus[], - width: number = CONTENT_WIDTH, -): { text: string; status: DcpMessageStatus }[] => { - const ACTIVE = "█" - const PRUNED = "░" - if (statuses.length === 0) return [{ text: PRUNED.repeat(width), status: "pruned" }] - - // Map each bar position to a message status - const bar: DcpMessageStatus[] = new Array(width).fill("active") - for (let m = 0; m < statuses.length; m++) { - const start = Math.floor((m / statuses.length) * width) - const end = Math.floor(((m + 1) / statuses.length) * width) - for (let i = start; i < end; i++) { - bar[i] = statuses[m] - } - } +): { count: number; status: DcpMessageStatus }[] => { + if (statuses.length === 0) return [{ count: 1, status: "pruned" }] - // Group consecutive same-status positions into runs - const runs: { text: string; status: DcpMessageStatus }[] = [] + // Group consecutive same-status messages into runs + const runs: { count: number; status: DcpMessageStatus }[] = [] let runStart = 0 - for (let i = 1; i <= width; i++) { - if (i === width || bar[i] !== bar[runStart]) { - const char = bar[runStart] === "pruned" ? PRUNED : ACTIVE - runs.push({ text: char.repeat(i - runStart), status: bar[runStart] }) + for (let i = 1; i <= statuses.length; i++) { + if (i === statuses.length || statuses[i] !== statuses[runStart]) { + runs.push({ count: i - runStart, status: statuses[runStart] }) runStart = i } } @@ -111,17 +87,24 @@ const SidebarContextBar = (props: { props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%", ) const label = createMemo(() => props.label.padEnd(9, " ")) - const bar = createMemo(() => buildBar(props.value, props.total)) return ( - + {label()} {` ${percent().padStart(4, " ")} |`} - {bar()} - {`| ${compactTokenCount(props.value).padStart(5, " ")}`} + + {props.value > 0 && ( + + )} + {props.total > props.value && } + + + {`| ${compactTokenCount(props.value).padStart(5, " ")}`} + ) } @@ -299,7 +282,7 @@ const SidebarContext = (props: { const topicOverflow = createMemo(() => topicTotal() - topics().length) const fallbackNote = createMemo(() => snapshot().notes[0] ?? "") - const messageBarRuns = createMemo(() => buildMessageBar(snapshot().messageStatuses)) + const messageBarRuns = createMemo(() => buildMessageRuns(snapshot().messageStatuses)) const status = createMemo(() => { if (error() && snapshot().breakdown.total > 0) @@ -351,15 +334,14 @@ const SidebarContext = (props: { /> {snapshot().messageStatuses.length > 0 && ( - + {messageBarRuns().map((run) => ( - - {run.text} - + /> ))} )} @@ -402,7 +384,7 @@ const SidebarContext = (props: { Compressed Topics
{topics().map((t) => ( - {truncate(t, CONTENT_WIDTH)} + {t} ))} {topicOverflow() > 0 ? ( From 2dd460a8f5121066e6461ffaa88438a2e9366d10 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 11 Mar 2026 22:53:41 -0400 Subject: [PATCH 16/40] fix: lazy-load tui plugin to prevent server crash when tui deps are missing --- index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.ts b/index.ts index eda40940..d327ef43 100644 --- a/index.ts +++ b/index.ts @@ -9,7 +9,6 @@ import { import { Logger } from "./lib/logger" import { createSessionState } from "./lib/state" import { PromptStore } from "./lib/prompts/store" -import tuiPlugin from "./tui/index" import { createChatMessageHandler, createChatMessageTransformHandler, @@ -19,6 +18,11 @@ import { } from "./lib/hooks" import { configureClientAuth, isSecureMode } from "./lib/auth" +let tuiPlugin: Record = {} +try { + tuiPlugin = (await import("./tui/index")).default +} catch {} + const server: Plugin = (async (ctx) => { const config = getConfig(ctx) From 1b03f4960211b9291fdc431482b93501715323b1 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 11 Mar 2026 22:57:24 -0400 Subject: [PATCH 17/40] fix(ci): skip devDependencies in security audit Transitive vulnerabilities (seroval, diff, file-type) come from TUI framework devDependencies that are not shipped to production. These cannot be fixed without upstream patches. --- .github/workflows/pr-checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 124c9091..46711e33 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -32,5 +32,5 @@ jobs: run: npm run build - name: Security audit - run: npm audit --audit-level=high + run: npm audit --omit=dev --audit-level=high continue-on-error: false From 7f4b3fb67f8a5f2c4be24642b9712f3fbc344ef5 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Thu, 12 Mar 2026 00:50:11 -0400 Subject: [PATCH 18/40] feat(tui): add compression summary route with collapsible sections --- tui/data/context.ts | 22 ++-- tui/index.tsx | 12 +- tui/routes/summary.tsx | 171 +++++++++++++++++++++++++++++ tui/shared/names.ts | 3 + tui/shared/types.ts | 7 +- tui/slots/sidebar-top.tsx | 61 ++++++++-- tui/types/opencode-plugin-tui.d.ts | 2 +- 7 files changed, 257 insertions(+), 21 deletions(-) create mode 100644 tui/routes/summary.tsx diff --git a/tui/data/context.ts b/tui/data/context.ts index a41a84ba..ddc24563 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -27,7 +27,7 @@ export const createPlaceholderContextSnapshot = ( persisted: { available: false, activeBlockCount: 0, - activeBlockTopics: [], + activeBlocks: [], activeBlockTopicTotal: 0, }, messageStatuses: [], @@ -36,6 +36,13 @@ export const createPlaceholderContextSnapshot = ( loadedAt: Date.now(), }) +function cleanBlockSummary(raw: string): string { + return raw + .replace(/^\s*\[Compressed conversation section\]\s*/i, "") + .replace(/\s*b\d+<\/dcp-message-id>\s*$/i, "") + .trim() +} + const buildState = async ( sessionID: string, messages: WithParts[], @@ -77,13 +84,12 @@ const loadContextSnapshot = async ( loadAllSessionStats(logger), ]) - const allTopics = Array.from(state.prune.messages.activeBlockIds) + const allBlocks = Array.from(state.prune.messages.activeBlockIds) .map((blockID) => state.prune.messages.blocksById.get(blockID)) - .filter((block): block is NonNullable => !!block) - .map((block) => block.topic) - .filter((topic) => !!topic) - const topics = allTopics.slice(0, 5) - const topicTotal = allTopics.length + .filter((block): block is NonNullable => !!block && !!block.topic) + .map((block) => ({ topic: block.topic, summary: cleanBlockSummary(block.summary) })) + const blocks = allBlocks.slice(0, 5) + const topicTotal = allBlocks.length const notes: string[] = [] if (persisted) { @@ -101,7 +107,7 @@ const loadContextSnapshot = async ( persisted: { available: !!persisted, activeBlockCount: state.prune.messages.activeBlockIds.size, - activeBlockTopics: topics, + activeBlocks: blocks, activeBlockTopicTotal: topicTotal, lastUpdated: persisted?.lastUpdated, }, diff --git a/tui/index.tsx b/tui/index.tsx index a28d1121..8060ac9b 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -3,6 +3,7 @@ import type { TuiPluginInput } from "@opencode-ai/plugin/tui" import { getConfigForDirectory } from "../lib/config" import { Logger } from "../lib/logger" import { createSidebarTopSlot } from "./slots/sidebar-top" +import { createSummaryRoute } from "./routes/summary" import { NAMES } from "./shared/names" const tui = async (input: TuiPluginInput) => { @@ -18,9 +19,18 @@ const tui = async (input: TuiPluginInput) => { const logger = new Logger(config.tui.debug, "tui") + input.api.route.register([createSummaryRoute(input.api)]) + if (config.tui.sidebar) { input.slots.register( - createSidebarTopSlot(input.client, input.event, input.renderer, NAMES, logger), + createSidebarTopSlot( + input.api, + input.client, + input.event, + input.renderer, + NAMES, + logger, + ), ) } } diff --git a/tui/routes/summary.tsx b/tui/routes/summary.tsx new file mode 100644 index 00000000..ff195344 --- /dev/null +++ b/tui/routes/summary.tsx @@ -0,0 +1,171 @@ +/** @jsxImportSource @opentui/solid */ +import { createMemo, createSignal, For, Show } from "solid-js" +import { useKeyboard } from "@opentui/solid" +import type { TuiApi } from "@opencode-ai/plugin/tui" +import { getPalette, type DcpPalette } from "../shared/theme" +import { LABEL, NAMES } from "../shared/names" + +const SINGLE_BORDER = { type: "single" } as any + +interface SummaryRouteParams { + topic?: string + summary?: string + sessionID?: string +} + +interface CollapsibleSection { + label: string + content: string +} + +interface ParsedSummary { + body: string + sections: CollapsibleSection[] +} + +const SECTION_HEADINGS: { pattern: RegExp; label: string }[] = [ + { + pattern: /\n*The following user messages were sent in this conversation verbatim:/, + label: "Protected User Messages", + }, + { + pattern: /\n*The following protected tools were used in this conversation as well:/, + label: "Protected Tools", + }, + { + pattern: + /\n*The following previously compressed summaries were also part of this conversation section:/, + label: "Included Compressed Summaries", + }, +] + +function parseSummary(raw: string): ParsedSummary { + if (!raw) return { body: "", sections: [] } + + const matches: { index: number; length: number; label: string }[] = [] + for (const heading of SECTION_HEADINGS) { + const match = raw.match(heading.pattern) + if (match && match.index !== undefined) { + matches.push({ index: match.index, length: match[0].length, label: heading.label }) + } + } + + if (matches.length === 0) { + return { body: raw, sections: [] } + } + + matches.sort((a, b) => a.index - b.index) + + const body = raw.slice(0, matches[0].index).trimEnd() + const sections: CollapsibleSection[] = [] + + for (let i = 0; i < matches.length; i++) { + const start = matches[i].index + matches[i].length + const end = i + 1 < matches.length ? matches[i + 1].index : raw.length + const content = raw.slice(start, end).trim() + if (content) { + sections.push({ label: matches[i].label, content }) + } + } + + return { body, sections } +} + +function CollapsibleSectionRow(props: { section: CollapsibleSection; palette: DcpPalette }) { + const [expanded, setExpanded] = createSignal(false) + + return ( + + + setExpanded(!expanded())} + > + {expanded() ? " ▼ " : " ▶ "} + + + setExpanded(!expanded())}> + {props.section.label} + + + + + + {props.section.content} + + + + ) +} + +function SummaryScreen(props: { api: TuiApi }) { + const params = createMemo(() => (props.api.route.current.params ?? {}) as SummaryRouteParams) + const palette = createMemo(() => getPalette(props.api.theme.current as Record)) + const parsed = createMemo(() => parseSummary(params().summary || "")) + + const keys = props.api.keybind?.create({ close: "escape" }) + + useKeyboard((evt: any) => { + if (props.api.route.current.name !== NAMES.routes.summary) return + if (props.api.ui?.dialog?.open) return + const matched = keys ? keys.match("close", evt) : evt.name === "escape" + if (!matched) return + evt.preventDefault() + evt.stopPropagation() + const sessionID = params().sessionID + if (sessionID) { + props.api.route.navigate("session", { sessionID }) + } else { + props.api.route.navigate("home") + } + }) + + return ( + + + + + {LABEL} + + + + {params().topic || "Compression Summary"} + + + + + {parsed().body || "(no summary available)"} + + + {(section) => } + + + + + + Press Escape to return + + + + ) +} + +export const createSummaryRoute = (api: TuiApi) => ({ + name: NAMES.routes.summary, + render: () => , +}) diff --git a/tui/shared/names.ts b/tui/shared/names.ts index a2187087..abffd675 100644 --- a/tui/shared/names.ts +++ b/tui/shared/names.ts @@ -2,6 +2,9 @@ export const LABEL = "DCP" export const NAMES = { slot: "dcp.sidebar", + routes: { + summary: "dcp.summary", + }, } as const export type DcpRouteNames = typeof NAMES diff --git a/tui/shared/types.ts b/tui/shared/types.ts index bae36262..a358d43b 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -8,10 +8,15 @@ export type DcpTuiClient = TuiPluginInput["client"] export type { DcpContextBreakdown, DcpMessageStatus } +export interface DcpActiveBlockInfo { + topic: string + summary: string +} + export interface DcpPersistedSummary { available: boolean activeBlockCount: number - activeBlockTopics: string[] + activeBlocks: DcpActiveBlockInfo[] activeBlockTopicTotal: number lastUpdated?: string } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 334b3300..8b4603e1 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @opentui/solid */ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" -import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" import { Logger } from "../../lib/logger" import { createPlaceholderContextSnapshot, @@ -10,12 +10,16 @@ import { } from "../data/context" import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared/theme" import { LABEL, type DcpRouteNames } from "../shared/names" -import type { DcpMessageStatus, DcpTuiClient } from "../shared/types" +import type { DcpActiveBlockInfo, DcpMessageStatus, DcpTuiClient } from "../shared/types" const SINGLE_BORDER = { type: "single" } as any const DIM_TEXT = { dim: true } as any const REFRESH_DEBOUNCE_MS = 100 +const MAX_TOPIC_LEN = 30 + +const truncateTopic = (topic: string): string => + topic.length > MAX_TOPIC_LEN ? topic.slice(0, MAX_TOPIC_LEN - 3) + "..." : topic const compactTokenCount = (value: number): string => { if (value >= 1_000_000) { @@ -110,9 +114,11 @@ const SidebarContextBar = (props: { } const SidebarContext = (props: { + api: TuiApi client: DcpTuiClient event: TuiPluginInput["event"] renderer: TuiPluginInput["renderer"] + names: DcpRouteNames palette: DcpPalette sessionID: () => string logger: Logger @@ -277,9 +283,17 @@ const SidebarContext = (props: { ), ) - const topics = createMemo(() => snapshot().persisted.activeBlockTopics) + const blocks = createMemo(() => snapshot().persisted.activeBlocks) const topicTotal = createMemo(() => snapshot().persisted.activeBlockTopicTotal) - const topicOverflow = createMemo(() => topicTotal() - topics().length) + const topicOverflow = createMemo(() => topicTotal() - blocks().length) + + const navigateToSummary = (block: DcpActiveBlockInfo) => { + props.api.route.navigate(props.names.routes.summary, { + topic: block.topic, + summary: block.summary, + sessionID: props.sessionID(), + }) + } const fallbackNote = createMemo(() => snapshot().notes[0] ?? "") const messageBarRuns = createMemo(() => buildMessageRuns(snapshot().messageStatuses)) @@ -378,18 +392,42 @@ const SidebarContext = (props: { - {topics().length > 0 ? ( + {blocks().length > 0 ? ( <> Compressed Topics - {topics().map((t) => ( - {t} + {blocks().map((block) => ( + + + + {truncateTopic(block.topic)} + + + + navigateToSummary(block)} + > + + + + ))} {topicOverflow() > 0 ? ( - - ... {topicOverflow()} more topics - + + + + ... {topicOverflow()} more topics + + + + + + + + ) : null} ) : fallbackNote() ? ( @@ -421,6 +459,7 @@ const SidebarContext = (props: { } export const createSidebarTopSlot = ( + api: TuiApi, client: DcpTuiClient, event: TuiPluginInput["event"], renderer: TuiPluginInput["renderer"], @@ -438,9 +477,11 @@ export const createSidebarTopSlot = ( ) return ( value.session_id} logger={logger} diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index 5df963d7..bf2b2336 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -41,7 +41,7 @@ declare module "@opencode-ai/plugin/tui" { variant?: string duration?: number }) => void - dialog: { + dialog?: { open?: boolean } } From 4f68572a600956c481e9960a31edad31047a77a0 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Thu, 12 Mar 2026 01:08:42 -0400 Subject: [PATCH 19/40] feat(tui): add expandable topic list in sidebar --- tui/data/context.ts | 6 +----- tui/shared/types.ts | 1 - tui/slots/sidebar-top.tsx | 24 ++++++++++++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/tui/data/context.ts b/tui/data/context.ts index ddc24563..d258b75d 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -28,7 +28,6 @@ export const createPlaceholderContextSnapshot = ( available: false, activeBlockCount: 0, activeBlocks: [], - activeBlockTopicTotal: 0, }, messageStatuses: [], allTimeStats: { totalTokensSaved: 0, sessionCount: 0 }, @@ -88,8 +87,6 @@ const loadContextSnapshot = async ( .map((blockID) => state.prune.messages.blocksById.get(blockID)) .filter((block): block is NonNullable => !!block && !!block.topic) .map((block) => ({ topic: block.topic, summary: cleanBlockSummary(block.summary) })) - const blocks = allBlocks.slice(0, 5) - const topicTotal = allBlocks.length const notes: string[] = [] if (persisted) { @@ -107,8 +104,7 @@ const loadContextSnapshot = async ( persisted: { available: !!persisted, activeBlockCount: state.prune.messages.activeBlockIds.size, - activeBlocks: blocks, - activeBlockTopicTotal: topicTotal, + activeBlocks: allBlocks, lastUpdated: persisted?.lastUpdated, }, messageStatuses, diff --git a/tui/shared/types.ts b/tui/shared/types.ts index a358d43b..492644a1 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -17,7 +17,6 @@ export interface DcpPersistedSummary { available: boolean activeBlockCount: number activeBlocks: DcpActiveBlockInfo[] - activeBlockTopicTotal: number lastUpdated?: string } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 8b4603e1..e8dbb761 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -283,9 +283,13 @@ const SidebarContext = (props: { ), ) - const blocks = createMemo(() => snapshot().persisted.activeBlocks) - const topicTotal = createMemo(() => snapshot().persisted.activeBlockTopicTotal) - const topicOverflow = createMemo(() => topicTotal() - blocks().length) + const TOPIC_LIMIT = 3 + const allBlocks = createMemo(() => snapshot().persisted.activeBlocks) + const [topicsExpanded, setTopicsExpanded] = createSignal(false) + const blocks = createMemo(() => + topicsExpanded() ? allBlocks() : allBlocks().slice(0, TOPIC_LIMIT), + ) + const topicOverflow = createMemo(() => allBlocks().length - TOPIC_LIMIT) const navigateToSummary = (block: DcpActiveBlockInfo) => { props.api.route.navigate(props.names.routes.summary, { @@ -419,12 +423,20 @@ const SidebarContext = (props: { - ... {topicOverflow()} more topics + {topicsExpanded() + ? `showing all ${allBlocks().length} topics` + : `... ${topicOverflow()} more topics`} - - + setTopicsExpanded(!topicsExpanded())} + > + + {topicsExpanded() ? " ▲ " : " ▼ "} + From 77073e563043846daf0f5c6cb876ddd27d696fdb Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Thu, 12 Mar 2026 01:24:43 -0400 Subject: [PATCH 20/40] fix(tui): rename and reorder sidebar summary rows --- tui/slots/sidebar-top.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index e8dbb761..0233937b 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -337,18 +337,18 @@ const SidebarContext = (props: { {snapshot().messageStatuses.length > 0 && ( From fc365a962e8d512035b626ea1adb48ebd00dcf28 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Thu, 12 Mar 2026 21:22:29 -0400 Subject: [PATCH 21/40] chore: remove dead code --- lib/config.ts | 1 - lib/ui/utils.ts | 17 ----------------- tui/shared/types.ts | 2 +- tui/slots/sidebar-top.tsx | 2 +- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/lib/config.ts b/lib/config.ts index 046ce2cb..7284ac67 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -98,7 +98,6 @@ export const VALID_CONFIG_KEYS = new Set([ "$schema", "enabled", "debug", - "showUpdateToasts", "pruneNotification", "pruneNotificationType", "turnProtection", diff --git a/lib/ui/utils.ts b/lib/ui/utils.ts index ad31542c..9cd92ef1 100644 --- a/lib/ui/utils.ts +++ b/lib/ui/utils.ts @@ -288,20 +288,3 @@ export function formatPrunedItemsList( return lines } - -export function formatPruningResultForTool( - prunedIds: string[], - toolMetadata: Map, - workingDirectory?: string, -): string { - const lines: string[] = [] - lines.push(`Context pruning complete. Pruned ${prunedIds.length} tool outputs.`) - lines.push("") - - if (prunedIds.length > 0) { - lines.push(`Semantically pruned (${prunedIds.length}):`) - lines.push(...formatPrunedItemsList(prunedIds, toolMetadata, workingDirectory)) - } - - return lines.join("\n").trim() -} diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 492644a1..70632697 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -6,7 +6,7 @@ import type { export type DcpTuiClient = TuiPluginInput["client"] -export type { DcpContextBreakdown, DcpMessageStatus } +export type { DcpMessageStatus } export interface DcpActiveBlockInfo { topic: string diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 0233937b..3236e094 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -40,7 +40,7 @@ const compactTokenCount = (value: number): string => { const buildMessageRuns = ( statuses: DcpMessageStatus[], ): { count: number; status: DcpMessageStatus }[] => { - if (statuses.length === 0) return [{ count: 1, status: "pruned" }] + if (statuses.length === 0) return [] // Group consecutive same-status messages into runs const runs: { count: number; status: DcpMessageStatus }[] = [] From b143db0522fb6cfc79c8a9cf216a1e3623dead16 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 23 Mar 2026 12:33:26 -0400 Subject: [PATCH 22/40] fix(lib): pass session messages to compress notifications --- lib/compress/pipeline.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/compress/pipeline.ts b/lib/compress/pipeline.ts index acf92392..b6de2e07 100644 --- a/lib/compress/pipeline.ts +++ b/lib/compress/pipeline.ts @@ -86,9 +86,10 @@ export async function finalizeSession( await saveSessionState(ctx.state, ctx.logger) const params = getCurrentParams(ctx.state, rawMessages, ctx.logger) - const sessionMessageIds = rawMessages - .filter((msg) => !isIgnoredUserMessage(msg)) - .map((msg) => msg.info.id) + const totalSessionTokens = getCurrentTokenUsage(ctx.state, rawMessages) + const sessionMessages = rawMessages.filter( + (msg) => !(msg.info.role === "user" && isIgnoredUserMessage(msg)), + ) await sendCompressNotification( ctx.client, @@ -98,7 +99,8 @@ export async function finalizeSession( toolCtx.sessionID, entries, batchTopic, - sessionMessageIds, + totalSessionTokens, + sessionMessages, params, ) } From 6d4c7956b78eac279178c0e34580341980f43c2f Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 23 Mar 2026 12:33:26 -0400 Subject: [PATCH 23/40] refactor(tui): port sidebar plugin to snapshot api --- tui/index.tsx | 19 +- tui/routes/summary.tsx | 11 +- tui/shared/types.ts | 5 +- tui/slots/sidebar-top.tsx | 43 ++-- tui/types/opencode-plugin-tui.d.ts | 368 +++++++++++++++++++++++++---- 5 files changed, 353 insertions(+), 93 deletions(-) diff --git a/tui/index.tsx b/tui/index.tsx index 8060ac9b..a159bc56 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -1,14 +1,14 @@ /** @jsxImportSource @opentui/solid */ -import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { TuiPlugin } from "@opencode-ai/plugin/tui" import { getConfigForDirectory } from "../lib/config" import { Logger } from "../lib/logger" import { createSidebarTopSlot } from "./slots/sidebar-top" import { createSummaryRoute } from "./routes/summary" import { NAMES } from "./shared/names" -const tui = async (input: TuiPluginInput) => { +const tui: TuiPlugin = async (api) => { const config = getConfigForDirectory(process.cwd(), (title, message) => { - input.api.ui.toast({ + api.ui.toast({ title, message, variant: "warning", @@ -19,19 +19,10 @@ const tui = async (input: TuiPluginInput) => { const logger = new Logger(config.tui.debug, "tui") - input.api.route.register([createSummaryRoute(input.api)]) + api.route.register([createSummaryRoute(api)]) if (config.tui.sidebar) { - input.slots.register( - createSidebarTopSlot( - input.api, - input.client, - input.event, - input.renderer, - NAMES, - logger, - ), - ) + api.slots.register(createSidebarTopSlot(api, NAMES, logger)) } } diff --git a/tui/routes/summary.tsx b/tui/routes/summary.tsx index ff195344..8cd42fff 100644 --- a/tui/routes/summary.tsx +++ b/tui/routes/summary.tsx @@ -100,16 +100,19 @@ function CollapsibleSectionRow(props: { section: CollapsibleSection; palette: Dc } function SummaryScreen(props: { api: TuiApi }) { - const params = createMemo(() => (props.api.route.current.params ?? {}) as SummaryRouteParams) + const params = createMemo(() => { + const current = props.api.route.current + return ("params" in current ? current.params : {}) as SummaryRouteParams + }) const palette = createMemo(() => getPalette(props.api.theme.current as Record)) const parsed = createMemo(() => parseSummary(params().summary || "")) - const keys = props.api.keybind?.create({ close: "escape" }) + const keys = props.api.keybind.create({ close: "escape" }) useKeyboard((evt: any) => { if (props.api.route.current.name !== NAMES.routes.summary) return - if (props.api.ui?.dialog?.open) return - const matched = keys ? keys.match("close", evt) : evt.name === "escape" + if (props.api.ui.dialog.open) return + const matched = keys.match("close", evt) if (!matched) return evt.preventDefault() evt.stopPropagation() diff --git a/tui/shared/types.ts b/tui/shared/types.ts index 70632697..f1128fc4 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -1,10 +1,11 @@ -import type { TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { TuiPluginApi } from "@opencode-ai/plugin/tui" import type { MessageStatus as DcpMessageStatus, TokenBreakdown as DcpContextBreakdown, } from "../../lib/analysis/tokens" -export type DcpTuiClient = TuiPluginInput["client"] +export type DcpTuiApi = TuiPluginApi +export type DcpTuiClient = DcpTuiApi["client"] export type { DcpMessageStatus } diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 3236e094..a878e5a0 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @opentui/solid */ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js" -import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" +import type { TuiSlotPlugin } from "@opencode-ai/plugin/tui" import { Logger } from "../../lib/logger" import { createPlaceholderContextSnapshot, @@ -10,7 +10,7 @@ import { } from "../data/context" import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared/theme" import { LABEL, type DcpRouteNames } from "../shared/names" -import type { DcpActiveBlockInfo, DcpMessageStatus, DcpTuiClient } from "../shared/types" +import type { DcpActiveBlockInfo, DcpMessageStatus, DcpTuiApi } from "../shared/types" const SINGLE_BORDER = { type: "single" } as any const DIM_TEXT = { dim: true } as any @@ -114,10 +114,7 @@ const SidebarContextBar = (props: { } const SidebarContext = (props: { - api: TuiApi - client: DcpTuiClient - event: TuiPluginInput["event"] - renderer: TuiPluginInput["renderer"] + api: DcpTuiApi names: DcpRouteNames palette: DcpPalette sessionID: () => string @@ -137,7 +134,7 @@ const SidebarContext = (props: { renderTimeout = setTimeout(() => { renderTimeout = undefined try { - props.renderer.requestRender() + props.api.renderer.requestRender() } catch (error) { props.logger.warn("Failed to request TUI render", { error: error instanceof Error ? error.message : String(error), @@ -180,7 +177,7 @@ const SidebarContext = (props: { const currentRequest = ++requestVersion try { - const value = await loadContextSnapshotCached(props.client, props.logger, sessionID) + const value = await loadContextSnapshotCached(props.api.client, props.logger, sessionID) if (currentRequest !== requestVersion || props.sessionID() !== sessionID) { return } @@ -230,43 +227,43 @@ const SidebarContext = (props: { } const unsubs = [ - props.event.on("message.updated", (event) => { + props.api.event.on("message.updated", (event) => { if (event.properties.info.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("message.removed", (event) => { + props.api.event.on("message.removed", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("message.part.updated", (event) => { + props.api.event.on("message.part.updated", (event) => { if (event.properties.part.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("message.part.delta", (event) => { + props.api.event.on("message.part.delta", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("message.part.removed", (event) => { + props.api.event.on("message.part.removed", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("session.updated", (event) => { + props.api.event.on("session.updated", (event) => { if (event.properties.info.id !== sessionID) return scheduleRefresh() }), - props.event.on("session.deleted", (event) => { + props.api.event.on("session.deleted", (event) => { if (event.properties.info.id !== sessionID) return scheduleRefresh() }), - props.event.on("session.diff", (event) => { + props.api.event.on("session.diff", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("session.error", (event) => { + props.api.event.on("session.error", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), - props.event.on("session.status", (event) => { + props.api.event.on("session.status", (event) => { if (event.properties.sessionID !== sessionID) return scheduleRefresh() }), @@ -471,13 +468,10 @@ const SidebarContext = (props: { } export const createSidebarTopSlot = ( - api: TuiApi, - client: DcpTuiClient, - event: TuiPluginInput["event"], - renderer: TuiPluginInput["renderer"], + api: DcpTuiApi, names: DcpRouteNames, logger: Logger, -) => ({ +): TuiSlotPlugin => ({ id: names.slot, slots: { sidebar_top( @@ -490,9 +484,6 @@ export const createSidebarTopSlot = ( return ( value.session_id} diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index bf2b2336..daf16b65 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -1,73 +1,347 @@ declare module "@opencode-ai/plugin/tui" { - export interface TuiCommand { + import type { + createOpencodeClient as createOpencodeClientV2, + Event as TuiEvent, + LspStatus, + McpStatus, + Todo, + } from "@opencode-ai/sdk/v2" + import type { CliRenderer, ParsedKey, Plugin as CorePlugin, SlotMode } from "@opentui/core" + import type { JSX } from "@opentui/solid" + import type { Plugin as ServerPlugin } from "@opencode-ai/plugin" + + export type { CliRenderer, SlotMode } + + export type TuiRouteCurrent = + | { + name: "home" + } + | { + name: "session" + params: { + sessionID: string + initialPrompt?: unknown + } + } + | { + name: string + params?: Record + } + + export type TuiRouteDefinition = { + name: string + render: (input: { params?: Record }) => JSX.Element + } + + export type TuiCommand = { title: string value: string description?: string category?: string - keybind?: unknown + keybind?: string + suggested?: boolean + hidden?: boolean + enabled?: boolean slash?: { name: string + aliases?: string[] } onSelect?: () => void } - export interface TuiRouteDefinition { + export type TuiKeybind = { name: string - render: (input: { params?: Record }) => Node + ctrl: boolean + meta: boolean + shift: boolean + super?: boolean + leader: boolean + } + + export type TuiKeybindMap = Record + + export type TuiKeybindSet = { + readonly all: TuiKeybindMap + get: (name: string) => string + match: (name: string, evt: ParsedKey) => boolean + print: (name: string) => string + } + + export type TuiDialogProps = { + size?: "medium" | "large" + onClose: () => void + children?: JSX.Element } - export interface TuiKeybindSet { - get: (key: string) => unknown - match: (key: string, evt: unknown) => boolean + export type TuiDialogStack = { + replace: (render: () => JSX.Element, onClose?: () => void) => void + clear: () => void + setSize: (size: "medium" | "large") => void + readonly size: "medium" | "large" + readonly depth: number + readonly open: boolean } - export interface TuiApi { + export type TuiDialogAlertProps = { + title: string + message: string + onConfirm?: () => void + } + + export type TuiDialogConfirmProps = { + title: string + message: string + onConfirm?: () => void + onCancel?: () => void + } + + export type TuiDialogPromptProps = { + title: string + description?: () => JSX.Element + placeholder?: string + value?: string + onConfirm?: (value: string) => void + onCancel?: () => void + } + + export type TuiDialogSelectOption = { + title: string + value: Value + description?: string + footer?: JSX.Element | string + category?: string + disabled?: boolean + onSelect?: () => void + } + + export type TuiDialogSelectProps = { + title: string + placeholder?: string + options: TuiDialogSelectOption[] + flat?: boolean + onMove?: (option: TuiDialogSelectOption) => void + onFilter?: (query: string) => void + onSelect?: (option: TuiDialogSelectOption) => void + skipFilter?: boolean + current?: Value + } + + export type TuiToast = { + variant?: "info" | "success" | "warning" | "error" + title?: string + message: string + duration?: number + } + + export type TuiTheme = { + readonly current: Record + readonly selected: string + has: (name: string) => boolean + set: (name: string) => boolean + install: (jsonPath: string) => Promise + mode: () => "dark" | "light" + readonly ready: boolean + } + + export type TuiKV = { + get: (key: string, fallback?: Value) => Value + set: (key: string, value: unknown) => void + readonly ready: boolean + } + + export type TuiState = { + session: { + diff: (sessionID: string) => ReadonlyArray + todo: (sessionID: string) => ReadonlyArray + } + lsp: () => ReadonlyArray + mcp: () => ReadonlyArray + } + + export type TuiApi = { command: { - register: (cb: () => TuiCommand[]) => void + register: (cb: () => TuiCommand[]) => () => void trigger: (value: string) => void } route: { - register: (routes: TuiRouteDefinition[]) => () => void - navigate: (name: string, params?: any) => void - current: { - name: string - params?: any - } + register: (routes: TuiRouteDefinition[]) => () => void + navigate: (name: string, params?: Record) => void + readonly current: TuiRouteCurrent } ui: { - toast: (input: { - title: string - message: string - variant?: string - duration?: number - }) => void - dialog?: { - open?: boolean - } - } - keybind?: { - create: ( - defaults: Record, - overrides?: Record, - ) => TuiKeybindSet - } - theme: { - current: unknown - } - } - - export interface TuiPluginInput< - Renderer extends { requestRender: () => void } = { requestRender: () => void }, - Node = unknown, - > { - client: any - event: { - on: (name: string, cb: (event: any) => void) => () => void + Dialog: (props: TuiDialogProps) => JSX.Element + DialogAlert: (props: TuiDialogAlertProps) => JSX.Element + DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element + DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element + DialogSelect: (props: TuiDialogSelectProps) => JSX.Element + toast: (input: TuiToast) => void + dialog: TuiDialogStack } - renderer: Renderer - slots: { - register: (slot: any) => () => void + keybind: { + match: (key: string, evt: ParsedKey) => boolean + print: (key: string) => string + create: (defaults: TuiKeybindMap, overrides?: Record) => TuiKeybindSet } - api: TuiApi + kv: TuiKV + state: TuiState + theme: TuiTheme + } + + export type TuiSidebarMcpItem = { + name: string + status: McpStatus["status"] + error?: string + } + + export type TuiSidebarLspItem = Pick + + export type TuiSidebarTodoItem = Pick + + export type TuiSidebarFileItem = { + file: string + additions: number + deletions: number + } + + export type TuiSlotMap = { + app: {} + home_logo: {} + home_tips: { + show_tips: boolean + tips_hidden: boolean + first_time_user: boolean + } + home_below_tips: { + show_tips: boolean + tips_hidden: boolean + first_time_user: boolean + } + sidebar_top: { + session_id: string + } + sidebar_title: { + session_id: string + title: string + share_url?: string + } + sidebar_context: { + session_id: string + tokens: number + percentage: number | null + cost: number + } + sidebar_mcp: { + session_id: string + items: TuiSidebarMcpItem[] + connected: number + errors: number + } + sidebar_lsp: { + session_id: string + items: TuiSidebarLspItem[] + disabled: boolean + } + sidebar_todo: { + session_id: string + items: TuiSidebarTodoItem[] + } + sidebar_files: { + session_id: string + items: TuiSidebarFileItem[] + } + sidebar_getting_started: { + session_id: string + show_getting_started: boolean + has_providers: boolean + dismissed: boolean + } + sidebar_directory: { + session_id: string + directory: string + directory_parent: string + directory_name: string + } + sidebar_version: { + session_id: string + version: string + } + sidebar_bottom: { + session_id: string + directory: string + directory_parent: string + directory_name: string + version: string + show_getting_started: boolean + has_providers: boolean + dismissed: boolean + } + } + + export type TuiSlotContext = { + theme: TuiTheme + } + + export type TuiSlotPlugin = CorePlugin + + export type TuiSlots = { + register: (plugin: TuiSlotPlugin) => () => void + } + + export type TuiEventBus = { + on: ( + type: Type, + handler: (event: Extract) => void, + ) => () => void + } + + export type TuiDispose = () => void | Promise + + export type TuiLifecycle = { + readonly signal: AbortSignal + onDispose: (fn: TuiDispose) => () => void + } + + export type TuiPluginState = "first" | "updated" | "same" + + export type TuiPluginEntry = { + name: string + source: "file" | "npm" | "internal" + spec: string + target: string + requested?: string + version?: string + modified?: number + first_time: number + last_time: number + time_changed: number + load_count: number + fingerprint: string + } + + export type TuiPluginMeta = TuiPluginEntry & { + state: TuiPluginState + } + + export type TuiHostPluginApi = TuiApi & { + client: ReturnType + event: TuiEventBus + renderer: Renderer + } + + export type TuiPluginApi = TuiHostPluginApi & { + slots: TuiSlots + lifecycle: TuiLifecycle + } + + export type TuiPlugin = ( + api: TuiPluginApi, + options: Record | undefined, + meta: TuiPluginMeta, + ) => Promise + + export type TuiPluginModule = { + server?: ServerPlugin + tui?: TuiPlugin + slots?: TuiSlotPlugin } } From f4c23a65f909e3a31cc4414d7e1b67c37373d084 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 25 Mar 2026 01:51:55 -0400 Subject: [PATCH 24/40] fix(tui): restore sidebar widget on new host layout --- tui/slots/sidebar-top.tsx | 46 ++++++++++++++++-------------- tui/types/opencode-plugin-tui.d.ts | 3 ++ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index a878e5a0..3042f042 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -471,25 +471,29 @@ export const createSidebarTopSlot = ( api: DcpTuiApi, names: DcpRouteNames, logger: Logger, -): TuiSlotPlugin => ({ - id: names.slot, - slots: { - sidebar_top( - ctx: { theme: { current: Record } }, - value: { session_id: string }, - ) { - const palette = createMemo(() => - getPalette(ctx.theme.current as Record), - ) - return ( - value.session_id} - logger={logger} - /> - ) +): TuiSlotPlugin => { + const renderSidebar = ( + ctx: { theme: { current: Record } }, + value: { session_id: string }, + ) => { + const palette = createMemo(() => getPalette(ctx.theme.current as Record)) + return ( + value.session_id} + logger={logger} + /> + ) + } + + return { + id: names.slot, + order: 90, + slots: { + sidebar_content: renderSidebar, + sidebar_top: renderSidebar, }, - }, -}) + } +} diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index daf16b65..ebf7befc 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -219,6 +219,9 @@ declare module "@opencode-ai/plugin/tui" { sidebar_top: { session_id: string } + sidebar_content: { + session_id: string + } sidebar_title: { session_id: string title: string From 98cf89884d458d650124e3b061abdf9d394427cc Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 25 Mar 2026 18:49:58 -0400 Subject: [PATCH 25/40] chore(deps): update opencode sdk/plugin to 1.3.2 --- tui/package-lock.json | 2453 +++++++++++++++++++++++++++++++++++++++++ tui/package.json | 2 +- 2 files changed, 2454 insertions(+), 1 deletion(-) create mode 100644 tui/package-lock.json diff --git a/tui/package-lock.json b/tui/package-lock.json new file mode 100644 index 00000000..ca7a02c3 --- /dev/null +++ b/tui/package-lock.json @@ -0,0 +1,2453 @@ +{ + "name": "dcp-tui-probe-local", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dcp-tui-probe-local", + "dependencies": { + "@opencode-ai/plugin": "1.3.2", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", + "solid-js": "1.9.9" + } + }, + "../../../../../src/opencode/node_modules/.bun/solid-js@1.9.10/node_modules/solid-js": { + "version": "1.9.10", + "extraneous": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "license": "MIT", + "dependencies": { + "@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/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier2d-simd-compat": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@dimforge/rapier2d-simd-compat/-/rapier2d-simd-compat-0.17.3.tgz", + "integrity": "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "license": "MIT", + "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.0.0", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "license": "MIT", + "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.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "license": "MIT", + "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.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.2.tgz", + "integrity": "sha512-eT0ZovMCOQlfTdAnfbEWgW343mJ9SHgEVfdiOSX1NMIVXac6hxE2xwUsRVTV3wLvfA6dKZhN800f8wLUEyPlyg==", + "license": "MIT", + "dependencies": { + "@opencode-ai/sdk": "1.3.2", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.2.tgz", + "integrity": "sha512-u7sXVKn0kyAA5vVVHuHQfq3+3UGWOU1Sh6d/e+aS4zO8AwriTSWNQ9r8Qy5yxBH+PoeOGl5WIVdp+s2Ea2zuAg==", + "license": "MIT" + }, + "node_modules/@opentui/core": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-e/n7hCtpOzS57X9llODu0SUXCQBWSxHQeTA0iuL7j0nhSFgM6KpL8kJ7VQBU1EEn33pytA0udbfKSJ6sqWmEJg==", + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.1.2", + "diff": "8.0.2", + "jimp": "1.6.0", + "marked": "17.0.1", + "yoga-layout": "3.2.1" + }, + "optionalDependencies": { + "@dimforge/rapier2d-simd-compat": "^0.17.3", + "@opentui/core-darwin-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-darwin-x64": "0.0.0-20260307-536c401b", + "@opentui/core-linux-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-linux-x64": "0.0.0-20260307-536c401b", + "@opentui/core-win32-arm64": "0.0.0-20260307-536c401b", + "@opentui/core-win32-x64": "0.0.0-20260307-536c401b", + "bun-webgpu": "0.1.5", + "planck": "^1.4.2", + "three": "0.177.0" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-y46MUgcjkIqC/IBxErchM51KmLARxudrKqr09Gyy25ry+GUE8gzaEIx6EeMAUnWDWvetMacKgEaNCjtdkfGgDQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-darwin-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-USf14JkFaCyKvn9FfLn6AZv14o5ED7uHBNq4kCmggD28HmqHsklDhGNyDnswUggCworJ6xz7jICZTiKKrSwRbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-fzNf0Mv7OjNktJFg17WsvdDD5Ej12eSwPVMProlQFbklC8qCEsZfLJKYq9ExYLRoxHX7wFm9Eq6L7hVaGcn2sA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-+80TgK5ZhdJvM2+fiCbeCJvXk9De3oNB42wcCtGcwt3x1wyPYAbWIetw6dIGqXIbica/El+7+6Y2DMV06PUUug==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-win32-arm64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-SBeHYwNpWJlHxMX6+aO8KsatWpMMxOs+LpFA7M2PTV0g81WUHPlxm6kHi6UHpjwYuslvtcYKgUL0IyQs1jbdNA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-QIU/s6NrXJLRlTyLJZ/41E3MhVGGZazPrwv6MnMx7LOE/uBQo4OGjcRdsIIkhXYIqNRUIH/Yfd5Hyf6twJpBBA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/solid": { + "version": "0.0.0-20260307-536c401b", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.0.0-20260307-536c401b.tgz", + "integrity": "sha512-wfItFCVBsP2iWvFgj2/lVRN7/O7R5eu9NReY4Wl34Z+c9d7P6FVSa1xOriziTPysukW1OhFe8MNN7MIaggYdHg==", + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.0.0-20260307-536c401b", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.9", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.9" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.6.tgz", + "integrity": "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", + "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", + "license": "MIT", + "dependencies": { + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", + "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.8" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bun-ffi-structs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.1.2.tgz", + "integrity": "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w==", + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, + "node_modules/bun-webgpu": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bun-webgpu/-/bun-webgpu-0.1.5.tgz", + "integrity": "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@webgpu/types": "^0.1.60" + }, + "optionalDependencies": { + "bun-webgpu-darwin-arm64": "^0.1.5", + "bun-webgpu-darwin-x64": "^0.1.5", + "bun-webgpu-linux-x64": "^0.1.5", + "bun-webgpu-win32-x64": "^0.1.5" + } + }, + "node_modules/bun-webgpu-darwin-arm64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-arm64/-/bun-webgpu-darwin-arm64-0.1.6.tgz", + "integrity": "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-darwin-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-x64/-/bun-webgpu-darwin-x64-0.1.6.tgz", + "integrity": "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-linux-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-linux-x64/-/bun-webgpu-linux-x64-0.1.6.tgz", + "integrity": "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/bun-webgpu-win32-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bun-webgpu-win32-x64/-/bun-webgpu-win32-x64-0.1.6.tgz", + "integrity": "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.325", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", + "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "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", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "license": "MIT" + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/planck": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/planck/-/planck-1.4.3.tgz", + "integrity": "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=24.0" + }, + "peerDependencies": { + "stage-js": "^1.0.0-alpha.12" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/s-js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", + "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.4.tgz", + "integrity": "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg==", + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/solid-js": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", + "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/stage-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stage-js/-/stage-js-1.0.1.tgz", + "integrity": "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/three": { + "version": "0.177.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", + "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", + "license": "MIT", + "optional": true + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", + "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/emscripten": "^1.40.0" + }, + "peerDependenciesMeta": { + "@types/emscripten": { + "optional": true + } + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tui/package.json b/tui/package.json index 2960722a..e59f69fd 100644 --- a/tui/package.json +++ b/tui/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@opencode-ai/plugin": "1.2.21", + "@opencode-ai/plugin": "1.3.2", "@opentui/core": "0.0.0-20260307-536c401b", "@opentui/solid": "0.0.0-20260307-536c401b", "solid-js": "1.9.9" From 215da5e72b3e8e81b5a6f815b1cfc4007ac009ec Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 25 Mar 2026 18:49:58 -0400 Subject: [PATCH 26/40] fix(tui): align plugin with snapshot tui api --- tui/shared/names.ts | 1 - tui/slots/sidebar-top.tsx | 2 - tui/types/opencode-plugin-tui.d.ts | 197 ++++++++++++++++++----------- 3 files changed, 126 insertions(+), 74 deletions(-) diff --git a/tui/shared/names.ts b/tui/shared/names.ts index abffd675..5e39b154 100644 --- a/tui/shared/names.ts +++ b/tui/shared/names.ts @@ -1,7 +1,6 @@ export const LABEL = "DCP" export const NAMES = { - slot: "dcp.sidebar", routes: { summary: "dcp.summary", }, diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-top.tsx index 3042f042..1adbbc91 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-top.tsx @@ -489,11 +489,9 @@ export const createSidebarTopSlot = ( } return { - id: names.slot, order: 90, slots: { sidebar_content: renderSidebar, - sidebar_top: renderSidebar, }, } } diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index ebf7befc..8daf59c5 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -5,12 +5,25 @@ declare module "@opencode-ai/plugin/tui" { LspStatus, McpStatus, Todo, + Message, + Part, + Provider, + PermissionRequest, + QuestionRequest, + SessionStatus, + Workspace, + Config as SdkConfig, } from "@opencode-ai/sdk/v2" - import type { CliRenderer, ParsedKey, Plugin as CorePlugin, SlotMode } from "@opentui/core" - import type { JSX } from "@opentui/solid" + + import type { CliRenderer, ParsedKey, RGBA } from "@opentui/core" + import type { JSX, SolidPlugin } from "@opentui/solid" import type { Plugin as ServerPlugin } from "@opencode-ai/plugin" - export type { CliRenderer, SlotMode } + // PluginOptions = Record — not yet exported by installed @opencode-ai/plugin + type PluginOptions = Record + + export type { CliRenderer } + export type { SlotMode } from "@opentui/core" export type TuiRouteCurrent = | { @@ -133,8 +146,64 @@ declare module "@opencode-ai/plugin/tui" { duration?: number } + export type TuiThemeCurrent = { + readonly primary: RGBA + readonly secondary: RGBA + readonly accent: RGBA + readonly error: RGBA + readonly warning: RGBA + readonly success: RGBA + readonly info: RGBA + readonly text: RGBA + readonly textMuted: RGBA + readonly selectedListItemText: RGBA + readonly background: RGBA + readonly backgroundPanel: RGBA + readonly backgroundElement: RGBA + readonly backgroundMenu: RGBA + readonly border: RGBA + readonly borderActive: RGBA + readonly borderSubtle: RGBA + readonly diffAdded: RGBA + readonly diffRemoved: RGBA + readonly diffContext: RGBA + readonly diffHunkHeader: RGBA + readonly diffHighlightAdded: RGBA + readonly diffHighlightRemoved: RGBA + readonly diffAddedBg: RGBA + readonly diffRemovedBg: RGBA + readonly diffContextBg: RGBA + readonly diffLineNumber: RGBA + readonly diffAddedLineNumberBg: RGBA + readonly diffRemovedLineNumberBg: RGBA + readonly markdownText: RGBA + readonly markdownHeading: RGBA + readonly markdownLink: RGBA + readonly markdownLinkText: RGBA + readonly markdownCode: RGBA + readonly markdownBlockQuote: RGBA + readonly markdownEmph: RGBA + readonly markdownStrong: RGBA + readonly markdownHorizontalRule: RGBA + readonly markdownListItem: RGBA + readonly markdownListEnumeration: RGBA + readonly markdownImage: RGBA + readonly markdownImageText: RGBA + readonly markdownCodeBlock: RGBA + readonly syntaxComment: RGBA + readonly syntaxKeyword: RGBA + readonly syntaxFunction: RGBA + readonly syntaxVariable: RGBA + readonly syntaxString: RGBA + readonly syntaxNumber: RGBA + readonly syntaxType: RGBA + readonly syntaxOperator: RGBA + readonly syntaxPunctuation: RGBA + readonly thinkingOpacity: number + } + export type TuiTheme = { - readonly current: Record + readonly current: TuiThemeCurrent readonly selected: string has: (name: string) => boolean set: (name: string) => boolean @@ -150,15 +219,52 @@ declare module "@opencode-ai/plugin/tui" { } export type TuiState = { + readonly ready: boolean + readonly config: SdkConfig + readonly provider: ReadonlyArray + readonly path: { + state: string + config: string + worktree: string + directory: string + } + readonly vcs: { branch?: string } | undefined + readonly workspace: { + list: () => ReadonlyArray + get: (workspaceID: string) => Workspace | undefined + } session: { + count: () => number diff: (sessionID: string) => ReadonlyArray todo: (sessionID: string) => ReadonlyArray + messages: (sessionID: string) => ReadonlyArray + status: (sessionID: string) => SessionStatus | undefined + permission: (sessionID: string) => ReadonlyArray + question: (sessionID: string) => ReadonlyArray } + part: (messageID: string) => ReadonlyArray lsp: () => ReadonlyArray mcp: () => ReadonlyArray } + // Inlined: Pick & NonNullable + // PluginConfig (opencode.json schema) is not re-exported by @opencode-ai/plugin at installed version. + type TuiConfigView = Record + + type Frozen = Value extends (...args: never[]) => unknown + ? Value + : Value extends ReadonlyArray + ? ReadonlyArray> + : Value extends object + ? { readonly [Key in keyof Value]: Frozen } + : Value + + export type TuiApp = { + readonly version: string + } + export type TuiApi = { + app: TuiApp command: { register: (cb: () => TuiCommand[]) => () => void trigger: (value: string) => void @@ -182,6 +288,7 @@ declare module "@opencode-ai/plugin/tui" { print: (key: string) => string create: (defaults: TuiKeybindMap, overrides?: Record) => TuiKeybindSet } + readonly tuiConfig: Frozen kv: TuiKV state: TuiState theme: TuiTheme @@ -206,77 +313,17 @@ declare module "@opencode-ai/plugin/tui" { export type TuiSlotMap = { app: {} home_logo: {} - home_tips: { - show_tips: boolean - tips_hidden: boolean - first_time_user: boolean - } - home_below_tips: { - show_tips: boolean - tips_hidden: boolean - first_time_user: boolean - } - sidebar_top: { - session_id: string - } - sidebar_content: { - session_id: string - } + home_bottom: {} sidebar_title: { session_id: string title: string share_url?: string } - sidebar_context: { - session_id: string - tokens: number - percentage: number | null - cost: number - } - sidebar_mcp: { - session_id: string - items: TuiSidebarMcpItem[] - connected: number - errors: number - } - sidebar_lsp: { - session_id: string - items: TuiSidebarLspItem[] - disabled: boolean - } - sidebar_todo: { - session_id: string - items: TuiSidebarTodoItem[] - } - sidebar_files: { - session_id: string - items: TuiSidebarFileItem[] - } - sidebar_getting_started: { - session_id: string - show_getting_started: boolean - has_providers: boolean - dismissed: boolean - } - sidebar_directory: { + sidebar_content: { session_id: string - directory: string - directory_parent: string - directory_name: string } - sidebar_version: { + sidebar_footer: { session_id: string - version: string - } - sidebar_bottom: { - session_id: string - directory: string - directory_parent: string - directory_name: string - version: string - show_getting_started: boolean - has_providers: boolean - dismissed: boolean } } @@ -284,10 +331,12 @@ declare module "@opencode-ai/plugin/tui" { theme: TuiTheme } - export type TuiSlotPlugin = CorePlugin + export type TuiSlotPlugin = Omit, "id"> & { + id?: never + } export type TuiSlots = { - register: (plugin: TuiSlotPlugin) => () => void + register: (plugin: TuiSlotPlugin) => string } export type TuiEventBus = { @@ -325,8 +374,15 @@ declare module "@opencode-ai/plugin/tui" { state: TuiPluginState } + export type TuiWorkspace = { + current: () => string | undefined + set: (workspaceID?: string) => void + } + export type TuiHostPluginApi = TuiApi & { client: ReturnType + scopedClient: (workspaceID?: string) => ReturnType + workspace: TuiWorkspace event: TuiEventBus renderer: Renderer } @@ -338,13 +394,12 @@ declare module "@opencode-ai/plugin/tui" { export type TuiPlugin = ( api: TuiPluginApi, - options: Record | undefined, + options: PluginOptions | undefined, meta: TuiPluginMeta, ) => Promise export type TuiPluginModule = { server?: ServerPlugin tui?: TuiPlugin - slots?: TuiSlotPlugin } } From 1d4bc3c5a1ae8e0893bd0e4c3f3be50adaa9cbd2 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Fri, 27 Mar 2026 15:32:30 -0400 Subject: [PATCH 27/40] refactor(tui): align plugin with flat TuiPluginApi and updated slot/route contracts --- index.ts | 3 + tui/data/context.ts | 10 +- tui/dcp-probe.tsx | 1 - tui/index.tsx | 7 +- tui/package.json | 2 +- tui/routes/summary.tsx | 23 ++-- .../{sidebar-top.tsx => sidebar-content.tsx} | 2 +- tui/types/opencode-plugin-tui.d.ts | 122 ++++++++++++------ 8 files changed, 109 insertions(+), 61 deletions(-) delete mode 100644 tui/dcp-probe.tsx rename tui/slots/{sidebar-top.tsx => sidebar-content.tsx} (99%) diff --git a/index.ts b/index.ts index d327ef43..59dd003a 100644 --- a/index.ts +++ b/index.ts @@ -18,6 +18,8 @@ import { } from "./lib/hooks" import { configureClientAuth, isSecureMode } from "./lib/auth" +const id = "opencode-dynamic-context-pruning" + let tuiPlugin: Record = {} try { tuiPlugin = (await import("./tui/index")).default @@ -138,6 +140,7 @@ const server: Plugin = (async (ctx) => { }) satisfies Plugin export default { + id, server, ...tuiPlugin, } diff --git a/tui/data/context.ts b/tui/data/context.ts index d258b75d..a82af9de 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -38,7 +38,7 @@ export const createPlaceholderContextSnapshot = ( function cleanBlockSummary(raw: string): string { return raw .replace(/^\s*\[Compressed conversation section\]\s*/i, "") - .replace(/\s*b\d+<\/dcp-message-id>\s*$/i, "") + .replace(/\s*b\d+<\/dcp-message-id>\s*$/i, "") .trim() } @@ -73,9 +73,11 @@ const loadContextSnapshot = async ( } const messagesResult = await client.session.messages({ sessionID }) - const messages = Array.isArray(messagesResult.data) - ? (messagesResult.data as WithParts[]) - : ([] as WithParts[]) + const rawMessages = + messagesResult && typeof messagesResult === "object" && "data" in messagesResult + ? messagesResult.data + : messagesResult + const messages = Array.isArray(rawMessages) ? (rawMessages as WithParts[]) : ([] as WithParts[]) const { state, persisted } = await buildState(sessionID, messages, logger) const [{ breakdown, messageStatuses }, aggregated] = await Promise.all([ diff --git a/tui/dcp-probe.tsx b/tui/dcp-probe.tsx deleted file mode 100644 index a84c7b34..00000000 --- a/tui/dcp-probe.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./index" diff --git a/tui/index.tsx b/tui/index.tsx index a159bc56..b277888d 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -2,7 +2,7 @@ import type { TuiPlugin } from "@opencode-ai/plugin/tui" import { getConfigForDirectory } from "../lib/config" import { Logger } from "../lib/logger" -import { createSidebarTopSlot } from "./slots/sidebar-top" +import { createSidebarContentSlot } from "./slots/sidebar-content" import { createSummaryRoute } from "./routes/summary" import { NAMES } from "./shared/names" @@ -22,10 +22,13 @@ const tui: TuiPlugin = async (api) => { api.route.register([createSummaryRoute(api)]) if (config.tui.sidebar) { - api.slots.register(createSidebarTopSlot(api, NAMES, logger)) + api.slots.register(createSidebarContentSlot(api, NAMES, logger)) } } +const id = "opencode-dynamic-context-pruning" + export default { + id, tui, } diff --git a/tui/package.json b/tui/package.json index e59f69fd..d435eaa1 100644 --- a/tui/package.json +++ b/tui/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "name": "dcp-tui-probe-local", + "name": "dcp-tui-local", "private": true, "type": "module", "dependencies": { diff --git a/tui/routes/summary.tsx b/tui/routes/summary.tsx index 8cd42fff..d3d01f8b 100644 --- a/tui/routes/summary.tsx +++ b/tui/routes/summary.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource @opentui/solid */ import { createMemo, createSignal, For, Show } from "solid-js" import { useKeyboard } from "@opentui/solid" -import type { TuiApi } from "@opencode-ai/plugin/tui" +import type { TuiPluginApi } from "@opencode-ai/plugin/tui" import { getPalette, type DcpPalette } from "../shared/theme" import { LABEL, NAMES } from "../shared/names" @@ -23,6 +23,9 @@ interface ParsedSummary { sections: CollapsibleSection[] } +// These patterns must stay in sync with the exact headings produced by +// lib/compress (compactSummary output). If compression wording changes, +// update these patterns accordingly. const SECTION_HEADINGS: { pattern: RegExp; label: string }[] = [ { pattern: /\n*The following user messages were sent in this conversation verbatim:/, @@ -99,13 +102,9 @@ function CollapsibleSectionRow(props: { section: CollapsibleSection; palette: Dc ) } -function SummaryScreen(props: { api: TuiApi }) { - const params = createMemo(() => { - const current = props.api.route.current - return ("params" in current ? current.params : {}) as SummaryRouteParams - }) +function SummaryScreen(props: { api: TuiPluginApi; params: SummaryRouteParams }) { const palette = createMemo(() => getPalette(props.api.theme.current as Record)) - const parsed = createMemo(() => parseSummary(params().summary || "")) + const parsed = createMemo(() => parseSummary(props.params.summary || "")) const keys = props.api.keybind.create({ close: "escape" }) @@ -116,7 +115,7 @@ function SummaryScreen(props: { api: TuiApi }) { if (!matched) return evt.preventDefault() evt.stopPropagation() - const sessionID = params().sessionID + const sessionID = props.params.sessionID if (sessionID) { props.api.route.navigate("session", { sessionID }) } else { @@ -139,7 +138,7 @@ function SummaryScreen(props: { api: TuiApi }) { - {params().topic || "Compression Summary"} + {props.params.topic || "Compression Summary"} @@ -168,7 +167,9 @@ function SummaryScreen(props: { api: TuiApi }) { ) } -export const createSummaryRoute = (api: TuiApi) => ({ +export const createSummaryRoute = (api: TuiPluginApi) => ({ name: NAMES.routes.summary, - render: () => , + render: (input: { params?: Record }) => ( + + ), }) diff --git a/tui/slots/sidebar-top.tsx b/tui/slots/sidebar-content.tsx similarity index 99% rename from tui/slots/sidebar-top.tsx rename to tui/slots/sidebar-content.tsx index 1adbbc91..abde8045 100644 --- a/tui/slots/sidebar-top.tsx +++ b/tui/slots/sidebar-content.tsx @@ -467,7 +467,7 @@ const SidebarContext = (props: { ) } -export const createSidebarTopSlot = ( +export const createSidebarContentSlot = ( api: DcpTuiApi, names: DcpRouteNames, logger: Logger, diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index 8daf59c5..b4346e5f 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -81,7 +81,7 @@ declare module "@opencode-ai/plugin/tui" { } export type TuiDialogProps = { - size?: "medium" | "large" + size?: "medium" | "large" | "xlarge" onClose: () => void children?: JSX.Element } @@ -89,8 +89,8 @@ declare module "@opencode-ai/plugin/tui" { export type TuiDialogStack = { replace: (render: () => JSX.Element, onClose?: () => void) => void clear: () => void - setSize: (size: "medium" | "large") => void - readonly size: "medium" | "large" + setSize: (size: "medium" | "large" | "xlarge") => void + readonly size: "medium" | "large" | "xlarge" readonly depth: number readonly open: boolean } @@ -247,9 +247,17 @@ declare module "@opencode-ai/plugin/tui" { mcp: () => ReadonlyArray } - // Inlined: Pick & NonNullable + // Upstream: Pick & NonNullable & { plugin_enabled?: Record } // PluginConfig (opencode.json schema) is not re-exported by @opencode-ai/plugin at installed version. - type TuiConfigView = Record + // Approximation using known TUI-native config keys: + type TuiConfigView = { + $schema?: string + theme?: string + keybinds?: Record + plugin?: Record + plugin_enabled?: Record + [key: string]: unknown + } type Frozen = Value extends (...args: never[]) => unknown ? Value @@ -263,37 +271,6 @@ declare module "@opencode-ai/plugin/tui" { readonly version: string } - export type TuiApi = { - app: TuiApp - command: { - register: (cb: () => TuiCommand[]) => () => void - trigger: (value: string) => void - } - route: { - register: (routes: TuiRouteDefinition[]) => () => void - navigate: (name: string, params?: Record) => void - readonly current: TuiRouteCurrent - } - ui: { - Dialog: (props: TuiDialogProps) => JSX.Element - DialogAlert: (props: TuiDialogAlertProps) => JSX.Element - DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element - DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element - DialogSelect: (props: TuiDialogSelectProps) => JSX.Element - toast: (input: TuiToast) => void - dialog: TuiDialogStack - } - keybind: { - match: (key: string, evt: ParsedKey) => boolean - print: (key: string) => string - create: (defaults: TuiKeybindMap, overrides?: Record) => TuiKeybindSet - } - readonly tuiConfig: Frozen - kv: TuiKV - state: TuiState - theme: TuiTheme - } - export type TuiSidebarMcpItem = { name: string status: McpStatus["status"] @@ -356,7 +333,7 @@ declare module "@opencode-ai/plugin/tui" { export type TuiPluginState = "first" | "updated" | "same" export type TuiPluginEntry = { - name: string + id: string source: "file" | "npm" | "internal" spec: string target: string @@ -374,21 +351,84 @@ declare module "@opencode-ai/plugin/tui" { state: TuiPluginState } + export type TuiPluginStatus = { + id: string + source: "file" | "npm" | "internal" + spec: string + target: string + enabled: boolean + active: boolean + } + + export type TuiPluginInstallOptions = { + global?: boolean + } + + export type TuiPluginInstallResult = + | { + ok: true + dir: string + tui: boolean + } + | { + ok: false + message: string + missing?: boolean + } + export type TuiWorkspace = { current: () => string | undefined set: (workspaceID?: string) => void } - export type TuiHostPluginApi = TuiApi & { + // Flat TuiPluginApi matching upstream spec from PR #19347. + // Previous local shim split this into TuiApi -> TuiHostPluginApi -> TuiPluginApi; + // the upstream API is a single flat type. + export type TuiPluginApi = { + app: TuiApp + command: { + register: (cb: () => TuiCommand[]) => () => void + trigger: (value: string) => void + } + route: { + register: (routes: TuiRouteDefinition[]) => () => void + navigate: (name: string, params?: Record) => void + readonly current: TuiRouteCurrent + } + ui: { + Dialog: (props: TuiDialogProps) => JSX.Element + DialogAlert: (props: TuiDialogAlertProps) => JSX.Element + DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element + DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element + DialogSelect: (props: TuiDialogSelectProps) => JSX.Element + toast: (input: TuiToast) => void + dialog: TuiDialogStack + } + keybind: { + match: (key: string, evt: ParsedKey) => boolean + print: (key: string) => string + create: (defaults: TuiKeybindMap, overrides?: Record) => TuiKeybindSet + } + readonly tuiConfig: Frozen + kv: TuiKV + state: TuiState + theme: TuiTheme client: ReturnType scopedClient: (workspaceID?: string) => ReturnType workspace: TuiWorkspace event: TuiEventBus renderer: Renderer - } - - export type TuiPluginApi = TuiHostPluginApi & { slots: TuiSlots + plugins: { + list: () => ReadonlyArray + activate: (id: string) => Promise + deactivate: (id: string) => Promise + add: (spec: string) => Promise + install: ( + spec: string, + options?: TuiPluginInstallOptions, + ) => Promise + } lifecycle: TuiLifecycle } From 3311950f96e6c321b95411bf95c1f491751c6cd6 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Fri, 27 Mar 2026 16:37:18 -0400 Subject: [PATCH 28/40] fix(ui): revert compress notification to match dev --- lib/compress/pipeline.ts | 12 +++++------- lib/ui/notification.ts | 1 - lib/ui/utils.ts | 5 +---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/compress/pipeline.ts b/lib/compress/pipeline.ts index b6de2e07..9a7f0fa2 100644 --- a/lib/compress/pipeline.ts +++ b/lib/compress/pipeline.ts @@ -4,7 +4,7 @@ import { saveSessionState } from "../state/persistence" import { assignMessageRefs } from "../message-ids" import { isIgnoredUserMessage } from "../messages/query" import { deduplicate, purgeErrors } from "../strategies" -import { getCurrentParams, getCurrentTokenUsage } from "../token-utils" +import { getCurrentParams } from "../token-utils" import { sendCompressNotification } from "../ui/notification" import type { ToolContext } from "./types" import { buildSearchContext, fetchSessionMessages } from "./search" @@ -86,10 +86,9 @@ export async function finalizeSession( await saveSessionState(ctx.state, ctx.logger) const params = getCurrentParams(ctx.state, rawMessages, ctx.logger) - const totalSessionTokens = getCurrentTokenUsage(ctx.state, rawMessages) - const sessionMessages = rawMessages.filter( - (msg) => !(msg.info.role === "user" && isIgnoredUserMessage(msg)), - ) + const sessionMessageIds = rawMessages + .filter((msg) => !isIgnoredUserMessage(msg)) + .map((msg) => msg.info.id) await sendCompressNotification( ctx.client, @@ -99,8 +98,7 @@ export async function finalizeSession( toolCtx.sessionID, entries, batchTopic, - totalSessionTokens, - sessionMessages, + sessionMessageIds, params, ) } diff --git a/lib/ui/notification.ts b/lib/ui/notification.ts index e65c070c..e4303a58 100644 --- a/lib/ui/notification.ts +++ b/lib/ui/notification.ts @@ -249,7 +249,6 @@ export async function sendCompressNotification( message = `${notificationHeader} — ${compressionLabel}` } else { message = notificationHeader - const activePrunedMessages = new Map() for (const [messageId, entry] of state.prune.messages.byMessageId) { if (entry.activeBlockIds.length > 0) { diff --git a/lib/ui/utils.ts b/lib/ui/utils.ts index 9cd92ef1..286ff967 100644 --- a/lib/ui/utils.ts +++ b/lib/ui/utils.ts @@ -144,10 +144,7 @@ export function formatStatsHeader(totalTokensSaved: number, pruneTokenCounter: n export function formatTokenCount(tokens: number, compact?: boolean): string { const suffix = compact ? "" : " tokens" - if (tokens >= 100_000) { - return `${Math.round(tokens / 1000)}K` + suffix - } - if (tokens >= 10_000 || (compact && tokens >= 1000)) { + if (tokens >= 1000) { return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K") + suffix } return tokens.toString() + suffix From 465c64d182d1f0c34c7fb55c660c8f3aa2a4e89b Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sat, 28 Mar 2026 23:10:53 -0400 Subject: [PATCH 29/40] fix(tui): restore dedicated tui entrypoint --- index.ts | 6 ------ package-lock.json | 21 +++++++++++++++------ package.json | 25 ++++++++++++++++++++++++- tui/index.tsx | 2 +- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/index.ts b/index.ts index 59dd003a..c8944a13 100644 --- a/index.ts +++ b/index.ts @@ -20,11 +20,6 @@ import { configureClientAuth, isSecureMode } from "./lib/auth" const id = "opencode-dynamic-context-pruning" -let tuiPlugin: Record = {} -try { - tuiPlugin = (await import("./tui/index")).default -} catch {} - const server: Plugin = (async (ctx) => { const config = getConfig(ctx) @@ -142,5 +137,4 @@ const server: Plugin = (async (ctx) => { export default { id, server, - ...tuiPlugin, } diff --git a/package-lock.json b/package-lock.json index 8e9bd0b5..c1914c76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,13 +21,26 @@ "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", + "solid-js": "1.9.9", "tsx": "^4.21.0", "typescript": "^6.0.2" }, "peerDependencies": { "@opencode-ai/plugin": ">=1.2.0", "@opentui/core": ">=0.1.87", - "@opentui/solid": ">=0.0.0-20260307" + "@opentui/solid": ">=0.0.0-20260307", + "solid-js": ">=1.9.9 <2" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } } }, "node_modules/@ampproject/remapping": { @@ -2116,8 +2129,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -3032,7 +3044,6 @@ "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -3043,7 +3054,6 @@ "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3073,7 +3083,6 @@ "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", diff --git a/package.json b/package.json index bf5ff6e1..3451f908 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,16 @@ "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./tui": { + "types": "./dist/tui/index.d.ts", + "import": "./dist/tui/index.js" + } + }, "scripts": { "clean": "rm -rf dist", "build": "npm run clean && tsc", @@ -40,7 +50,19 @@ "peerDependencies": { "@opencode-ai/plugin": ">=1.2.0", "@opentui/core": ">=0.1.87", - "@opentui/solid": ">=0.0.0-20260307" + "@opentui/solid": ">=0.0.0-20260307", + "solid-js": ">=1.9.9 <2" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } }, "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", @@ -55,6 +77,7 @@ "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", + "solid-js": "1.9.9", "tsx": "^4.21.0", "typescript": "^6.0.2" }, diff --git a/tui/index.tsx b/tui/index.tsx index b277888d..596a72b1 100644 --- a/tui/index.tsx +++ b/tui/index.tsx @@ -7,7 +7,7 @@ import { createSummaryRoute } from "./routes/summary" import { NAMES } from "./shared/names" const tui: TuiPlugin = async (api) => { - const config = getConfigForDirectory(process.cwd(), (title, message) => { + const config = getConfigForDirectory(api.state.path.directory, (title, message) => { api.ui.toast({ title, message, From a578bb706f624450830968fd7a062e4cad5d781b Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sat, 28 Mar 2026 23:10:53 -0400 Subject: [PATCH 30/40] fix(types): refresh vendored tui plugin shim --- tui/types/opencode-plugin-tui.d.ts | 46 ++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/tui/types/opencode-plugin-tui.d.ts b/tui/types/opencode-plugin-tui.d.ts index b4346e5f..9c43070e 100644 --- a/tui/types/opencode-plugin-tui.d.ts +++ b/tui/types/opencode-plugin-tui.d.ts @@ -17,7 +17,6 @@ declare module "@opencode-ai/plugin/tui" { import type { CliRenderer, ParsedKey, RGBA } from "@opentui/core" import type { JSX, SolidPlugin } from "@opentui/solid" - import type { Plugin as ServerPlugin } from "@opencode-ai/plugin" // PluginOptions = Record — not yet exported by installed @opencode-ai/plugin type PluginOptions = Record @@ -113,6 +112,8 @@ declare module "@opencode-ai/plugin/tui" { description?: () => JSX.Element placeholder?: string value?: string + busy?: boolean + busyText?: string onConfirm?: (value: string) => void onCancel?: () => void } @@ -139,6 +140,19 @@ declare module "@opencode-ai/plugin/tui" { current?: Value } + export type TuiPromptProps = { + workspaceID?: string + visible?: boolean + disabled?: boolean + onSubmit?: () => void + hint?: JSX.Element + showPlaceholder?: boolean + placeholders?: { + normal?: string[] + shell?: string[] + } + } + export type TuiToast = { variant?: "info" | "success" | "warning" | "error" title?: string @@ -247,14 +261,13 @@ declare module "@opencode-ai/plugin/tui" { mcp: () => ReadonlyArray } - // Upstream: Pick & NonNullable & { plugin_enabled?: Record } - // PluginConfig (opencode.json schema) is not re-exported by @opencode-ai/plugin at installed version. - // Approximation using known TUI-native config keys: + // Current upstream shape is based on the host TUI config plus plugin tuples. + // PluginConfig is not re-exported by the installed @opencode-ai/plugin version. type TuiConfigView = { $schema?: string theme?: string keybinds?: Record - plugin?: Record + plugin?: Array plugin_enabled?: Record [key: string]: unknown } @@ -290,6 +303,9 @@ declare module "@opencode-ai/plugin/tui" { export type TuiSlotMap = { app: {} home_logo: {} + home_prompt: { + workspace_id?: string + } home_bottom: {} sidebar_title: { session_id: string @@ -381,10 +397,8 @@ declare module "@opencode-ai/plugin/tui" { set: (workspaceID?: string) => void } - // Flat TuiPluginApi matching upstream spec from PR #19347. - // Previous local shim split this into TuiApi -> TuiHostPluginApi -> TuiPluginApi; - // the upstream API is a single flat type. - export type TuiPluginApi = { + // Flat TuiPluginApi matching the current upstream TUI plugin API. + export type TuiPluginApi = { app: TuiApp command: { register: (cb: () => TuiCommand[]) => () => void @@ -401,6 +415,7 @@ declare module "@opencode-ai/plugin/tui" { DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element DialogSelect: (props: TuiDialogSelectProps) => JSX.Element + Prompt: (props: TuiPromptProps) => JSX.Element toast: (input: TuiToast) => void dialog: TuiDialogStack } @@ -417,7 +432,7 @@ declare module "@opencode-ai/plugin/tui" { scopedClient: (workspaceID?: string) => ReturnType workspace: TuiWorkspace event: TuiEventBus - renderer: Renderer + renderer: CliRenderer slots: TuiSlots plugins: { list: () => ReadonlyArray @@ -432,14 +447,15 @@ declare module "@opencode-ai/plugin/tui" { lifecycle: TuiLifecycle } - export type TuiPlugin = ( - api: TuiPluginApi, + export type TuiPlugin = ( + api: TuiPluginApi, options: PluginOptions | undefined, meta: TuiPluginMeta, ) => Promise - export type TuiPluginModule = { - server?: ServerPlugin - tui?: TuiPlugin + export type TuiPluginModule = { + id?: string + tui: TuiPlugin + server?: never } } From 8be21b2bf9c78000442862058bb4cc8f268174ad Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 00:02:01 -0400 Subject: [PATCH 31/40] feat: add plugin install targets --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 3451f908..660cfdae 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,10 @@ "import": "./dist/tui/index.js" } }, + "oc-plugin": [ + "server", + "tui" + ], "scripts": { "clean": "rm -rf dist", "build": "npm run clean && tsc", From 3ef58aa66e0dc4e7bce2a72922068f43ec330857 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 01:37:37 -0400 Subject: [PATCH 32/40] fix(tui): show summary token totals --- tui/data/context.ts | 5 ++++- tui/shared/types.ts | 1 + tui/slots/sidebar-content.tsx | 11 ++++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tui/data/context.ts b/tui/data/context.ts index a82af9de..2dec84a4 100644 --- a/tui/data/context.ts +++ b/tui/data/context.ts @@ -7,6 +7,7 @@ import { } from "../../lib/state" import { findLastCompactionTimestamp, + getActiveSummaryTokenUsage, loadPruneMap, loadPruneMessagesState, } from "../../lib/state/utils" @@ -24,6 +25,7 @@ export const createPlaceholderContextSnapshot = ( ): DcpContextSnapshot => ({ sessionID, breakdown: emptyBreakdown(), + activeSummaryTokens: 0, persisted: { available: false, activeBlockCount: 0, @@ -38,7 +40,7 @@ export const createPlaceholderContextSnapshot = ( function cleanBlockSummary(raw: string): string { return raw .replace(/^\s*\[Compressed conversation section\]\s*/i, "") - .replace(/\s*b\d+<\/dcp-message-id>\s*$/i, "") + .replace(/(?:\r?\n)*b\d+<\/dcp-message-id>\s*$/i, "") .trim() } @@ -103,6 +105,7 @@ const loadContextSnapshot = async ( return { sessionID, breakdown, + activeSummaryTokens: getActiveSummaryTokenUsage(state), persisted: { available: !!persisted, activeBlockCount: state.prune.messages.activeBlockIds.size, diff --git a/tui/shared/types.ts b/tui/shared/types.ts index f1128fc4..36db52df 100644 --- a/tui/shared/types.ts +++ b/tui/shared/types.ts @@ -29,6 +29,7 @@ export interface DcpAllTimeStats { export interface DcpContextSnapshot { sessionID?: string breakdown: DcpContextBreakdown + activeSummaryTokens: number persisted: DcpPersistedSummary messageStatuses: DcpMessageStatus[] allTimeStats: DcpAllTimeStats diff --git a/tui/slots/sidebar-content.tsx b/tui/slots/sidebar-content.tsx index abde8045..7d39429b 100644 --- a/tui/slots/sidebar-content.tsx +++ b/tui/slots/sidebar-content.tsx @@ -395,9 +395,14 @@ const SidebarContext = (props: { {blocks().length > 0 ? ( <> - - Compressed Topics - + + + Compressed Summaries + + + {`~${compactTokenCount(snapshot().activeSummaryTokens)}`} + + {blocks().map((block) => ( From ff9be14a1c3c8c3d5901ecef139ca156a57695d4 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 01:37:37 -0400 Subject: [PATCH 33/40] docs: add tui plugin install config --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index f3a9480f..d6a99dce 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,26 @@ opencode plugin @tarquinen/opencode-dcp@latest --global This installs the package and adds it to your global OpenCode config. +Or add it to your OpenCode configs manually: + +```jsonc +// opencode.jsonc +{ + "plugin": ["@tarquinen/opencode-dcp@latest"], +} +``` + +```jsonc +// tui.jsonc +{ + "plugin": ["@tarquinen/opencode-dcp@latest"], +} +``` + +Using `@latest` ensures you always get the newest version automatically when OpenCode starts. + +Restart OpenCode. The plugin will automatically start optimizing your sessions. + ## How It Works DCP reduces context size through a compress tool and automatic cleanup. Your session history is never modified — DCP replaces pruned content with placeholders before sending requests to your LLM. From a24207285f5219afb355cbb123824216c54a0c2b Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 03:56:47 -0400 Subject: [PATCH 34/40] v3.2.0-beta0 - Bump version --- README.md | 6 +++--- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d6a99dce..989021e7 100644 --- a/README.md +++ b/README.md @@ -22,18 +22,18 @@ Or add it to your OpenCode configs manually: ```jsonc // opencode.jsonc { - "plugin": ["@tarquinen/opencode-dcp@latest"], + "plugin": ["@tarquinen/opencode-dcp@beta"], } ``` ```jsonc // tui.jsonc { - "plugin": ["@tarquinen/opencode-dcp@latest"], + "plugin": ["@tarquinen/opencode-dcp@beta"], } ``` -Using `@latest` ensures you always get the newest version automatically when OpenCode starts. +Using `@beta` keeps you on the current prerelease channel when OpenCode starts. Restart OpenCode. The plugin will automatically start optimizing your sessions. diff --git a/package-lock.json b/package-lock.json index c1914c76..d2176383 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tarquinen/opencode-dcp", - "version": "3.1.5", + "version": "3.2.0-beta0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "3.1.5", + "version": "3.2.0-beta0", "license": "AGPL-3.0-or-later", "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", diff --git a/package.json b/package.json index 660cfdae..d55999b8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "3.1.5", + "version": "3.2.0-beta0", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", From 308ac0ef266b6227792e553220814cb86449d117 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 04:17:30 -0400 Subject: [PATCH 35/40] v3.2.1-beta0 - Fix TUI runtime deps --- package-lock.json | 225 ++-------------------------------------------- package.json | 24 ++--- 2 files changed, 12 insertions(+), 237 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2176383..09d16ff2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,53 +1,38 @@ { "name": "@tarquinen/opencode-dcp", - "version": "3.2.0-beta0", + "version": "3.2.1-beta0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "3.2.0-beta0", + "version": "3.2.1-beta0", "license": "AGPL-3.0-or-later", "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.3.2", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", "fuzzball": "^2.2.3", "jsonc-parser": "^3.3.1", + "solid-js": "1.9.9", "zod": "^4.3.6" }, "devDependencies": { "@opencode-ai/plugin": "^1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", - "solid-js": "1.9.9", "tsx": "^4.21.0", "typescript": "^6.0.2" }, "peerDependencies": { - "@opencode-ai/plugin": ">=1.2.0", - "@opentui/core": ">=0.1.87", - "@opentui/solid": ">=0.0.0-20260307", - "solid-js": ">=1.9.9 <2" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - }, - "solid-js": { - "optional": true - } + "@opencode-ai/plugin": ">=1.2.0" } }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -86,7 +71,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -101,7 +85,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -111,7 +94,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -142,7 +124,6 @@ "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", @@ -159,7 +140,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -172,7 +152,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -189,7 +168,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -211,7 +189,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -221,7 +198,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.5", @@ -235,7 +211,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", @@ -249,7 +224,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", @@ -267,7 +241,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -280,7 +253,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -290,7 +262,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", @@ -308,7 +279,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -322,7 +292,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -332,7 +301,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -342,7 +310,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -352,7 +319,6 @@ "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", @@ -366,7 +332,6 @@ "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -382,7 +347,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -398,7 +362,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -414,7 +377,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.6", @@ -431,7 +393,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -451,7 +412,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -471,7 +431,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -486,7 +445,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -505,7 +463,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -519,7 +476,6 @@ "version": "0.17.3", "resolved": "https://registry.npmjs.org/@dimforge/rapier2d-simd-compat/-/rapier2d-simd-compat-0.17.3.tgz", "integrity": "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==", - "dev": true, "license": "Apache-2.0", "optional": true }, @@ -969,7 +925,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/file-ops": "1.6.0", @@ -988,7 +943,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/plugin-resize": "1.6.0", @@ -1004,7 +958,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1014,7 +967,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1030,7 +982,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1046,7 +997,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1061,7 +1011,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1076,7 +1025,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1091,7 +1039,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1106,7 +1053,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1116,7 +1062,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1130,7 +1075,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1144,7 +1088,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1154,7 +1097,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1171,7 +1113,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1181,7 +1122,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1199,7 +1139,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1209,7 +1148,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1226,7 +1164,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1236,7 +1173,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1252,7 +1188,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1262,7 +1197,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1277,7 +1211,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1287,7 +1220,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0" @@ -1300,7 +1232,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1315,7 +1246,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1325,7 +1255,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1339,7 +1268,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1349,7 +1277,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1371,7 +1298,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1385,7 +1311,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1395,7 +1320,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1417,7 +1341,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1427,7 +1350,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", - "dev": true, "license": "MIT", "dependencies": { "image-q": "^4.0.0", @@ -1441,7 +1363,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1451,7 +1372,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1466,7 +1386,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1476,7 +1395,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1494,7 +1412,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1504,7 +1421,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -1522,7 +1438,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1532,7 +1447,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", - "dev": true, "license": "MIT", "dependencies": { "zod": "^3.23.8" @@ -1545,7 +1459,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -1555,7 +1468,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/types": "1.6.0", @@ -1569,7 +1481,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1580,7 +1491,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1590,14 +1500,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1635,7 +1543,6 @@ "version": "0.0.0-20260307-536c401b", "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.0.0-20260307-536c401b.tgz", "integrity": "sha512-e/n7hCtpOzS57X9llODu0SUXCQBWSxHQeTA0iuL7j0nhSFgM6KpL8kJ7VQBU1EEn33pytA0udbfKSJ6sqWmEJg==", - "dev": true, "license": "MIT", "dependencies": { "bun-ffi-structs": "0.1.2", @@ -1667,7 +1574,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1681,7 +1587,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1695,7 +1600,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1709,7 +1613,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1723,7 +1626,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1737,7 +1639,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1748,7 +1649,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.1.2.tgz", "integrity": "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w==", - "dev": true, "license": "MIT", "peerDependencies": { "typescript": "^5" @@ -1758,7 +1658,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -1773,7 +1672,6 @@ "version": "0.0.0-20260307-536c401b", "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.0.0-20260307-536c401b.tgz", "integrity": "sha512-wfItFCVBsP2iWvFgj2/lVRN7/O7R5eu9NReY4Wl34Z+c9d7P6FVSa1xOriziTPysukW1OhFe8MNN7MIaggYdHg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "7.28.0", @@ -1792,7 +1690,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { @@ -1809,7 +1706,6 @@ "version": "0.1.69", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", - "dev": true, "license": "BSD-3-Clause", "optional": true }, @@ -1817,7 +1713,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -1830,14 +1725,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "dev": true, "license": "MIT" }, "node_modules/await-to-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1847,7 +1740,6 @@ "version": "0.40.6", "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.6.tgz", "integrity": "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "7.18.6", @@ -1864,7 +1756,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.18.6" @@ -1877,7 +1768,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", - "dev": true, "license": "MIT", "dependencies": { "find-babel-config": "^2.1.1", @@ -1891,7 +1781,6 @@ "version": "1.9.9", "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", - "dev": true, "license": "MIT", "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" @@ -1910,14 +1799,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1938,7 +1825,6 @@ "version": "2.10.11", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.11.tgz", "integrity": "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -1951,14 +1837,12 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", - "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -1968,7 +1852,6 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2002,7 +1885,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "funding": [ { "type": "github", @@ -2027,7 +1909,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/bun-webgpu/-/bun-webgpu-0.1.5.tgz", "integrity": "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA==", - "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2047,7 +1928,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2061,7 +1941,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2075,7 +1954,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2089,7 +1967,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2100,7 +1977,6 @@ "version": "1.0.30001781", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2121,21 +1997,18 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2153,7 +2026,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -2163,14 +2035,12 @@ "version": "1.5.328", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", - "dev": true, "license": "ISC" }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2225,7 +2095,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2235,7 +2104,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2245,7 +2113,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -2254,14 +2121,12 @@ "node_modules/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "dev": true + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" }, "node_modules/file-type": { "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "dev": true, "license": "MIT", "dependencies": { "readable-web-to-node-stream": "^3.0.0", @@ -2279,7 +2144,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.3" @@ -2289,7 +2153,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^3.0.0" @@ -2302,7 +2165,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -2324,7 +2186,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2345,7 +2206,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2368,7 +2228,6 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", - "dev": true, "license": "MIT", "dependencies": { "image-q": "^4.0.0", @@ -2380,7 +2239,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "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", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -2399,7 +2257,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2418,14 +2275,12 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", - "dev": true, "license": "MIT" }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -2446,7 +2301,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "16.9.1" @@ -2456,14 +2310,12 @@ "version": "16.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2479,7 +2331,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", - "dev": true, "license": "MIT", "dependencies": { "@jimp/core": "1.6.0", @@ -2518,21 +2369,18 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2545,7 +2393,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -2564,7 +2411,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^3.0.0", @@ -2584,7 +2430,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -2594,7 +2439,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", - "dev": true, "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -2607,7 +2451,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -2620,7 +2463,6 @@ "version": "8.0.7", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -2636,7 +2478,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -2646,28 +2487,24 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.36", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, "license": "MIT" }, "node_modules/omggif": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true, "license": "MIT" }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -2683,7 +2520,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.0.0" @@ -2696,7 +2532,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2706,28 +2541,24 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parse-bmfont-ascii": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "dev": true, "license": "MIT" }, "node_modules/parse-bmfont-binary": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "dev": true, "license": "MIT" }, "node_modules/parse-bmfont-xml": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", - "dev": true, "license": "MIT", "dependencies": { "xml-parse-from-string": "^1.0.0", @@ -2738,7 +2569,6 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -2751,7 +2581,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2764,7 +2593,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2774,14 +2602,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -2798,14 +2624,12 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -2815,7 +2639,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2829,14 +2652,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/pixelmatch": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, "license": "ISC", "dependencies": { "pngjs": "^6.0.0" @@ -2849,7 +2670,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.13.0" @@ -2859,7 +2679,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, "license": "MIT", "dependencies": { "find-up": "^3.0.0" @@ -2872,7 +2691,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/planck/-/planck-1.4.3.tgz", "integrity": "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2886,7 +2704,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.19.0" @@ -2912,7 +2729,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -2922,7 +2738,6 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "dev": true, "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -2939,7 +2754,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "dev": true, "license": "MIT", "dependencies": { "readable-stream": "^4.7.0" @@ -2956,14 +2770,12 @@ "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", - "dev": true, "license": "MIT" }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -2994,14 +2806,12 @@ "version": "0.4.9", "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", - "dev": true, "license": "MIT" }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -3022,7 +2832,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -3032,7 +2841,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3042,7 +2850,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3052,7 +2859,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3071,7 +2877,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.4.tgz", "integrity": "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg==", - "dev": true, "license": "MIT", "engines": { "node": ">=20.12.2" @@ -3081,7 +2886,6 @@ "version": "1.9.9", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.1.0", @@ -3093,7 +2897,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/stage-js/-/stage-js-1.0.1.tgz", "integrity": "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw==", - "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -3105,7 +2908,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -3115,7 +2917,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "dev": true, "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", @@ -3133,7 +2934,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3146,7 +2946,6 @@ "version": "0.177.0", "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", - "dev": true, "license": "MIT", "optional": true }, @@ -3160,14 +2959,12 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true, "license": "MIT" }, "node_modules/token-types": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "dev": true, "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", @@ -3226,7 +3023,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3257,7 +3053,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", - "dev": true, "license": "MIT", "dependencies": { "pako": "^1.0.11" @@ -3267,7 +3062,6 @@ "version": "0.25.10", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", - "dev": true, "license": "MIT", "peer": true, "peerDependencies": { @@ -3283,14 +3077,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "dev": true, "license": "MIT" }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -3304,7 +3096,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4.0" @@ -3314,14 +3105,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", - "dev": true, "license": "MIT" }, "node_modules/zod": { diff --git a/package.json b/package.json index d55999b8..657771ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "3.2.0-beta0", + "version": "3.2.1-beta0", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", @@ -52,36 +52,22 @@ "author": "tarquinen", "license": "AGPL-3.0-or-later", "peerDependencies": { - "@opencode-ai/plugin": ">=1.2.0", - "@opentui/core": ">=0.1.87", - "@opentui/solid": ">=0.0.0-20260307", - "solid-js": ">=1.9.9 <2" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - }, - "solid-js": { - "optional": true - } + "@opencode-ai/plugin": ">=1.2.0" }, "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.3.2", + "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/solid": "0.0.0-20260307-536c401b", "fuzzball": "^2.2.3", "jsonc-parser": "^3.3.1", + "solid-js": "1.9.9", "zod": "^4.3.6" }, "devDependencies": { "@opencode-ai/plugin": "^1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", "@types/node": "^25.5.0", "prettier": "^3.8.1", - "solid-js": "1.9.9", "tsx": "^4.21.0", "typescript": "^6.0.2" }, From d181bda73d24241b0468e8e05b1768d7bb697707 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 12:51:46 -0400 Subject: [PATCH 36/40] v3.2.2-beta0 - Fix npm TUI source entry --- package-lock.json | 4 ++-- package.json | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09d16ff2..762f5712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tarquinen/opencode-dcp", - "version": "3.2.1-beta0", + "version": "3.2.2-beta0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "3.2.1-beta0", + "version": "3.2.2-beta0", "license": "AGPL-3.0-or-later", "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", diff --git a/package.json b/package.json index 657771ba..ba59ed2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "3.2.1-beta0", + "version": "3.2.2-beta0", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", @@ -13,7 +13,7 @@ }, "./tui": { "types": "./dist/tui/index.d.ts", - "import": "./dist/tui/index.js" + "import": "./tui/index.tsx" } }, "oc-plugin": [ @@ -73,6 +73,17 @@ }, "files": [ "dist/", + "lib/analysis/", + "lib/state/", + "lib/config.ts", + "lib/logger.ts", + "lib/messages/query.ts", + "lib/token-utils.ts", + "tui/data/", + "tui/routes/", + "tui/shared/", + "tui/slots/", + "tui/index.tsx", "README.md", "LICENSE" ] From 834fb9eb2e1e1e614715137f5db8fbc02fb77eb0 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 13:36:10 -0400 Subject: [PATCH 37/40] chore: add package directories metadata --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ba59ed2b..101edbc0 100644 --- a/package.json +++ b/package.json @@ -86,5 +86,10 @@ "tui/index.tsx", "README.md", "LICENSE" - ] + ], + "directories": { + "doc": "docs", + "lib": "lib", + "test": "tests" + } } From 92728c65f80eba8b2aea59d4ea92d99b91be2be2 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 13:55:37 -0400 Subject: [PATCH 38/40] fix(tui): bump opentui deps for audit --- package-lock.json | 104 ++++++++++++++++++++-------------------- package.json | 6 +-- tui/package-lock.json | 108 +++++++++++++++++++++--------------------- tui/package.json | 6 +-- 4 files changed, 112 insertions(+), 112 deletions(-) diff --git a/package-lock.json b/package-lock.json index 762f5712..1f66824a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,11 @@ "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", "fuzzball": "^2.2.3", "jsonc-parser": "^3.3.1", - "solid-js": "1.9.9", + "solid-js": "1.9.11", "zod": "^4.3.6" }, "devDependencies": { @@ -1540,9 +1540,9 @@ "license": "MIT" }, "node_modules/@opentui/core": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-e/n7hCtpOzS57X9llODu0SUXCQBWSxHQeTA0iuL7j0nhSFgM6KpL8kJ7VQBU1EEn33pytA0udbfKSJ6sqWmEJg==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.1.92.tgz", + "integrity": "sha512-c+KdYAIH3M8n24RYaor+t7AQtKZ3l84L7xdP7DEaN4xtuYH8W08E6Gi+wUal4g+HSai3HS9irox68yFf0VPAxw==", "license": "MIT", "dependencies": { "bun-ffi-structs": "0.1.2", @@ -1553,12 +1553,12 @@ }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", - "@opentui/core-darwin-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-darwin-x64": "0.0.0-20260307-536c401b", - "@opentui/core-linux-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-linux-x64": "0.0.0-20260307-536c401b", - "@opentui/core-win32-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-win32-x64": "0.0.0-20260307-536c401b", + "@opentui/core-darwin-arm64": "0.1.92", + "@opentui/core-darwin-x64": "0.1.92", + "@opentui/core-linux-arm64": "0.1.92", + "@opentui/core-linux-x64": "0.1.92", + "@opentui/core-win32-arm64": "0.1.92", + "@opentui/core-win32-x64": "0.1.92", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" @@ -1568,9 +1568,9 @@ } }, "node_modules/@opentui/core-darwin-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-y46MUgcjkIqC/IBxErchM51KmLARxudrKqr09Gyy25ry+GUE8gzaEIx6EeMAUnWDWvetMacKgEaNCjtdkfGgDQ==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.1.92.tgz", + "integrity": "sha512-NX/qFRuc7My0pazyOrw9fdTXmU7omXcZzQuHcsaVnwssljaT52UYMrJ7mCKhSo69RhHw0lnGCymTorvz3XBdsA==", "cpu": [ "arm64" ], @@ -1581,9 +1581,9 @@ ] }, "node_modules/@opentui/core-darwin-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-USf14JkFaCyKvn9FfLn6AZv14o5ED7uHBNq4kCmggD28HmqHsklDhGNyDnswUggCworJ6xz7jICZTiKKrSwRbQ==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.1.92.tgz", + "integrity": "sha512-Zb4jn33hOf167llINKLniOabQIycs14LPOBZnQ6l4khbeeTPVJdG8gy9PhlAyIQygDKmRTFncVlP0RP+L6C7og==", "cpu": [ "x64" ], @@ -1594,9 +1594,9 @@ ] }, "node_modules/@opentui/core-linux-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-fzNf0Mv7OjNktJFg17WsvdDD5Ej12eSwPVMProlQFbklC8qCEsZfLJKYq9ExYLRoxHX7wFm9Eq6L7hVaGcn2sA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.1.92.tgz", + "integrity": "sha512-4VA1A91OTMPJ3LkAyaxKEZVJsk5jIc3Kz0gV2vip8p2aGLPpYHHpkFZpXP/FyzsnJzoSGftBeA6ya1GKa5bkXg==", "cpu": [ "arm64" ], @@ -1607,9 +1607,9 @@ ] }, "node_modules/@opentui/core-linux-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-+80TgK5ZhdJvM2+fiCbeCJvXk9De3oNB42wcCtGcwt3x1wyPYAbWIetw6dIGqXIbica/El+7+6Y2DMV06PUUug==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.1.92.tgz", + "integrity": "sha512-tr7va8hfKS1uY+TBmulQBoBlwijzJk56K/U/L9/tbHfW7oJctqxPVwEFHIh1HDcOQ3/UhMMWGvMfeG6cFiK8/A==", "cpu": [ "x64" ], @@ -1620,9 +1620,9 @@ ] }, "node_modules/@opentui/core-win32-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-SBeHYwNpWJlHxMX6+aO8KsatWpMMxOs+LpFA7M2PTV0g81WUHPlxm6kHi6UHpjwYuslvtcYKgUL0IyQs1jbdNA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.1.92.tgz", + "integrity": "sha512-34YM3uPtDjzUVeSnJWIK2J8mxyduzV7f3mYc4Hub0glNpUdM1jjzF2HvvvnrKK5ElzTsIcno3c3lOYT8yvG1Zg==", "cpu": [ "arm64" ], @@ -1633,9 +1633,9 @@ ] }, "node_modules/@opentui/core-win32-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-QIU/s6NrXJLRlTyLJZ/41E3MhVGGZazPrwv6MnMx7LOE/uBQo4OGjcRdsIIkhXYIqNRUIH/Yfd5Hyf6twJpBBA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.1.92.tgz", + "integrity": "sha512-uk442kA2Vn0mmJHHqk5sPM+Zai/AN9sgl7egekhoEOUx2VK3gxftKsVlx2YVpCHTvTE/S+vnD2WpQaJk2SNjww==", "cpu": [ "x64" ], @@ -1669,21 +1669,21 @@ } }, "node_modules/@opentui/solid": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-wfItFCVBsP2iWvFgj2/lVRN7/O7R5eu9NReY4Wl34Z+c9d7P6FVSa1xOriziTPysukW1OhFe8MNN7MIaggYdHg==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.1.92.tgz", + "integrity": "sha512-0Sx1+6zRpmMJ5oDEY0JS9b9+eGd/Q0fPndNllrQNnp7w2FCjpXmvHdBdq+pFI6kFp01MHq2ZOkUU5zX5/9YMSQ==", "license": "MIT", "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", - "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/core": "0.1.92", "babel-plugin-module-resolver": "5.0.2", - "babel-preset-solid": "1.9.9", + "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { - "solid-js": "1.9.9" + "solid-js": "1.9.11" } }, "node_modules/@tokenizer/token": { @@ -1778,16 +1778,16 @@ } }, "node_modules/babel-preset-solid": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", - "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz", + "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==", "license": "MIT", "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.40.1" + "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", - "solid-js": "^1.9.8" + "solid-js": "^1.9.10" }, "peerDependenciesMeta": { "solid-js": { @@ -2847,18 +2847,18 @@ } }, "node_modules/seroval": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", - "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.1.tgz", + "integrity": "sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA==", "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/seroval-plugins": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", - "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.1.tgz", + "integrity": "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==", "license": "MIT", "engines": { "node": ">=10" @@ -2883,14 +2883,14 @@ } }, "node_modules/solid-js": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", - "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", + "version": "1.9.11", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz", + "integrity": "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==", "license": "MIT", "dependencies": { "csstype": "^3.1.0", - "seroval": "~1.3.0", - "seroval-plugins": "~1.3.0" + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" } }, "node_modules/stage-js": { diff --git a/package.json b/package.json index 101edbc0..930126d0 100644 --- a/package.json +++ b/package.json @@ -57,11 +57,11 @@ "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", "fuzzball": "^2.2.3", "jsonc-parser": "^3.3.1", - "solid-js": "1.9.9", + "solid-js": "1.9.11", "zod": "^4.3.6" }, "devDependencies": { diff --git a/tui/package-lock.json b/tui/package-lock.json index ca7a02c3..23559e90 100644 --- a/tui/package-lock.json +++ b/tui/package-lock.json @@ -1,15 +1,15 @@ { - "name": "dcp-tui-probe-local", + "name": "dcp-tui-local", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "dcp-tui-probe-local", + "name": "dcp-tui-local", "dependencies": { "@opencode-ai/plugin": "1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", - "solid-js": "1.9.9" + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", + "solid-js": "1.9.11" } }, "../../../../../src/opencode/node_modules/.bun/solid-js@1.9.10/node_modules/solid-js": { @@ -1055,9 +1055,9 @@ "license": "MIT" }, "node_modules/@opentui/core": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-e/n7hCtpOzS57X9llODu0SUXCQBWSxHQeTA0iuL7j0nhSFgM6KpL8kJ7VQBU1EEn33pytA0udbfKSJ6sqWmEJg==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.1.92.tgz", + "integrity": "sha512-c+KdYAIH3M8n24RYaor+t7AQtKZ3l84L7xdP7DEaN4xtuYH8W08E6Gi+wUal4g+HSai3HS9irox68yFf0VPAxw==", "license": "MIT", "dependencies": { "bun-ffi-structs": "0.1.2", @@ -1068,12 +1068,12 @@ }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", - "@opentui/core-darwin-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-darwin-x64": "0.0.0-20260307-536c401b", - "@opentui/core-linux-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-linux-x64": "0.0.0-20260307-536c401b", - "@opentui/core-win32-arm64": "0.0.0-20260307-536c401b", - "@opentui/core-win32-x64": "0.0.0-20260307-536c401b", + "@opentui/core-darwin-arm64": "0.1.92", + "@opentui/core-darwin-x64": "0.1.92", + "@opentui/core-linux-arm64": "0.1.92", + "@opentui/core-linux-x64": "0.1.92", + "@opentui/core-win32-arm64": "0.1.92", + "@opentui/core-win32-x64": "0.1.92", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" @@ -1083,9 +1083,9 @@ } }, "node_modules/@opentui/core-darwin-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-y46MUgcjkIqC/IBxErchM51KmLARxudrKqr09Gyy25ry+GUE8gzaEIx6EeMAUnWDWvetMacKgEaNCjtdkfGgDQ==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.1.92.tgz", + "integrity": "sha512-NX/qFRuc7My0pazyOrw9fdTXmU7omXcZzQuHcsaVnwssljaT52UYMrJ7mCKhSo69RhHw0lnGCymTorvz3XBdsA==", "cpu": [ "arm64" ], @@ -1096,9 +1096,9 @@ ] }, "node_modules/@opentui/core-darwin-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-USf14JkFaCyKvn9FfLn6AZv14o5ED7uHBNq4kCmggD28HmqHsklDhGNyDnswUggCworJ6xz7jICZTiKKrSwRbQ==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.1.92.tgz", + "integrity": "sha512-Zb4jn33hOf167llINKLniOabQIycs14LPOBZnQ6l4khbeeTPVJdG8gy9PhlAyIQygDKmRTFncVlP0RP+L6C7og==", "cpu": [ "x64" ], @@ -1109,9 +1109,9 @@ ] }, "node_modules/@opentui/core-linux-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-fzNf0Mv7OjNktJFg17WsvdDD5Ej12eSwPVMProlQFbklC8qCEsZfLJKYq9ExYLRoxHX7wFm9Eq6L7hVaGcn2sA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.1.92.tgz", + "integrity": "sha512-4VA1A91OTMPJ3LkAyaxKEZVJsk5jIc3Kz0gV2vip8p2aGLPpYHHpkFZpXP/FyzsnJzoSGftBeA6ya1GKa5bkXg==", "cpu": [ "arm64" ], @@ -1122,9 +1122,9 @@ ] }, "node_modules/@opentui/core-linux-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-+80TgK5ZhdJvM2+fiCbeCJvXk9De3oNB42wcCtGcwt3x1wyPYAbWIetw6dIGqXIbica/El+7+6Y2DMV06PUUug==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.1.92.tgz", + "integrity": "sha512-tr7va8hfKS1uY+TBmulQBoBlwijzJk56K/U/L9/tbHfW7oJctqxPVwEFHIh1HDcOQ3/UhMMWGvMfeG6cFiK8/A==", "cpu": [ "x64" ], @@ -1135,9 +1135,9 @@ ] }, "node_modules/@opentui/core-win32-arm64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-SBeHYwNpWJlHxMX6+aO8KsatWpMMxOs+LpFA7M2PTV0g81WUHPlxm6kHi6UHpjwYuslvtcYKgUL0IyQs1jbdNA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.1.92.tgz", + "integrity": "sha512-34YM3uPtDjzUVeSnJWIK2J8mxyduzV7f3mYc4Hub0glNpUdM1jjzF2HvvvnrKK5ElzTsIcno3c3lOYT8yvG1Zg==", "cpu": [ "arm64" ], @@ -1148,9 +1148,9 @@ ] }, "node_modules/@opentui/core-win32-x64": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-QIU/s6NrXJLRlTyLJZ/41E3MhVGGZazPrwv6MnMx7LOE/uBQo4OGjcRdsIIkhXYIqNRUIH/Yfd5Hyf6twJpBBA==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.1.92.tgz", + "integrity": "sha512-uk442kA2Vn0mmJHHqk5sPM+Zai/AN9sgl7egekhoEOUx2VK3gxftKsVlx2YVpCHTvTE/S+vnD2WpQaJk2SNjww==", "cpu": [ "x64" ], @@ -1161,21 +1161,21 @@ ] }, "node_modules/@opentui/solid": { - "version": "0.0.0-20260307-536c401b", - "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.0.0-20260307-536c401b.tgz", - "integrity": "sha512-wfItFCVBsP2iWvFgj2/lVRN7/O7R5eu9NReY4Wl34Z+c9d7P6FVSa1xOriziTPysukW1OhFe8MNN7MIaggYdHg==", + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.1.92.tgz", + "integrity": "sha512-0Sx1+6zRpmMJ5oDEY0JS9b9+eGd/Q0fPndNllrQNnp7w2FCjpXmvHdBdq+pFI6kFp01MHq2ZOkUU5zX5/9YMSQ==", "license": "MIT", "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", - "@opentui/core": "0.0.0-20260307-536c401b", + "@opentui/core": "0.1.92", "babel-plugin-module-resolver": "5.0.2", - "babel-preset-solid": "1.9.9", + "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { - "solid-js": "1.9.9" + "solid-js": "1.9.11" } }, "node_modules/@tokenizer/token": { @@ -1266,16 +1266,16 @@ } }, "node_modules/babel-preset-solid": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", - "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz", + "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==", "license": "MIT", "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.40.1" + "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", - "solid-js": "^1.9.8" + "solid-js": "^1.9.10" }, "peerDependenciesMeta": { "solid-js": { @@ -2213,18 +2213,18 @@ } }, "node_modules/seroval": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", - "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.1.tgz", + "integrity": "sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA==", "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/seroval-plugins": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", - "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.1.tgz", + "integrity": "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==", "license": "MIT", "engines": { "node": ">=10" @@ -2243,14 +2243,14 @@ } }, "node_modules/solid-js": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", - "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", + "version": "1.9.11", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz", + "integrity": "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==", "license": "MIT", "dependencies": { "csstype": "^3.1.0", - "seroval": "~1.3.0", - "seroval-plugins": "~1.3.0" + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" } }, "node_modules/stage-js": { diff --git a/tui/package.json b/tui/package.json index d435eaa1..fc6ff3ba 100644 --- a/tui/package.json +++ b/tui/package.json @@ -5,8 +5,8 @@ "type": "module", "dependencies": { "@opencode-ai/plugin": "1.3.2", - "@opentui/core": "0.0.0-20260307-536c401b", - "@opentui/solid": "0.0.0-20260307-536c401b", - "solid-js": "1.9.9" + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", + "solid-js": "1.9.11" } } From 319191d4f3a06011ec86e09bc02361f1491647a7 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sun, 29 Mar 2026 14:46:59 -0400 Subject: [PATCH 39/40] docs: add global plugin install command --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 989021e7..51e29965 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Automatically reduces token usage in OpenCode by managing conversation context. Install with the OpenCode CLI: ```bash -opencode plugin @tarquinen/opencode-dcp@latest --global +opencode plugin @tarquinen/opencode-dcp@beta --global ``` -This installs the package and adds it to your global OpenCode config. +This installs the package and adds it to your global OpenCode config files. Or add it to your OpenCode configs manually: From 45be2cdda734efecd3bfe8fc60720eed44a41eb8 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Mon, 30 Mar 2026 14:19:06 -0400 Subject: [PATCH 40/40] docs: simplify installation instructions --- README.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/README.md b/README.md index 51e29965..39dd72e2 100644 --- a/README.md +++ b/README.md @@ -15,27 +15,7 @@ Install with the OpenCode CLI: opencode plugin @tarquinen/opencode-dcp@beta --global ``` -This installs the package and adds it to your global OpenCode config files. - -Or add it to your OpenCode configs manually: - -```jsonc -// opencode.jsonc -{ - "plugin": ["@tarquinen/opencode-dcp@beta"], -} -``` - -```jsonc -// tui.jsonc -{ - "plugin": ["@tarquinen/opencode-dcp@beta"], -} -``` - -Using `@beta` keeps you on the current prerelease channel when OpenCode starts. - -Restart OpenCode. The plugin will automatically start optimizing your sessions. +This installs the package and adds it to your global OpenCode config. ## How It Works