From bbc2e6d8b061afeaa63bd5a0776f0260a94c6f13 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 01/15] feat: preserve protected V2 tool and subagent results Recognize native file paths, Code Mode nested calls, and subagent result metadata. Apply shared protection rules to compression, sweep, deduplication, and failed-tool pruning. --- lib/commands/sweep.ts | 36 ++++---- lib/compress/protected-content.ts | 27 +++--- lib/messages/inject/subagent-results.ts | 2 +- lib/protected-patterns.ts | 29 ++++++- lib/state/tool-cache.ts | 1 + lib/state/types.ts | 1 + lib/strategies/deduplication.ts | 22 +++-- lib/strategies/purge-errors.ts | 22 +++-- lib/subagents/subagent-results.ts | 5 +- tests/v2-protection.test.ts | 110 ++++++++++++++++++++++++ 10 files changed, 192 insertions(+), 63 deletions(-) create mode 100644 tests/v2-protection.test.ts diff --git a/lib/commands/sweep.ts b/lib/commands/sweep.ts index b60b3a9b..15c4a5c8 100644 --- a/lib/commands/sweep.ts +++ b/lib/commands/sweep.ts @@ -17,11 +17,7 @@ import { isIgnoredUserMessage } from "../messages/query" import { buildToolIdList } from "../messages/utils" import { saveSessionState } from "../state/persistence" import { isMessageCompacted } from "../state/utils" -import { - getFilePathsFromParameters, - isFilePathProtected, - isToolNameProtected, -} from "../protected-patterns" +import { isToolProtected } from "../protected-patterns" import { syncToolCache } from "../state/tool-cache" export interface SweepCommandContext { @@ -176,15 +172,18 @@ export async function handleSweepCommand(ctx: SweepCommandContext): Promise 0) { - const filePaths = getFilePathsFromParameters(part.tool, part.state?.input) - if (isFilePathProtected(filePaths, protectedFilePatterns)) { - isToolProtected = true - } - } - - if (isToolProtected) { + if ( + isToolProtected( + part.tool, + part.state.input, + protectedTools, + protectedFilePatterns, + "metadata" in part.state ? part.state.metadata : undefined, + ) + ) { const title = `Tool: ${part.tool}` let output = "" @@ -153,7 +148,7 @@ export async function appendProtectedTools( if ( allowSubAgents && - part.tool === "task" && + (part.tool === "task" || part.tool === "subagent") && part.state?.status === "completed" && typeof part.state?.output === "string" ) { diff --git a/lib/messages/inject/subagent-results.ts b/lib/messages/inject/subagent-results.ts index 8ca3d1d5..42c00590 100644 --- a/lib/messages/inject/subagent-results.ts +++ b/lib/messages/inject/subagent-results.ts @@ -31,7 +31,7 @@ export const injectExtendedSubAgentResults = async ( const parts = Array.isArray(message.parts) ? message.parts : [] for (const part of parts) { - if (part.type !== "tool" || part.tool !== "task" || !part.callID) { + if (part.type !== "tool" || !["task", "subagent"].includes(part.tool) || !part.callID) { continue } if (state.prune.tools.has(part.callID)) { diff --git a/lib/protected-patterns.ts b/lib/protected-patterns.ts index 15d1535c..c2da8c68 100644 --- a/lib/protected-patterns.ts +++ b/lib/protected-patterns.ts @@ -70,7 +70,7 @@ export function getFilePathsFromParameters(tool: string, parameters: unknown): s const params = parameters as Record // 1. apply_patch uses patchText with embedded paths - if (tool === "apply_patch" && typeof params.patchText === "string") { + if ((tool === "apply_patch" || tool === "patch") && typeof params.patchText === "string") { const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g let match while ((match = pathRegex.exec(params.patchText)) !== null) { @@ -97,6 +97,11 @@ export function getFilePathsFromParameters(tool: string, parameters: unknown): s paths.push(params.filePath) } + // V2 file tools use path rather than filePath. + if (["read", "write", "edit"].includes(tool) && typeof params.path === "string") { + paths.push(params.path) + } + // Return unique non-empty paths return [...new Set(paths)].filter((p) => p.length > 0) } @@ -130,3 +135,25 @@ export function isToolNameProtected(toolName: string, patterns: string[]): boole return globPatterns.some((pattern) => matchesGlob(toolName, pattern)) } + +export function isToolProtected( + tool: string, + input: unknown, + tools: string[], + files: string[], + metadata?: Record, +): boolean { + const calls = [{ tool, input }] + // V2 Code Mode keeps nested inputs, but only one combined output. Protect + // that output as a whole when any nested call matches the user's rules. + if (tool === "execute" && Array.isArray(metadata?.toolCalls)) { + for (const call of metadata.toolCalls) { + if (call && typeof call.tool === "string") calls.push(call) + } + } + return calls.some( + (call) => + isToolNameProtected(call.tool, tools) || + isFilePathProtected(getFilePathsFromParameters(call.tool, call.input), files), + ) +} diff --git a/lib/state/tool-cache.ts b/lib/state/tool-cache.ts index 82f4f836..aab67f1c 100644 --- a/lib/state/tool-cache.ts +++ b/lib/state/tool-cache.ts @@ -56,6 +56,7 @@ export function syncToolCache( state.toolParameters.set(part.callID, { tool: part.tool, parameters: part.state?.input ?? {}, + metadata: "metadata" in part.state ? part.state.metadata : undefined, status: part.state.status as ToolStatus | undefined, error: part.state.status === "error" ? part.state.error : undefined, turn: turnCounter, diff --git a/lib/state/types.ts b/lib/state/types.ts index acce05f1..3999e43d 100644 --- a/lib/state/types.ts +++ b/lib/state/types.ts @@ -11,6 +11,7 @@ export type ToolStatus = "pending" | "running" | "completed" | "error" export interface ToolParameterEntry { tool: string parameters: any + metadata?: Record status?: ToolStatus error?: string turn: number diff --git a/lib/strategies/deduplication.ts b/lib/strategies/deduplication.ts index 42fbeda9..9cda5fbf 100644 --- a/lib/strategies/deduplication.ts +++ b/lib/strategies/deduplication.ts @@ -1,11 +1,7 @@ import { PluginConfig } from "../config" import { Logger } from "../logger" import type { SessionState, WithParts } from "../state" -import { - getFilePathsFromParameters, - isFilePathProtected, - isToolNameProtected, -} from "../protected-patterns" +import { isToolProtected } from "../protected-patterns" import { getTotalToolTokens } from "../token-utils" /** @@ -51,13 +47,15 @@ export const deduplicate = ( continue } - // Skip protected tools - if (isToolNameProtected(metadata.tool, protectedTools)) { - continue - } - - const filePaths = getFilePathsFromParameters(metadata.tool, metadata.parameters) - if (isFilePathProtected(filePaths, config.protectedFilePatterns)) { + if ( + isToolProtected( + metadata.tool, + metadata.parameters, + protectedTools, + config.protectedFilePatterns, + metadata.metadata, + ) + ) { continue } diff --git a/lib/strategies/purge-errors.ts b/lib/strategies/purge-errors.ts index d19be82a..40303616 100644 --- a/lib/strategies/purge-errors.ts +++ b/lib/strategies/purge-errors.ts @@ -1,11 +1,7 @@ import { PluginConfig } from "../config" import { Logger } from "../logger" import type { SessionState, WithParts } from "../state" -import { - getFilePathsFromParameters, - isFilePathProtected, - isToolNameProtected, -} from "../protected-patterns" +import { isToolProtected } from "../protected-patterns" import { getTotalToolTokens } from "../token-utils" /** @@ -53,13 +49,15 @@ export const purgeErrors = ( continue } - // Skip protected tools - if (isToolNameProtected(metadata.tool, protectedTools)) { - continue - } - - const filePaths = getFilePathsFromParameters(metadata.tool, metadata.parameters) - if (isFilePathProtected(filePaths, config.protectedFilePatterns)) { + if ( + isToolProtected( + metadata.tool, + metadata.parameters, + protectedTools, + config.protectedFilePatterns, + metadata.metadata, + ) + ) { continue } diff --git a/lib/subagents/subagent-results.ts b/lib/subagents/subagent-results.ts index bea4af49..2839d2df 100644 --- a/lib/subagents/subagent-results.ts +++ b/lib/subagents/subagent-results.ts @@ -1,9 +1,10 @@ import type { WithParts } from "../state" -const SUB_AGENT_RESULT_BLOCK_REGEX = /(\s*)([\s\S]*?)(\s*<\/task_result>)/i +const SUB_AGENT_RESULT_BLOCK_REGEX = + /(<(?:task_result|subagent\b[^>]*)>\s*)([\s\S]*?)(\s*<\/(?:task_result|subagent)>)/i export function getSubAgentId(part: any): string | null { - const sessionId = part?.state?.metadata?.sessionId + const sessionId = part?.state?.metadata?.sessionId ?? part?.state?.metadata?.sessionID if (typeof sessionId !== "string") { return null } diff --git a/tests/v2-protection.test.ts b/tests/v2-protection.test.ts new file mode 100644 index 00000000..ad9db7e7 --- /dev/null +++ b/tests/v2-protection.test.ts @@ -0,0 +1,110 @@ +import assert from "node:assert/strict" +import test from "node:test" +import type { PluginConfig } from "../lib/config" +import { Logger } from "../lib/logger" +import { createSessionState, syncToolCache, type WithParts } from "../lib/state" +import { deduplicate } from "../lib/strategies/deduplication" +import { purgeErrors } from "../lib/strategies/purge-errors" +import { appendProtectedTools } from "../lib/compress/protected-content" +import { buildSearchContext, resolveSelection } from "../lib/compress/search" +import { isToolProtected } from "../lib/protected-patterns" +import { getSubAgentId, mergeSubagentResult } from "../lib/subagents/subagent-results" + +const logger = new Logger(false) +const config = { + manualMode: { automaticStrategies: true }, + turnProtection: { enabled: false, turns: 4 }, + protectedFilePatterns: ["**/protected.txt"], + strategies: { + deduplication: { enabled: true, protectedTools: [] }, + purgeErrors: { enabled: true, turns: 1, protectedTools: [] }, + }, +} as PluginConfig + +test("Code Mode protections survive caching, strategy selection, and compression", async () => { + const state = createSessionState() + state.currentTurn = 10 + const messages = ["first", "second", "failed"].map((id) => ({ + info: { id, role: "assistant", time: { created: 1 } }, + parts: [ + { + type: "tool", + tool: "execute", + callID: id, + state: { + status: id === "failed" ? "error" : "completed", + input: { code: "return await tools.read({path: 'protected.txt'})" }, + output: "PROTECTED_OUTPUT and another tool's output", + error: "Failed operation", + metadata: { + toolCalls: [ + { + tool: "read", + input: { path: "/project/protected.txt" }, + status: "completed", + }, + ], + }, + }, + }, + ], + })) as unknown as WithParts[] + syncToolCache(state, config, logger, messages) + state.toolIdList = ["first", "second", "failed"] + deduplicate(state, logger, config, messages) + purgeErrors(state, logger, config, messages) + assert.equal(state.prune.tools.size, 0) + + const context = buildSearchContext(state, messages) + const boundary = { kind: "message" as const, rawIndex: 0, messageId: "first" } + const selection = resolveSelection(context, boundary, boundary) + const summary = await appendProtectedTools( + {}, + state, + false, + "SUMMARY", + selection, + context, + [], + config.protectedFilePatterns, + ) + assert.match(summary, /PROTECTED_OUTPUT and another tool's output/) + + const unprotected = { ...config, protectedFilePatterns: [] } + deduplicate(state, logger, unprotected, messages) + purgeErrors(state, logger, unprotected, messages) + assert.deepEqual([...state.prune.tools.keys()], ["first", "second", "failed"]) +}) + +test("Code Mode honors protected tool names as well as file paths", () => { + const metadata = { toolCalls: [{ tool: "skill", input: { name: "example" } }] } + assert.equal(isToolProtected("execute", {}, ["skill"], [], metadata), true) + assert.equal(isToolProtected("execute", {}, [], [], metadata), false) + assert.equal( + isToolProtected( + "read", + { path: "/project/protected.txt" }, + [], + config.protectedFilePatterns, + ), + true, + ) +}) + +test("subagent expansion preserves both host wrapper formats", () => { + for (const [metadata, original, expected] of [ + [ + { sessionId: "ses_child" }, + "old", + "new", + ], + [ + { sessionID: "ses_child" }, + 'old', + 'new', + ], + ] as const) { + assert.equal(getSubAgentId({ state: { metadata } }), "ses_child") + assert.equal(mergeSubagentResult(original, "new"), expected) + } +}) From 3f16adf8a8adf22baeccd9acb73e17130ff4e444 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 02/15] feat: add a V2 server adapter alongside V1 support Register native hooks, tools, commands, and panel RPC while retaining the V1 server entrypoint. Preserve provider-native context and tool pairs, isolate session state, and keep compaction prefixes stable. Require V1 1.18.29+ and reject unsupported V2 ask permissions. --- index.ts | 3 +- lib/config.ts | 18 +- lib/messages/inject/inject.ts | 6 + lib/messages/prune.ts | 6 +- lib/v2/index.ts | 370 ++ lib/v2/messages.ts | 237 ++ lib/v2/rpc.ts | 48 + package-lock.json | 6588 ++++++++++++++++++++++++++----- package.json | 7 +- scripts/verify-package.mjs | 5 +- server.js | 1 + tests/compaction-nudges.test.ts | 60 + tests/v2-messages.test.ts | 170 + 13 files changed, 6527 insertions(+), 992 deletions(-) create mode 100644 lib/v2/index.ts create mode 100644 lib/v2/messages.ts create mode 100644 lib/v2/rpc.ts create mode 100644 server.js create mode 100644 tests/compaction-nudges.test.ts create mode 100644 tests/v2-messages.test.ts diff --git a/index.ts b/index.ts index 3f11e392..02216b33 100644 --- a/index.ts +++ b/index.ts @@ -18,6 +18,7 @@ import { } from "./lib/hooks" import { configureClientAuth, isSecureMode } from "./lib/auth" import { startAutoUpdate } from "./lib/update" +import { setup } from "./lib/v2" const server: Plugin = (async (ctx) => { const config = getConfig(ctx) @@ -134,4 +135,4 @@ const server: Plugin = (async (ctx) => { } }) satisfies Plugin -export default server +export default { id: "opencode-dcp", setup, server } diff --git a/lib/config.ts b/lib/config.ts index d7ddee28..792b7ca3 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -4,6 +4,16 @@ import { homedir } from "os" import { parse } from "jsonc-parser/lib/esm/main.js" import type { PluginInput } from "@opencode-ai/plugin" +type ConfigContext = Pick & { + client: { + tui: { + showToast(input: { + body: { title: string; message: string; variant: "warning"; duration: number } + }): unknown + } + } +} + type Permission = "ask" | "allow" | "deny" type CompressMode = "range" | "message" @@ -609,7 +619,7 @@ export function validateConfigTypes(config: Record): ValidationErro } function showConfigWarnings( - ctx: PluginInput, + ctx: ConfigContext, configPath: string, configData: Record, isProject: boolean, @@ -725,7 +735,7 @@ function findOpencodeDir(startDir: string): string | null { return null } -function getConfigPaths(ctx?: PluginInput): { +function getConfigPaths(ctx?: ConfigContext): { global: string | null configDir: string | null project: string | null @@ -951,7 +961,7 @@ function mergeLayer(config: PluginConfig, data: Record): PluginConf } } -function scheduleParseWarning(ctx: PluginInput, title: string, message: string): void { +function scheduleParseWarning(ctx: ConfigContext, title: string, message: string): void { setTimeout(() => { try { ctx.client.tui.showToast({ @@ -966,7 +976,7 @@ function scheduleParseWarning(ctx: PluginInput, title: string, message: string): }, 7000) } -export function getConfig(ctx: PluginInput): PluginConfig { +export function getConfig(ctx: ConfigContext): PluginConfig { let config = deepCloneConfig(defaultConfig) const configPaths = getConfigPaths(ctx) diff --git a/lib/messages/inject/inject.ts b/lib/messages/inject/inject.ts index 16599e45..ebd6fb31 100644 --- a/lib/messages/inject/inject.ts +++ b/lib/messages/inject/inject.ts @@ -37,6 +37,7 @@ export const injectCompressNudges = ( messages: WithParts[], prompts: RuntimePrompts, compressionPriorities?: CompressionPriorityMap, + createAnchors = true, ): void => { if (compressPermission(state, config) === "deny") { return @@ -46,6 +47,11 @@ export const injectCompressNudges = ( return } + if (!createAnchors) { + applyAnchoredNudges(state, config, messages, prompts, compressionPriorities) + return + } + const lastMessage = findLastNonIgnoredMessage(messages) const lastAssistantMessage = messages.findLast((message) => message.info.role === "assistant") diff --git a/lib/messages/prune.ts b/lib/messages/prune.ts index 444cdf86..49b67194 100644 --- a/lib/messages/prune.ts +++ b/lib/messages/prune.ts @@ -16,8 +16,9 @@ export const prune = ( logger: Logger, config: PluginConfig, messages: WithParts[], + summaryBase?: WithParts, ): void => { - filterCompressedRanges(state, logger, config, messages) + filterCompressedRanges(state, logger, config, messages, summaryBase) // pruneFullTool(state, logger, messages) pruneToolOutputs(state, logger, messages) pruneToolInputs(state, logger, messages) @@ -161,6 +162,7 @@ const filterCompressedRanges = ( logger: Logger, config: PluginConfig, messages: WithParts[], + summaryBase?: WithParts, ): void => { if ( state.prune.messages.byMessageId.size === 0 && @@ -192,7 +194,7 @@ const filterCompressedRanges = ( } else { // Find user message for variant and as base for synthetic message const msgIndex = messages.indexOf(msg) - const userMessage = getLastUserMessage(messages, msgIndex) + const userMessage = getLastUserMessage(messages, msgIndex) ?? summaryBase if (userMessage) { const userInfo = userMessage.info as UserMessage diff --git a/lib/v2/index.ts b/lib/v2/index.ts new file mode 100644 index 00000000..8f3c1b2d --- /dev/null +++ b/lib/v2/index.ts @@ -0,0 +1,370 @@ +import type { Plugin } from "@opencode/plugin" +import { tool, type ToolDefinition } from "@opencode-ai/plugin" +import { getConfig } from "../config" +import { Logger } from "../logger" +import { PromptStore } from "../prompts/store" +import { createCompressMessageTool, createCompressRangeTool } from "../compress" +import { attachCompressionDuration } from "../compress/state" +import { createCommandExecuteHandler, createSystemPromptHandler } from "../hooks" +import { + createSessionState, + ensureSessionInitialized, + checkSession, + saveSessionState, + syncToolCache, + type SessionState, +} from "../state" +import { assignMessageRefs } from "../message-ids" +import { applyPendingManualTrigger } from "../commands/manual" +import { + buildPriorityMap, + buildToolIdList, + injectCompressNudges, + injectMessageIds, + injectExtendedSubAgentResults, + prune, + stripHallucinations, + syncCompressionBlocks, +} from "../messages" +import { countTokens } from "../token-utils" +import { matchesGlob } from "../protected-patterns" +import { history, project } from "./messages" +import { analyzeContextTokens } from "../commands/context" +import { buildStatsReport } from "../commands/stats" +import { rpc } from "./rpc" + +// Extension point for future model-invisible V2 reports. Never use synthetic() +// here: its text would enter the model's context, unlike V1 ignored messages. +export async function report(logger: Logger, text: string, sessionID?: string) { + logger.debug("V2 report (display pending)", { sessionID, text }) +} + +export async function setup(ctx: Plugin.Context) { + const warnings = { + tui: { + showToast: async (input: { body: { message: string } }) => { + console.warn(`DCP: ${input.body.message}`) + }, + }, + } + const config = getConfig({ directory: ctx.location.directory, client: warnings }) + if (!config.enabled) { + await ctx.rpc.register( + { ...rpc, methods: { status: rpc.methods.status } }, + { status: async () => ({ enabled: false }) }, + ) + return + } + const logger = new Logger(config.debug) + const prompts = new PromptStore( + logger, + ctx.location.directory, + config.experimental.customPrompts, + ) + const sessions = new Map() + const queues = new Map>() + const limits = new Map() + const aliases: Record = { + task: "subagent", + bash: "shell", + apply_patch: "patch", + } + for (const list of [ + config.compress.protectedTools, + config.commands.protectedTools, + config.strategies.deduplication.protectedTools, + config.strategies.purgeErrors.protectedTools, + ]) { + for (const name of [...list]) + if (aliases[name] && !list.includes(aliases[name]!)) list.push(aliases[name]!) + } + + function serial(sessionID: string, operation: () => Promise): Promise { + const pending = (queues.get(sessionID) ?? Promise.resolve()).catch(() => {}).then(operation) + queues.set(sessionID, pending) + void pending + .finally(() => { + if (queues.get(sessionID) === pending) queues.delete(sessionID) + }) + .catch(() => {}) + return pending + } + + const client = { + session: { + get: async ({ path }: { path: { id: string } }) => ({ + data: await ctx.session.get({ sessionID: path.id }), + }), + messages: async ({ path }: { path: { id: string } }) => { + const [entries, session] = await Promise.all([ + ctx.session.context({ sessionID: path.id }), + ctx.session.get({ sessionID: path.id }), + ]) + return { data: history(entries, session) } + }, + prompt: async (input: { + path: { id: string } + body: { parts: Array<{ text: string }> } + }) => + report(logger, input.body.parts.map((part) => part.text).join("\n"), input.path.id), + }, + tui: { + showToast: async (input: { body: { message: string } }) => + report(logger, input.body.message), + }, + } + + async function load(sessionID: string, agentID?: string) { + const [session, entries] = await Promise.all([ + ctx.session.get({ sessionID }), + ctx.session.context({ sessionID }), + ]) + const selected = + agentID ?? + session.agent ?? + entries.findLast((entry) => entry.type === "assistant")?.agent + if (!selected) throw new Error("DCP commands require a session with a selected agent") + const { data: agent } = await ctx.agent.get({ agentID: selected }) + let state = sessions.get(sessionID) + if (!state) { + state = createSessionState() + sessions.set(sessionID, state) + } + const messages = history(entries, session) + await ensureSessionInitialized( + client, + state, + sessionID, + logger, + messages, + config.manualMode.enabled, + ) + await checkSession(client, state, logger, messages, config.manualMode.enabled) + const rule = [...agent.permissions, ...(session.permissions ?? [])].findLast( + (rule) => matchesGlob("compress", rule.action) && matchesGlob("*", rule.resource), + ) + state.compressPermission = + config.compress.permission === "deny" + ? "deny" + : rule?.effect === "deny" + ? "deny" + : config.compress.permission === "ask" || rule?.effect === "ask" + ? "ask" + : "allow" + return { state, entries, session, messages } + } + + function allowed(state: SessionState) { + if (state.isSubAgent && !config.experimental.allowSubAgents) + throw new Error("DCP compression is disabled in subagents") + if (state.compressPermission === "deny") throw new Error("DCP compression is denied") + if (state.compressPermission === "ask") + throw new Error( + "DCP: compress permission 'ask' is not supported by OpenCode V2's public plugin API yet. Compression was not performed.", + ) + } + + await ctx.model.transform((editor) => { + limits.clear() + for (const model of editor.list()) + limits.set(`${model.providerID}/${model.id}`, model.limit.context) + }) + for (const kind of ["context", "compaction"] as const) + await ctx.session.hook(kind, (event) => + serial(event.sessionID, async () => { + const { state, entries, session, messages } = await load( + event.sessionID, + event.agent, + ) + if (state.isSubAgent && !config.experimental.allowSubAgents) { + delete event.tools.compress + return + } + if (state.compressPermission === "deny") delete event.tools.compress + state.modelContextLimit = limits.get(`${event.model.providerID}/${event.model.id}`) + state.systemPromptTokens = countTokens( + event.system.map((part) => part.text).join("\n"), + ) + const view = project(event.messages, entries, { + ...session, + agent: event.agent, + model: event.model, + }) + stripHallucinations(view.messages) + assignMessageRefs(state, view.messages) + // Compaction may select only a prefix; block origins can be in the retained tail. + syncCompressionBlocks(state, logger, messages) + syncToolCache(state, config, logger, view.messages) + buildToolIdList(state, view.messages) + prune(state, logger, config, view.messages, view.summaryBase) + await injectExtendedSubAgentResults( + client, + state, + logger, + view.messages, + config.experimental.allowSubAgents, + ) + const priorities = buildPriorityMap(config, state, view.messages) + prompts.reload() + injectCompressNudges( + state, + config, + logger, + view.messages, + prompts.getRuntimePrompts(), + priorities, + kind === "context", + ) + injectMessageIds(state, config, view.messages, priorities) + applyPendingManualTrigger(state, view.messages, logger) + event.messages = view.restore() + const system = { system: event.system.map((part) => part.text) } + await createSystemPromptHandler( + state, + logger, + config, + prompts, + )( + { + sessionID: event.sessionID, + model: { limit: { context: state.modelContextLimit ?? 0 } }, + }, + system, + ) + event.system = system.system.map((text, index) => ({ + ...event.system[index], + type: "text", + text, + })) + await logger.saveContext(event.sessionID, view.messages) + }), + ) + + if (config.compress.permission !== "deny") { + const define = (state: SessionState): ToolDefinition => + (config.compress.mode === "message" + ? createCompressMessageTool + : createCompressRangeTool)({ client, state, logger, config, prompts }) + const definition = define(createSessionState()) + await ctx.tool.transform((editor) => + editor.add({ + name: "compress", + description: definition.description, + input: tool.schema.object(definition.args), + options: { codemode: false, permission: "compress" }, + execute: (input, context) => + serial(context.sessionID, async () => { + const { state } = await load(context.sessionID, context.agent) + allowed(state) + const started = Date.now() + const legacy = define(state) + const content = await legacy.execute(input, { + sessionID: context.sessionID, + messageID: context.messageID, + callID: context.id, + agent: context.agent, + directory: ctx.location.directory, + worktree: ctx.location.directory, + abort: new AbortController().signal, + ask: async () => allowed(state), + metadata: ({ title }: { title?: string }) => { + void context.progress({ title }) + }, + } as Parameters[1]) + attachCompressionDuration( + state.prune.messages, + context.messageID, + context.id, + Date.now() - started, + ) + await saveSessionState(state, logger) + // Both shared compression executors return text; the V1 helper's + // public return type also permits unrelated attachment results. + return { content: content as string } + }), + }), + ) + } + if (config.commands.enabled) + await ctx.command.transform((editor) => { + for (const name of ["dcp", "dcp-compress"]) + editor.add({ + name, + description: name === "dcp" ? "DCP commands" : "Trigger DCP manual compression", + execute: async (invocation) => { + const prompt = await serial(invocation.sessionID, async () => { + const { state } = await load(invocation.sessionID) + const permission = + state.compressPermission ?? config.compress.permission + if ( + name === "dcp-compress" || + invocation.prompt.text.trim().split(/\s+/)[0] === "compress" + ) + allowed(state) + const output = { parts: [] } + await createCommandExecuteHandler( + client, + state, + logger, + config, + ctx.location.directory, + { global: { compress: permission }, agents: {} }, + )( + { + command: name, + sessionID: invocation.sessionID, + arguments: invocation.prompt.text, + }, + output, + ) + state.compressPermission = permission + const pending = state.pendingManualTrigger + if (!pending) return + allowed(state) + state.pendingManualTrigger = null + return pending.prompt + }) + if (prompt) + await ctx.session.prompt({ + sessionID: invocation.sessionID, + text: prompt, + delivery: invocation.delivery, + }) + }, + }) + }) + await ctx.rpc.register(rpc, { + status: async () => ({ enabled: config.commands.enabled }), + snapshot: ({ sessionID }) => + serial(sessionID, async () => { + const { state, messages } = await load(sessionID) + syncCompressionBlocks(state, logger, messages) + return { + manualMode: !!state.manualMode, + canCompress: + state.compressPermission === "allow" && + (!state.isSubAgent || config.experimental.allowSubAgents), + ...(state.compressPermission === "ask" + ? { + blockedReason: + "Permission 'ask' is not supported by the V2 plugin API yet.", + } + : {}), + context: analyzeContextTokens(state, messages), + stats: await buildStatsReport(state, logger), + } + }), + manual: ({ sessionID, enabled }) => + serial(sessionID, async () => { + const { state } = await load(sessionID) + state.manualMode = enabled ? "active" : false + await saveSessionState(state, logger) + return {} + }), + }) + logger.info("DCP V2 initialized") + return () => { + sessions.clear() + limits.clear() + } +} diff --git a/lib/v2/messages.ts b/lib/v2/messages.ts new file mode 100644 index 00000000..62f0c737 --- /dev/null +++ b/lib/v2/messages.ts @@ -0,0 +1,237 @@ +import type { Message, ContentPart, ToolResultPart } from "@opencode/ai/schema/messages" +import type { Plugin } from "@opencode/plugin" +import type { WithParts } from "../state" + +type History = Awaited> +type Session = Awaited> +type Part = WithParts["parts"][number] + +const emptyUsage = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } + +function info(id: string, role: string, session: Session, entry?: History[number]) { + const model = entry?.type === "assistant" ? entry.model : session.model + return { + id, + role, + sessionID: session.id, + time: { created: entry?.time.created ?? 0 }, + agent: entry?.type === "assistant" ? entry.agent : session.agent, + model: { providerID: model?.providerID, modelID: model?.id, variant: model?.variant }, + providerID: model?.providerID, + modelID: model?.id, + tokens: entry?.type === "assistant" ? (entry.tokens ?? emptyUsage) : emptyUsage, + summary: entry?.type === "compaction" && entry.status === "completed", + } as unknown as WithParts["info"] +} + +// Bookkeeping projections are never sent to a provider. Native messages remain +// authoritative for reasoning, media, checkpoints, and provider metadata. +export function history(entries: History, session: Session): WithParts[] { + return entries.flatMap((entry): WithParts[] => { + const base = { sessionID: session.id, messageID: entry.id } + if (entry.type === "assistant") { + const parts = entry.content.map((part, index) => { + if (part.type !== "tool") + return { ...base, id: `${entry.id}:${index}`, type: part.type, text: part.text } + const state = part.state + return { + ...base, + id: part.id, + type: "tool", + callID: part.id, + tool: part.name, + state: { + ...state, + input: structuredClone(state.input), + ...(state.status === "completed" ? { output: output(state.content) } : {}), + }, + } + }) + return [ + { + info: info(entry.id, "assistant", session, entry), + parts: [{ ...base, id: `${entry.id}:step`, type: "step-start" }, ...parts], + } as WithParts, + ] + } + let text: string | undefined + if (entry.type === "user") + text = [ + ...(entry.skills ?? []).flatMap((skill) => + skill.text === undefined ? [] : [skill.text], + ), + entry.text, + ].join("\n\n") + if (entry.type === "synthetic" || entry.type === "skill") text = entry.text + if (entry.type === "location-switched") + text = `The working directory has been changed to ${entry.location.directory}.` + if (entry.type === "shell" && entry.metadata?.background !== true) + text = `The following shell command was executed by the user:\n\nCommand:\n${entry.command}\n\nOutput:\n${entry.output?.output ?? ""}` + if (text !== undefined) { + return [ + { + info: info(entry.id, "user", session, entry), + parts: [{ ...base, id: `${entry.id}:text`, type: "text", text }], + } as WithParts, + ] + } + if (entry.type === "compaction" && entry.status === "completed") { + return [{ info: info(entry.id, "assistant", session, entry), parts: [] } as WithParts] + } + return [] + }) +} + +function output(content: ReadonlyArray<{ type: string; text?: string }>): unknown { + return content.length === 1 && content[0]?.type === "text" + ? content[0].text + : structuredClone(content) +} + +export function project(native: Message[], entries: History, session: Session) { + const byID = new Map(entries.map((entry) => [entry.id, entry])) + const results = new Map() + const owners = new Map() + const links = new Map() + const originals = new Map() + const messages: WithParts[] = [] + for (const message of native) + for (const part of message.content) { + if (part.type === "tool-result") results.set(part.id, part) + if (part.type === "tool-call" && message.id) owners.set(part.id, message.id) + } + for (const message of native) { + if (!message.id || !["user", "assistant"].includes(message.role)) continue + const entry = byID.get(message.id) + if (!entry || entry.type === "compaction") continue + const base = { sessionID: session.id, messageID: message.id } + const parts: Part[] = [] + if (message.role === "assistant") + parts.push({ ...base, id: `${message.id}:step`, type: "step-start" }) + for (const [index, part] of message.content.entries()) { + let projected: Part | undefined + if (part.type === "text") + projected = { ...base, id: `${message.id}:${index}`, type: "text", text: part.text } + if (part.type === "media") + projected = { + ...base, + id: `${message.id}:${index}`, + type: "file", + mime: part.mediaType, + url: typeof part.data === "string" ? part.data : "", + } + if (part.type === "tool-call") { + const result = results.get(part.id) + const entry = byID.get(message.id) + const durable = + entry?.type === "assistant" + ? entry.content.find((item) => item.type === "tool" && item.id === part.id) + : undefined + projected = { + ...base, + id: part.id, + type: "tool", + callID: part.id, + tool: part.name, + state: { + status: !result + ? "running" + : result.result.type === "error" + ? "error" + : "completed", + input: structuredClone(part.input), + ...(result?.result.type === "error" + ? { error: result.result.value } + : { + output: structuredClone( + result?.result.type === "text" + ? result.result.value + : result?.result, + ), + }), + metadata: + durable?.type === "tool" && "metadata" in durable.state + ? durable.state.metadata + : {}, + }, + } as Part + if (result) links.set(result, projected) + } + if (projected) { + parts.push(projected) + links.set(part, projected) + } + } + const projected = { + info: info(message.id, message.role, session, byID.get(message.id)), + parts, + } as WithParts + messages.push(projected) + originals.set(message.id, projected) + } + + function restore(): Message[] { + const retained = new Set(messages.map((message) => message.info.id)) + const linked = new Set(links.values()) + const output: Message[] = [] + let next = 0 + const synthetic = () => { + while (next < messages.length && !originals.has(messages[next]!.info.id)) { + const message = messages[next++]! + output.push({ + id: message.info.id, + role: "user", + content: message.parts + .filter((part) => part.type === "text") + .map((part) => ({ type: "text", text: part.text })), + }) + } + } + for (const message of native) { + const projected = message.id ? originals.get(message.id) : undefined + if (projected) { + synthetic() + if (!retained.has(message.id!)) continue + next++ + } + const content: ContentPart[] = [] + const added = + projected?.parts.flatMap((part) => + part.type === "text" && !linked.has(part) + ? [{ type: "text" as const, text: part.text }] + : [], + ) ?? [] + for (const part of message.content) { + if (part.type === "tool-call") content.push(...added.splice(0)) + if ( + part.type === "tool-result" && + originals.has(owners.get(part.id)!) && + !retained.has(owners.get(part.id)!) + ) + continue + const edit = links.get(part) + if (part.type === "text" && edit?.type === "text") + content.push({ ...part, text: edit.text }) + else if (part.type === "tool-call" && edit?.type === "tool") + content.push({ ...part, input: edit.state.input }) + else if ( + part.type === "tool-result" && + edit?.type === "tool" && + edit.state.status === "completed" && + typeof edit.state.output === "string" && + edit.state.output !== part.result.value + ) { + content.push({ ...part, result: { type: "text", value: edit.state.output } }) + } else content.push(part) + } + content.push(...added) + if (content.length || message.content.length === 0) output.push({ ...message, content }) + } + synthetic() + return output + } + // A native checkpoint can replace every user message. Summary construction + // still needs session/agent/model fields, without projecting that checkpoint. + const summaryBase: WithParts = { info: info("msg_dcp_base", "user", session), parts: [] } + return { messages, restore, summaryBase } +} diff --git a/lib/v2/rpc.ts b/lib/v2/rpc.ts new file mode 100644 index 00000000..9d579352 --- /dev/null +++ b/lib/v2/rpc.ts @@ -0,0 +1,48 @@ +import { tool } from "@opencode-ai/plugin" + +const z = tool.schema +const session = z.object({ sessionID: z.string() }) +const stats = z.object({ + sessionTokens: z.number(), + sessionSummaryTokens: z.number(), + sessionDurationMs: z.number(), + sessionTools: z.number(), + sessionMessages: z.number(), + allTime: z.object({ + totalTokens: z.number(), + totalTools: z.number(), + totalMessages: z.number(), + sessionCount: z.number(), + }), +}) +const context = z.object({ + system: z.number(), + user: z.number(), + assistant: z.number(), + tools: z.number(), + toolCount: z.number(), + toolsInContextCount: z.number(), + prunedTokens: z.number(), + prunedToolCount: z.number(), + prunedMessageCount: z.number(), + total: z.number(), +}) + +export const rpc = { + id: "dcp", + methods: { + status: { input: z.object({}), output: z.object({ enabled: z.boolean() }) }, + snapshot: { + input: session, + output: z.object({ + manualMode: z.boolean(), + canCompress: z.boolean(), + blockedReason: z.string().optional(), + context, + stats, + }), + }, + manual: { input: session.extend({ enabled: z.boolean() }), output: z.object({}) }, + }, + events: {}, +} as const diff --git a/package-lock.json b/package-lock.json index d90568eb..12ddcfad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,9 @@ "solid-js": "^1.9.12" }, "devDependencies": { - "@opencode-ai/plugin": "^1.4.3", + "@opencode-ai/plugin": "^1.18.29", + "@opencode/plugin": "^2.0.4", + "@opencode/theme": "^2.0.4", "@types/node": "^25.5.0", "prettier": "^3.8.1", "tsup": "^8.5.1", @@ -25,7 +27,20 @@ "typescript": "^6.0.2" }, "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3" + "@opencode-ai/plugin": ">=1.18.29" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@anthropic-ai/tokenizer": { @@ -53,6 +68,479 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1057.0.tgz", + "integrity": "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.0.tgz", + "integrity": "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@aws-sdk/xml-builder": "^3.972.40", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.33.3", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.70.tgz", + "integrity": "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.71.tgz", + "integrity": "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.73.tgz", + "integrity": "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.16.tgz", + "integrity": "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-login": "^3.972.78", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.78.tgz", + "integrity": "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.83", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.83.tgz", + "integrity": "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-ini": "^3.973.16", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.71.tgz", + "integrity": "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.15.tgz", + "integrity": "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/token-providers": "3.1129.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.77.tgz", + "integrity": "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1057.0.tgz", + "integrity": "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.1057.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", + "@aws-sdk/credential-provider-env": "^3.972.41", + "@aws-sdk/credential-provider-http": "^3.972.43", + "@aws-sdk/credential-provider-ini": "^3.972.46", + "@aws-sdk/credential-provider-login": "^3.972.45", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/credential-provider-process": "^3.972.41", + "@aws-sdk/credential-provider-sso": "^3.972.45", + "@aws-sdk/credential-provider-web-identity": "^3.972.45", + "@aws-sdk/nested-clients": "^3.997.13", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/credential-provider-imds": "^4.3.6", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.45.tgz", + "integrity": "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", + "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1129.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1129.0.tgz", + "integrity": "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", + "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.10.tgz", + "integrity": "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", + "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -458,6 +946,90 @@ "node": ">=6.9.0" } }, + "node_modules/@effect/opentelemetry": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/opentelemetry/-/opentelemetry-4.0.0-rc.112.tgz", + "integrity": "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <2.0.0", + "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/resources": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", + "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", + "effect": "^4.0.0-rc.112" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/api-logs": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-logs": { + "optional": true + }, + "@opentelemetry/sdk-metrics": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/sdk-trace-node": { + "optional": true + }, + "@opentelemetry/sdk-trace-web": { + "optional": true + } + } + }, + "node_modules/@effect/platform-node": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/platform-node/-/platform-node-4.0.0-rc.112.tgz", + "integrity": "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@effect/platform-node-shared": "^4.0.0-rc.112", + "mime": "^4.1.0", + "undici": "^8.10.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.0-rc.112", + "redis": ">=5.0.0 <7.0.0" + } + }, + "node_modules/@effect/platform-node-shared": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/platform-node-shared/-/platform-node-shared-4.0.0-rc.112.tgz", + "integrity": "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ws": "^8.18.1", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.0-rc.112" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -900,14 +1472,97 @@ "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==", + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/fs-minipass/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/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true, + "license": "ISC" + }, + "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/remapping": { @@ -945,1323 +1600,4518 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@opencode-ai/plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.3.tgz", - "integrity": "sha512-Ob/3tVSIeuMRJBr2O23RtrnC5djRe01Lglx+TwGEmjrH9yDBJ2tftegYLnNEjRoMuzITgq9LD8168p4pzv+U/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@opencode-ai/sdk": "1.4.3", - "zod": "4.1.8" - }, - "peerDependencies": { - "@opentui/core": ">=0.1.97", - "@opentui/solid": ">=0.1.97" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } - } - }, - "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.4.3", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.3.tgz", - "integrity": "sha512-X0CAVbwoGAjTY2iecpWkx2B+GAa2jSaQKYpJ+xILopeF/OGKZUN15mjqci+L7cEuwLHV5wk3x2TStUOVCa5p0A==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@opentui/core": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.4.5.tgz", - "integrity": "sha512-JsgRTPkA6e+Vxmumxai6SElOSlRQkbzNKHlCfemlArRiLhfC1IZ9RXJo2QH4xSu+uBOWAM90uss73/pPlkdEig==", - "license": "MIT", - "dependencies": { - "bun-ffi-structs": "0.2.4", - "diff": "9.0.0", - "marked": "17.0.1", - "string-width": "7.2.0", - "strip-ansi": "7.1.2" - }, - "optionalDependencies": { - "@opentui/core-darwin-arm64": "0.4.5", - "@opentui/core-darwin-x64": "0.4.5", - "@opentui/core-linux-arm64": "0.4.5", - "@opentui/core-linux-arm64-musl": "0.4.5", - "@opentui/core-linux-x64": "0.4.5", - "@opentui/core-linux-x64-musl": "0.4.5", - "@opentui/core-win32-arm64": "0.4.5", - "@opentui/core-win32-x64": "0.4.5" - }, - "peerDependencies": { - "web-tree-sitter": "0.25.10" - } - }, - "node_modules/@opentui/core-darwin-arm64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.4.5.tgz", - "integrity": "sha512-8KUG0oRidnR+oW1RSZJ72/PhZLl+qRRMk5U/mieF4c0SJ5V3tYACpBZAKzQfHNd1f7QzD8FHZct1lPpQgtmkWg==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, - "node_modules/@opentui/core-darwin-x64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.4.5.tgz", - "integrity": "sha512-R2bocsg55gwjOqCp/MWFgFYzRmsduKegB6nzgFAPCvAD/L5Jf30xpWJWFlSg3x8vxe1L9WJ84dfqa4M7mZZ3wA==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, - "node_modules/@opentui/core-linux-arm64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.4.5.tgz", - "integrity": "sha512-R4MZ25a4CzOAGVjW9aj1hUfzQGVfCJwrwBDbNs2SXaIvzcZqkxCVtU4FoQ5LsaD0j/BdNQVg2CIfFkFsm1fDuQ==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", "cpu": [ - "arm64" + "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@opentui/core-linux-arm64-musl": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.4.5.tgz", - "integrity": "sha512-ieqdyKI6EIYPalYAETB2wsdP83hr5Ifi+dFnBFUmdEEFHsoKwBmn2S7bsTOYlX7Bg03F4/YPIg+IvRpeC+cUJw==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@opentui/core-linux-x64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.4.5.tgz", - "integrity": "sha512-SNyuQoxMKI1vuJhgxSSW96adWM6LqFl2SoS3GM4tGeneGOanVVG2Y06PvlytXvF4cKik97t0rqkVMRetmOs93w==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@opentui/core-linux-x64-musl": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.4.5.tgz", - "integrity": "sha512-mKVKcIcPiSVVZZsdPSBoWwoa2/TCeQAaMDeHF7PFw2kt5bTXZPP7xxWfRQLCNIcA1eaGl59UuwUWHDR2Ve548Q==", + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", "cpu": [ "x64" ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@opentui/core-win32-arm64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.4.5.tgz", - "integrity": "sha512-GHTTsqeR45q2Iek9Rb7ty+x/hAKn2jZ1ujlCgPR8LBKyF7h0E1dNFryoZ7ehMc3kJndP1sKn836IemKFqxuDdQ==", - "cpu": [ - "arm64" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@opentui/core-win32-x64": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.4.5.tgz", - "integrity": "sha512-Y8T/yXCDGagRGiQrtmuB6AhRcPucKFs/Dre3v8kJwNYqDccI4FzUPKclZ7djfmRZNjl7JUqPhZZP/PwDpQocMg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@npmcli/agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", + "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@opentui/core/node_modules/bun-ffi-structs": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.2.4.tgz", - "integrity": "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg==", - "license": "MIT", - "peerDependencies": { - "typescript": "^5" + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, - "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==", - "license": "Apache-2.0", - "peer": true, + "node_modules/@npmcli/arborist": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.4.0.tgz", + "integrity": "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "arborist": "bin/index.js" }, "engines": { - "node": ">=14.17" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@opentui/solid": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.4.5.tgz", - "integrity": "sha512-B0RSkXnrtPVfEJOX+Hj+axjLJ3lzbG1BZw5I7Pvb9OPp48Vzg2cW2a3cSa86/q48ndLt647i/XwFPIw/jqnI5g==", + "node_modules/@npmcli/arborist/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "7.28.0", - "@babel/preset-typescript": "7.27.1", - "@opentui/core": "0.4.5", - "babel-plugin-module-resolver": "5.0.2", - "babel-preset-solid": "1.9.12", - "entities": "7.0.1", - "s-js": "^0.4.9" - }, - "peerDependencies": { - "solid-js": "1.9.12" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", - "cpu": [ - "arm" - ], + "node_modules/@npmcli/arborist/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", - "cpu": [ - "arm64" - ], + "node_modules/@npmcli/arborist/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", - "cpu": [ - "arm64" - ], + "node_modules/@npmcli/arborist/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/arborist/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/config": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-10.8.1.tgz", + "integrity": "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/config/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/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/@npmcli/map-workspaces/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/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/@npmcli/package-json/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.18.29", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.18.29.tgz", + "integrity": "sha512-IhF83EU4I/ASgWwvm0FIh1O3a8ZVuCLqPrCbmSHdSYq7GHxIYe773i6dHqcbrzCzvlG/lP2H+dyTQ+xPAwFpbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode-ai/sdk": "1.18.29", + "effect": "4.0.0-beta.83", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.4.5", + "@opentui/keymap": ">=0.4.5", + "@opentui/solid": ">=0.4.5" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/keymap": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@opencode-ai/plugin/node_modules/effect": { + "version": "4.0.0-beta.83", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.83.tgz", + "integrity": "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "fast-check": "^4.8.0", + "find-my-way-ts": "^0.1.6", + "ini": "^7.0.0", + "kubernetes-types": "^1.30.0", + "msgpackr": "^2.0.1", + "multipasta": "^0.2.7", + "toml": "^4.1.1", + "uuid": "^14.0.0", + "yaml": "^2.9.0" + } + }, + "node_modules/@opencode-ai/plugin/node_modules/ini": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-7.0.0.tgz", + "integrity": "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.18.29", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.18.29.tgz", + "integrity": "sha512-4CS+FoLPkymTlcga8jxivGDDb2AbWMIIl3b8+myoe2wtv/1ANYCErslgz1xy5hTVHymWE6CtVNKzRuPU0ED57A==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/@opencode/ai": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.4.tgz", + "integrity": "sha512-T/2wTuhy/I1Wxdy/GdhtMqugpl1OWKgVp+8A45mtS1jDrTSeHq2x5YhkComkJZ6BwdG/OvU6afTGDPA7/vd4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/credential-providers": "3.1057.0", + "@opencode/schema": "2.0.4", + "@smithy/eventstream-codec": "4.2.14", + "@smithy/util-utf8": "4.2.2", + "aws4fetch": "1.0.20", + "effect": "4.0.0-rc.112", + "google-auth-library": "10.5.0" + } + }, + "node_modules/@opencode/client": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.4.tgz", + "integrity": "sha512-GBFWT2os+gEtGXpHRvDUNkf0H9x9njDUVbUvvc4drH2CuAihVcHqdi9Wm4UiwkBrSLjrKPs4U1NJBh2Z7/x4tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opencode/protocol": "2.0.4", + "@opencode/schema": "2.0.4" + }, + "peerDependencies": { + "effect": "4.0.0-rc.112", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "effect": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/plugin": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.4.tgz", + "integrity": "sha512-NsC1STARfXxe+ZoXjXzpxvtS9l4h9Aen8ZccaCd1d9+1eyYdp+q8DU3Rxh7Ch0jiwiQMybS2Z+KgAfZJBnTBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode/ai": "2.0.4", + "@opencode/client": "2.0.4", + "@opencode/protocol": "2.0.4", + "@opencode/schema": "2.0.4", + "@opencode/util": "2.0.4", + "@standard-schema/spec": "1.1.0", + "effect": "4.0.0-rc.112", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opencode/theme": "2.0.4", + "@opentui/core": ">=0.5.10", + "@opentui/solid": ">=0.5.10", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "@opencode/theme": { + "optional": true + }, + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/protocol": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.4.tgz", + "integrity": "sha512-uWe2dmMouprSkfBu29XLR6GCWckkhm0YOo9ve07kTRqJCSoJzgQDF/8pouWG0cDCJ6oWj3bRQajXriBQyWSpUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opencode/schema": "2.0.4", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/schema": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.4.tgz", + "integrity": "sha512-UyGcCsRb2hTH98rnBqfJogFve8kJsIbsEreRxqhkBIEGrtDTqcUd5NnqB7qlJp2khesMvpOH0GmCHlgz44C+uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.1.0", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/theme": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/theme/-/theme-2.0.4.tgz", + "integrity": "sha512-z0iLvKSKltIiFtPDpsh4dOMurYRhk7fuB+yAvf5sDgrpMvwJdIEBZI9grH7Uap/FPZtQLG99xdf2BEaTfd//Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opentui/core": "0.5.10", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/theme/node_modules/@opentui/core": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.5.10.tgz", + "integrity": "sha512-C3a2UbmefeAjIxAgm4BqjuSxKT4oqutfvYFwVvUgMxmGRHkNbBc/s7sukV0JgwcxFcV3uMFrXxo+E+BQtvuOiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.3.1", + "diff": "9.0.0", + "marked": "17.0.1", + "string-width": "7.2.0", + "strip-ansi": "7.1.2" + }, + "optionalDependencies": { + "@opentui/core-darwin-arm64": "0.5.10", + "@opentui/core-darwin-x64": "0.5.10", + "@opentui/core-linux-arm64": "0.5.10", + "@opentui/core-linux-arm64-musl": "0.5.10", + "@opentui/core-linux-x64": "0.5.10", + "@opentui/core-linux-x64-musl": "0.5.10", + "@opentui/core-win32-arm64": "0.5.10", + "@opentui/core-win32-x64": "0.5.10" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-darwin-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.5.10.tgz", + "integrity": "sha512-Vyb+nTbhab8ZcRy5gg1loEEGwRcIbjAeVRIBfHBcbFDqmITBOg7x2gqJ+x/TnoOy4uwMhCmICUN2wiyREw3r1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-darwin-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.5.10.tgz", + "integrity": "sha512-tTFLcM7Oj1gTyhm/bUdAt3C6grZdCxPk6+/g2azcZBUlI3/62LwbeRS6HbQKFFmm+1fUmX8cq6kWrtul885mVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.5.10.tgz", + "integrity": "sha512-ncJXcgudhBf2GdJyF3xVQN/Ec+1F7GOL+pRrURmgBYSj2v1w6EyoDQFAACtPTK2c3R38W6fvZwL4JSLlm4EFXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-arm64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.5.10.tgz", + "integrity": "sha512-dGMphDKexSdeYqwl0wgoFBP88Ta/cdi1Zc1mk29/ENkSCGz+74zlCHgqTHRNGLmI8W5TfuUtCyktQH11/Z+TBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.5.10.tgz", + "integrity": "sha512-5qtYaOgwVycZD1GaGshTRsi0rXPAmVExO03N1JQaHu+NYxK/vXSOc7Bu4QW0sPXx3Sp0SpzpP+FHjXABfoK66g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-x64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.5.10.tgz", + "integrity": "sha512-Oj4H9hApuvuTKPWxh4SoZAgGJorR7vbvnrZA/cAkSMAk2VGSoHRRcqeXQbcH8IcdjVZ0KFpv8Zkl/D5Ye+2mew==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-win32-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.5.10.tgz", + "integrity": "sha512-A9VhgvTxQoUdZ+8LmUumEng1sQNbj9QQQT3NYG9mSxI54qTANi7vOWNSphMiY6RMVsr22pgm6nUvSSvJXv7Jog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-win32-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.5.10.tgz", + "integrity": "sha512-u3KHa7kEeWrmKVDRJYpxSGO+g5E9cMGlrmTsPN3GVPHUmQMiREUawLXUvsU8+IHaQnqG3Q5nuE1yf4fPBzS+Qw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opencode/util": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.4.tgz", + "integrity": "sha512-jo6zhWqJ4WaU+fUjM8/Ea4i06gxTQFEFgfq2XG9J8gSDUhfsj4l9wJ3cLtSuKFQGeUeWqJJwRgDhCW+rUbis6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@effect/opentelemetry": "4.0.0-rc.112", + "@effect/platform-node": "4.0.0-rc.112", + "@effect/platform-node-shared": "4.0.0-rc.112", + "@npmcli/arborist": "9.4.0", + "@npmcli/config": "10.8.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/exporter-trace-otlp-http": "0.214.0", + "@opentelemetry/sdk-trace-base": "2.6.1", + "@opentelemetry/sdk-trace-node": "2.6.1", + "cross-spawn": "7.0.6", + "effect": "4.0.0-rc.112", + "glob": "13.0.5", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "npm-package-arg": "13.0.2", + "pacote": "21.5.1" + } + }, + "node_modules/@opencode/util/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@opencode/util/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@opencode/util/node_modules/glob": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.5.tgz", + "integrity": "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@opencode/util/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@opencode/util/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@opencode/util/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/@opencode/util/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.1.tgz", + "integrity": "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.214.0.tgz", + "integrity": "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-exporter-base": "0.214.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.214.0.tgz", + "integrity": "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-transformer": "0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.214.0.tgz", + "integrity": "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-logs": "0.214.0", + "@opentelemetry/sdk-metrics": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1", + "protobufjs": "^7.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz", + "integrity": "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", + "integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", + "integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.1.tgz", + "integrity": "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentui/core": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.4.5.tgz", + "integrity": "sha512-JsgRTPkA6e+Vxmumxai6SElOSlRQkbzNKHlCfemlArRiLhfC1IZ9RXJo2QH4xSu+uBOWAM90uss73/pPlkdEig==", + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.2.4", + "diff": "9.0.0", + "marked": "17.0.1", + "string-width": "7.2.0", + "strip-ansi": "7.1.2" + }, + "optionalDependencies": { + "@opentui/core-darwin-arm64": "0.4.5", + "@opentui/core-darwin-x64": "0.4.5", + "@opentui/core-linux-arm64": "0.4.5", + "@opentui/core-linux-arm64-musl": "0.4.5", + "@opentui/core-linux-x64": "0.4.5", + "@opentui/core-linux-x64-musl": "0.4.5", + "@opentui/core-win32-arm64": "0.4.5", + "@opentui/core-win32-x64": "0.4.5" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.4.5.tgz", + "integrity": "sha512-8KUG0oRidnR+oW1RSZJ72/PhZLl+qRRMk5U/mieF4c0SJ5V3tYACpBZAKzQfHNd1f7QzD8FHZct1lPpQgtmkWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-darwin-x64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.4.5.tgz", + "integrity": "sha512-R2bocsg55gwjOqCp/MWFgFYzRmsduKegB6nzgFAPCvAD/L5Jf30xpWJWFlSg3x8vxe1L9WJ84dfqa4M7mZZ3wA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.4.5.tgz", + "integrity": "sha512-R4MZ25a4CzOAGVjW9aj1hUfzQGVfCJwrwBDbNs2SXaIvzcZqkxCVtU4FoQ5LsaD0j/BdNQVg2CIfFkFsm1fDuQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-arm64-musl": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.4.5.tgz", + "integrity": "sha512-ieqdyKI6EIYPalYAETB2wsdP83hr5Ifi+dFnBFUmdEEFHsoKwBmn2S7bsTOYlX7Bg03F4/YPIg+IvRpeC+cUJw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.4.5.tgz", + "integrity": "sha512-SNyuQoxMKI1vuJhgxSSW96adWM6LqFl2SoS3GM4tGeneGOanVVG2Y06PvlytXvF4cKik97t0rqkVMRetmOs93w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64-musl": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.4.5.tgz", + "integrity": "sha512-mKVKcIcPiSVVZZsdPSBoWwoa2/TCeQAaMDeHF7PFw2kt5bTXZPP7xxWfRQLCNIcA1eaGl59UuwUWHDR2Ve548Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-win32-arm64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.4.5.tgz", + "integrity": "sha512-GHTTsqeR45q2Iek9Rb7ty+x/hAKn2jZ1ujlCgPR8LBKyF7h0E1dNFryoZ7ehMc3kJndP1sKn836IemKFqxuDdQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.4.5.tgz", + "integrity": "sha512-Y8T/yXCDGagRGiQrtmuB6AhRcPucKFs/Dre3v8kJwNYqDccI4FzUPKclZ7djfmRZNjl7JUqPhZZP/PwDpQocMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core/node_modules/bun-ffi-structs": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.2.4.tgz", + "integrity": "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg==", + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, + "node_modules/@opentui/solid": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.4.5.tgz", + "integrity": "sha512-B0RSkXnrtPVfEJOX+Hj+axjLJ3lzbG1BZw5I7Pvb9OPp48Vzg2cW2a3cSa86/q48ndLt647i/XwFPIw/jqnI5g==", + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.4.5", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.12", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", - "cpu": [ - "x64" - ], + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz", + "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.2.tgz", + "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz", + "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.34.1.tgz", + "integrity": "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", + "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", + "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.5.2.tgz", + "integrity": "sha512-nxu3SgmAw9JXT2CtkU0m/XNLWpP9MsaBx1zAGAypCbYj15tIFlmcYwpF+Oh18le83d+IM9PT7ENdXnE4C+d5mA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.5.2.tgz", + "integrity": "sha512-iq+cW3mAb7vfcxEEpYi3zXKpDtbrIFyanWjQl4zBq4seWD4OSxXDWSfespZxenX6aEaighn+NR3u1nU1DSvs3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tufjs/models/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "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/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws4fetch": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.7", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", + "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", + "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.12", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", + "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.12" + }, + "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==", + "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.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bin-links": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.2.tgz", + "integrity": "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bun-ffi-structs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.3.1.tgz", + "integrity": "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/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/cacache/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "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/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cmd-shim": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/effect": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-rc.112.tgz", + "integrity": "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-check": "^4.9.0", + "msgpackr": "^2.0.5" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.373", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.373.tgz", + "integrity": "sha512-G2Hym8JIf/QreuseqkDibgH8Ci8KfJzqGDKdakbhSx9UltwRBH2cBLAWU/lBX0sCdv0TlhyxQyDCnSfxgMWsjA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "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/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.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/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.10.1.tgz", + "integrity": "sha512-sB5Vghiu8MyCyToHoBVGsT0baZg3sZWNIY+a6Ct2EDrQJlT4YdH6MC1BSLNe3kX1k5i5g0q1O52XFgcKK/rGHg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "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-my-way-ts": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", + "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", + "dev": true, + "license": "MIT" + }, + "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/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-minipass/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/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/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" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", - "cpu": [ - "arm64" ], - "dev": true, + "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==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", - "cpu": [ - "x64" - ], + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", - "cpu": [ - "arm" - ], + "node_modules/gcp-metadata": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.4.tgz", + "integrity": "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", - "cpu": [ - "arm" - ], + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "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", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", - "cpu": [ - "arm64" - ], + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", - "cpu": [ - "arm64" - ], + "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/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.2.0.tgz", + "integrity": "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" - ], + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", - "cpu": [ - "loong64" - ], + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "cpu": [ - "ppc64" - ], + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", - "cpu": [ - "ppc64" - ], + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", - "cpu": [ - "riscv64" - ], + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "18 || 20 || >=22" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", - "cpu": [ - "riscv64" - ], + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", - "cpu": [ - "s390x" - ], + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", - "cpu": [ - "x64" - ], + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", - "cpu": [ - "x64" - ], + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 12" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" - ], + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", - "cpu": [ - "arm64" - ], + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "engines": { + "node": ">=10" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", - "cpu": [ - "arm64" - ], + "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/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "bignumber.js": "^9.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", - "cpu": [ - "ia32" - ], + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", - "cpu": [ - "x64" - ], + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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", - "optional": true, - "os": [ - "win32" - ] + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", - "cpu": [ - "x64" + "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/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", "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==", + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/kubernetes-types": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", + "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, - "node_modules/babel-plugin-jsx-dom-expressions": { - "version": "0.40.7", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", - "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "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" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "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==", + "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": { - "@babel/types": "^7.18.6" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=6" } }, - "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", + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "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": { - "find-babel-config": "^2.1.1", - "glob": "^9.3.3", - "pkg-up": "^3.1.0", - "reselect": "^4.1.7", - "resolve": "^1.22.8" + "yallist": "^3.0.2" } }, - "node_modules/babel-preset-solid": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", - "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.40.6" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "solid-js": "^1.9.12" - }, - "peerDependenciesMeta": { - "solid-js": { - "optional": true - } + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "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/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "node_modules/make-fetch-happen": { + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", + "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": ">=6.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node_modules/make-fetch-happen/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/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "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" - } - ], + "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", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, "bin": { - "browserslist": "cli.js" + "marked": "bin/marked.js" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 20" } }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" + "bin": { + "mime": "bin/cli.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" + "node": ">=16" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "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/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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": ">= 14.16.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", + "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": ">= 6" + "node": ">=8" } }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "node_modules/minipass-collect/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": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">=16 || 14 >=14.17" } }, - "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/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" } }, - "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/minipass-fetch/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/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "ms": "^2.1.3" + "minipass": "^3.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 8" } }, - "node_modules/diff": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", - "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", - "license": "BSD-3-Clause", + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.373", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.373.tgz", - "integrity": "sha512-G2Hym8JIf/QreuseqkDibgH8Ci8KfJzqGDKdakbhSx9UltwRBH2cBLAWU/lBX0sCdv0TlhyxQyDCnSfxgMWsjA==", + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "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" + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "node": ">=8" } }, - "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", + "node_modules/minipass-sized/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": ">=6" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "dependencies": { + "minipass": "^7.1.2" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": ">= 18" } }, - "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/minizlib/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/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" } }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "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/msgpackr": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.1.0.tgz", + "integrity": "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ==", "dev": true, "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" + "optionalDependencies": { + "msgpackr-extract": "^3.0.4" } }, - "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/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", "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==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/multipasta": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.8.tgz", + "integrity": "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "engines": { + "node": ">=10.5.0" } }, - "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", + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 0.4" + "node": "^20.17.0 || >=22.9.0" } }, - "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/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" + "detect-libc": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=20" } }, - "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", + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", "bin": { - "jsesc": "bin/jsesc" + "semver": "bin/semver.js" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, "bin": { - "json5": "lib/cli.js" + "node-which": "bin/which.js" }, "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "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/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" + "node": ">=18" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "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", + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "semver": "^7.1.1" }, "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "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==", + "node_modules/npm-install-checks/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "dev": true, - "license": "MIT", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "dev": true, + "license": "ISC", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "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", + "node_modules/npm-package-arg/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", "bin": { - "marked": "bin/marked.js" + "semver": "bin/semver.js" }, "engines": { - "node": ">= 20" + "node": ">=10" } }, - "node_modules/minimatch": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", - "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "dev": true, "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "node_modules/npm-pick-manifest/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "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/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "license": "MIT", + "node_modules/npm-registry-fetch/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": ">=18" + "node": ">=16 || 14 >=14.17" } }, "node_modules/object-assign": { @@ -2301,6 +6151,19 @@ "node": ">=6" } }, + "node_modules/p-map": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", + "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -2310,6 +6173,70 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.5.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", + "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pacote/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/parse-conflict-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -2492,6 +6419,20 @@ } } }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/prettier": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", @@ -2508,6 +6449,97 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pure-rand": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz", + "integrity": "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/read-cmd-shim": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -2569,6 +6601,70 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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/rollup": { "version": "4.60.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", @@ -2620,6 +6716,35 @@ "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==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2668,7 +6793,79 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz", + "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" } }, "node_modules/solid-js": { @@ -2692,6 +6889,54 @@ "node": ">= 12" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ssri/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/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -2709,6 +6954,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -2724,6 +7015,30 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -2759,6 +7074,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/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/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2812,6 +7164,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/toml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-4.3.0.tgz", + "integrity": "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -2822,6 +7184,16 @@ "tree-kill": "cli.js" } }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -2829,6 +7201,13 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tsup": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", @@ -2902,6 +7281,21 @@ "fsevents": "~2.3.3" } }, + "node_modules/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -2923,6 +7317,16 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -2960,19 +7364,55 @@ "browserslist": ">= 4.21.0" } }, - "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==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", + "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/emscripten": "^1.40.0" - }, - "peerDependenciesMeta": { - "@types/emscripten": { - "optional": true - } + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" } }, "node_modules/which": { @@ -2990,11 +7430,195 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "dev": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "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/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "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" + } } } } diff --git a/package.json b/package.json index 35326a14..89e0b7a4 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "author": "tarquinen", "license": "AGPL-3.0-or-later", "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3" + "@opencode-ai/plugin": ">=1.18.29" }, "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", @@ -64,7 +64,9 @@ "solid-js": "^1.9.12" }, "devDependencies": { - "@opencode-ai/plugin": "^1.4.3", + "@opencode-ai/plugin": "^1.18.29", + "@opencode/plugin": "^2.0.4", + "@opencode/theme": "^2.0.4", "@types/node": "^25.5.0", "prettier": "^3.8.1", "tsup": "^8.5.1", @@ -78,6 +80,7 @@ "files": [ "dist/", "lib/", + "server.js", "tui.tsx", "README.md", "LICENSE" diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs index 2e06700e..8f841e48 100644 --- a/scripts/verify-package.mjs +++ b/scripts/verify-package.mjs @@ -18,6 +18,7 @@ const requiredRepoFiles = [ "dist/index.d.ts", "dist/tui.d.ts", "tui.tsx", + "server.js", "README.md", "LICENSE", ] @@ -28,6 +29,7 @@ const requiredTarballFiles = [ "dist/index.d.ts", "dist/tui.d.ts", "tui.tsx", + "server.js", "README.md", "LICENSE", ] @@ -216,7 +218,8 @@ function validatePackedFiles() { encoding: "utf8", }) - const [result] = JSON.parse(output) + // npm versions return either an array or an object keyed by package name. + const [result] = Object.values(JSON.parse(output)) if (!result || !Array.isArray(result.files)) { fail("npm pack --dry-run --json did not return file metadata") } diff --git a/server.js b/server.js new file mode 100644 index 00000000..1842a449 --- /dev/null +++ b/server.js @@ -0,0 +1 @@ +export { default } from "./dist/index.js" diff --git a/tests/compaction-nudges.test.ts b/tests/compaction-nudges.test.ts new file mode 100644 index 00000000..a4aa4456 --- /dev/null +++ b/tests/compaction-nudges.test.ts @@ -0,0 +1,60 @@ +import assert from "node:assert/strict" +import test from "node:test" +import type { PluginConfig } from "../lib/config" +import { Logger } from "../lib/logger" +import { injectCompressNudges } from "../lib/messages/inject/inject" +import type { RuntimePrompts } from "../lib/prompts/store" +import { createSessionState, type WithParts } from "../lib/state" + +test("compaction replays existing nudges without changing the cached prefix or adding anchors", () => { + const state = createSessionState() + const logger = new Logger(false) + const config = { + compress: { + permission: "allow", + mode: "range", + minContextLimit: 0, + maxContextLimit: 1, + nudgeFrequency: 1, + summaryBuffer: false, + }, + } as PluginConfig + const prompts = { + contextLimitNudge: "NUDGE_KEEP", + turnNudge: "", + iterationNudge: "", + } as RuntimePrompts + const user = { + info: { + id: "msg_user", + role: "user", + time: { created: 1 }, + model: { providerID: "lab", modelID: "model" }, + }, + parts: [{ type: "text", text: "Original question" }], + } + const raw = [ + user, + { + info: { + id: "msg_answer", + role: "assistant", + time: { created: 2 }, + tokens: { input: 100, output: 1, reasoning: 0, cache: { read: 0, write: 0 } }, + }, + parts: [{ type: "text", text: "Original answer" }], + }, + ] as WithParts[] + const primary = structuredClone(raw) + injectCompressNudges(state, config, logger, primary, prompts) + assert.match(JSON.stringify(primary), /NUDGE_KEEP/) + const anchors = structuredClone(state.nudges) + const compact = structuredClone([ + ...raw, + { ...user, info: { ...user.info, id: "msg_later" } }, + ]) as WithParts[] + injectCompressNudges(state, config, logger, compact, prompts, undefined, false) + assert.deepEqual(compact.slice(0, raw.length), primary) + assert.deepEqual(state.nudges, anchors) + assert.doesNotMatch(JSON.stringify(compact.at(-1)), /NUDGE_KEEP/) +}) diff --git a/tests/v2-messages.test.ts b/tests/v2-messages.test.ts new file mode 100644 index 00000000..3061467f --- /dev/null +++ b/tests/v2-messages.test.ts @@ -0,0 +1,170 @@ +import assert from "node:assert/strict" +import test from "node:test" +import type { Message } from "@opencode/ai/schema/messages" +import { project } from "../lib/v2/messages" +import { createSessionState, type CompressionBlock } from "../lib/state" +import { assignMessageRefs } from "../lib/message-ids" +import { injectMessageIds, prune } from "../lib/messages" +import type { PluginConfig } from "../lib/config" +import { Logger } from "../lib/logger" + +const session = { + id: "ses_test", + agent: "build", + model: { id: "test", providerID: "test" }, +} as Parameters[2] +const config = { + compress: { mode: "range", permission: "allow", protectUserMessages: false }, +} as PluginConfig +const logger = new Logger(false) +function transcript(): Message[] { + return [ + { + role: "system", + content: [{ type: "compaction", provider: "test", encrypted: "opaque-checkpoint" }], + }, + { + id: "msg_user", + role: "user", + content: [ + { type: "media", mediaType: "image/png", data: new Uint8Array([0, 255]) }, + { type: "text", text: "Inspect this", cache: { type: "ephemeral" } }, + ], + }, + { + id: "msg_assistant", + role: "assistant", + metadata: { host: true }, + content: [ + { + type: "reasoning", + text: "Reasoning", + encrypted: "signed-reasoning", + providerMetadata: { test: { signature: "original" } }, + }, + { + type: "tool-call", + id: "call_one", + name: "read", + input: { path: "one" }, + providerMetadata: { test: { itemId: "fc_original" } }, + }, + { type: "tool-call", id: "call_two", name: "read", input: { path: "two" } }, + ], + }, + { + role: "tool", + content: [ + { + type: "tool-result", + id: "call_one", + name: "read", + result: { type: "text", value: "Original output" }, + providerMetadata: { test: { replay: true } }, + }, + { + type: "tool-result", + id: "call_two", + name: "read", + result: { + type: "content", + value: [ + { type: "file", uri: "data:image/png;base64,AA==", mime: "image/png" }, + ], + }, + }, + ], + }, + { id: "msg_latest", role: "user", content: [{ type: "text", text: "Continue" }] }, + ] as Message[] +} + +function entries(messages: Message[]) { + return messages + .filter((message) => message.id) + .map((message) => ({ + id: message.id, + type: message.role, + time: { created: 1 }, + agent: "build", + model: session.model, + content: [], + text: "", + })) as Parameters[1] +} + +test("V2 projection preserves all native content without edits", () => { + const messages = transcript() + assert.deepEqual(project(messages, entries(messages), session).restore(), messages) +}) + +test("V2 ID injection preserves signatures, media and tool pairing", () => { + const native = transcript() + const view = project(native, entries(native), session) + const state = createSessionState() + assignMessageRefs(state, view.messages) + injectMessageIds(state, config, view.messages, new Map()) + const restored = view.restore() + assert.deepEqual(restored[0], native[0]) + assert.equal(restored[1]!.content[0], native[1]!.content[0]) + assert.equal(restored[2]!.content[0], native[2]!.content[0]) + assert.equal(restored[3]!.content[1], native[3]!.content[1]) + assert.match(JSON.stringify(restored[3]!.content[0]), /m0002/) + assert.ok(!JSON.stringify(native).includes("m0002"), "native input must not be mutated") +}) + +test("V2 tool pruning replaces only marked results", () => { + const native = transcript() + const view = project(native, entries(native), session) + const state = createSessionState() + state.prune.tools.set("call_one", 100) + prune(state, logger, config, view.messages) + const restored = view.restore() + assert.match(JSON.stringify(restored[3]!.content[0]), /Output removed/) + assert.equal(restored[3]!.content[1], native[3]!.content[1]) + assert.deepEqual(restored[2], native[2]) +}) + +test("V2 compression removes the assistant and its ID-less results together", () => { + const native = transcript() + const view = project(native, entries(native), session) + const state = createSessionState() + state.prune.messages.byMessageId.set("msg_assistant", { + tokenCount: 100, + allBlockIds: [1], + activeBlockIds: [1], + }) + prune(state, logger, config, view.messages) + const restored = view.restore() + assert.deepEqual(restored, [native[0], native[1], native[4]]) +}) + +test("V2 leaves non-durable plugin messages and checkpoint tool results intact", () => { + const native = transcript() + const view = project(native, [], session) + assert.equal(view.messages.length, 0, "unselectable messages must not receive DCP IDs") + assert.deepEqual(view.restore(), native) +}) + +test("V2 inserts summaries after native checkpoints without an ordinary user message", () => { + const native = transcript().filter((message) => message.role !== "user") + const view = project(native, entries(native), session) + const state = createSessionState() + state.prune.messages.byMessageId.set("msg_assistant", { + tokenCount: 100, + allBlockIds: [1], + activeBlockIds: [1], + }) + state.prune.messages.activeByAnchorMessageId.set("msg_assistant", 1) + state.prune.messages.blocksById.set(1, { + blockId: 1, + active: true, + anchorMessageId: "msg_assistant", + summary: "CHECKPOINT_SUMMARY", + } as CompressionBlock) + prune(state, logger, config, view.messages, view.summaryBase) + const restored = view.restore() + assert.equal(restored.length, 2) + assert.deepEqual(restored[0], native[0]) + assert.deepEqual(restored[1]?.content, [{ type: "text", text: "CHECKPOINT_SUMMARY" }]) +}) From 83095ebfff7ffda933424edc625b320a75764f91 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 03/15] feat: support the DCP panel in the V2 terminal Use component-owned V2 keymaps, dialogs, and themes with shared panel views. Read and update server-owned state through typed RPC while preserving the V1 panel. --- lib/tui/dialogs.tsx | 48 +++++++++-------- lib/tui/modals.tsx | 9 ++-- lib/tui/types.ts | 18 ++++++- lib/tui/ui.tsx | 41 +++++++------- lib/v2/tui.tsx | 126 ++++++++++++++++++++++++++++++++++++++++++++ tui.tsx | 5 +- 6 files changed, 196 insertions(+), 51 deletions(-) create mode 100644 lib/v2/tui.tsx diff --git a/lib/tui/dialogs.tsx b/lib/tui/dialogs.tsx index d262d76a..1f1c5802 100644 --- a/lib/tui/dialogs.tsx +++ b/lib/tui/dialogs.tsx @@ -1,17 +1,13 @@ /** @jsxImportSource @opentui/solid */ -import { compressPermission } from "../compress-permission" -import { analyzeContextTokens } from "../commands/context" -import type { PluginConfig } from "../config" -import type { SessionState, WithParts } from "../state" +import type { analyzeContextTokens } from "../commands/context" import { formatTokenCount } from "../ui/utils" -import { TextAttributes } from "@opentui/core" import { formatDuration, formatRatio } from "./format" import { ActionRow, Card, DcpFrame, Metric, Progress, PromptRow, StatusPill } from "./ui" -import type { StatsReport, TuiApi } from "./types" +import type { StatsReport, ViewApi } from "./types" export function StatusDialog(props: { - api: TuiApi + api: ViewApi title: string eyebrow: string message: string @@ -26,13 +22,12 @@ export function StatusDialog(props: { } export function ContextDialog(props: { - api: TuiApi - state: SessionState - messages: WithParts[] + api: ViewApi + breakdown: ReturnType onBack: () => void }) { const theme = props.api.theme.current - const breakdown = analyzeContextTokens(props.state, props.messages) + const breakdown = props.breakdown const total = Math.max(0, breakdown.total) const activePruned = breakdown.prunedToolCount + breakdown.prunedMessageCount @@ -96,7 +91,7 @@ export function ContextDialog(props: { ) } -export function StatsDialog(props: { api: TuiApi; report: StatsReport; onBack: () => void }) { +export function StatsDialog(props: { api: ViewApi; report: StatsReport; onBack: () => void }) { const theme = props.api.theme.current const ratio = formatRatio(props.report.sessionTokens, props.report.sessionSummaryTokens) return ( @@ -155,15 +150,16 @@ export function StatsDialog(props: { api: TuiApi; report: StatsReport; onBack: ( } export function PanelDialog(props: { - api: TuiApi - state: SessionState - config: PluginConfig + api: ViewApi + manualMode: boolean + canCompress: boolean + blockedReason?: string onContext: () => void onStats: () => void onManual: (enabled: boolean) => void }) { const theme = props.api.theme.current - const canCompress = compressPermission(props.state, props.config) !== "deny" + const canCompress = props.canCompress return ( @@ -191,11 +187,17 @@ export function PanelDialog(props: { accent="primary" /> ) : ( - Compression is denied by permissions. + + {props.blockedReason ?? "Compression is denied by permissions."} + )} - + void }) { const theme = props.api.theme.current - const enabled = !!props.state.manualMode + const enabled = props.enabled const track = enabled ? theme.success : theme.error return ( - - Manual mode + + Manual mode ( openPanelModal(api, config)} /> )) @@ -65,8 +66,8 @@ export function openPanelModal(api: TuiApi, config: PluginConfig) { showDialog(api, () => ( openContextModal(api, config)} onStats={() => openStatsModal(api, config)} onManual={(enabled) => setManualMode(api, config, data.state.sessionId, enabled)} diff --git a/lib/tui/types.ts b/lib/tui/types.ts index a8c447ef..8f3e0c31 100644 --- a/lib/tui/types.ts +++ b/lib/tui/types.ts @@ -2,8 +2,22 @@ import type { TuiPluginModule } from "@opencode-ai/plugin/tui" import type { buildStatsReport } from "../commands/stats" export type TuiApi = Parameters>[0] -export type Theme = TuiApi["theme"]["current"] -export type ThemeColor = Exclude +export type Theme = Pick< + TuiApi["theme"]["current"], + | "primary" + | "accent" + | "text" + | "textMuted" + | "background" + | "backgroundElement" + | "borderSubtle" + | "selectedListItemText" + | "success" + | "warning" + | "error" +> +export type ThemeColor = keyof Theme +export type ViewApi = { theme: { readonly current: Theme }; ui: { dialog: { clear(): void } } } export type StatsReport = Awaited> export type DcpCommand = { diff --git a/lib/tui/ui.tsx b/lib/tui/ui.tsx index 7d544ceb..9adbf378 100644 --- a/lib/tui/ui.tsx +++ b/lib/tui/ui.tsx @@ -1,12 +1,11 @@ /** @jsxImportSource @opentui/solid */ -import { TextAttributes } from "@opentui/core" import type { JSX } from "solid-js" import { pct } from "./format" -import type { Theme, ThemeColor, TuiApi } from "./types" +import type { Theme, ThemeColor, ViewApi } from "./types" export function DcpFrame(props: { - api: TuiApi + api: ViewApi title?: string eyebrow: string children: JSX.Element @@ -17,12 +16,12 @@ export function DcpFrame(props: { - - {props.eyebrow} + + {props.eyebrow} {props.title ? ( - - {props.title} + + {props.title} ) : null} @@ -89,8 +88,8 @@ export function Card(props: { theme: Theme; title: string; children: JSX.Element borderColor={accent} gap={1} > - - {props.title} + + {props.title} {props.children} @@ -104,8 +103,8 @@ export function Metric(props: { theme: Theme; label: string; value: string; hint {props.label} - - {props.value} + + {props.value} {props.hint ? {props.hint} : null} @@ -132,8 +131,8 @@ export function Progress(props: { {props.label} - - {pct(props.value, props.total)} + + {pct(props.value, props.total)} {props.detail} @@ -156,8 +155,8 @@ export function PromptRow(props: { return ( - - {props.command} + + {props.command} @@ -177,12 +176,12 @@ export function StatusPill(props: { return ( - - {props.label} + + {props.label} - - {props.value} + + {props.value} ) @@ -205,8 +204,8 @@ export function ActionRow(props: { > - - {props.title} + + {props.title} {props.detail} diff --git a/lib/v2/tui.tsx b/lib/v2/tui.tsx new file mode 100644 index 00000000..397a4989 --- /dev/null +++ b/lib/v2/tui.tsx @@ -0,0 +1,126 @@ +/** @jsxImportSource @opentui/solid */ +import type { Plugin } from "@opencode/plugin/tui" +import { ContextDialog, PanelDialog, StatsDialog, StatusDialog } from "../tui/dialogs" +import type { ViewApi } from "../tui/types" +import { rpc } from "./rpc" + +export async function setup(ctx: Plugin.Context) { + const client = ctx.client.rpc(rpc) + const options = () => ({ location: ctx.location ?? ctx.data.location.default() }) + if (!(await client.status({}, options())).enabled) return + const api: ViewApi = { + theme: { + get current() { + const theme = ctx.theme.contextual.overlay + return { + primary: theme.text.action.primary.default, + accent: theme.text.action.secondary.default, + text: theme.text.default, + textMuted: theme.text.subdued, + background: theme.background.default, + backgroundElement: theme.background.surface.offset, + borderSubtle: theme.border.default, + selectedListItemText: theme.background.default, + success: theme.text.feedback.success.default, + warning: theme.text.feedback.warning.default, + error: theme.text.feedback.error.default, + } + }, + }, + ui: { dialog: { clear: () => ctx.ui.dialog.clear() } }, + } + function show(render: Parameters[0]) { + ctx.ui.dialog.set({ size: "xlarge" }) + ctx.ui.dialog.show(render) + } + async function open(page: "panel" | "context" | "stats" = "panel") { + const route = ctx.ui.router.current() + if (route.type !== "session") { + show(() => ( + + )) + return + } + const sessionID = route.sessionID + try { + const data = await client.snapshot({ sessionID }, options()) + const back = () => { + void open() + } + if (page === "context") + show(() => ) + else if (page === "stats") + show(() => ) + else + show(() => ( + { + void open("context") + }} + onStats={() => { + void open("stats") + }} + onManual={(enabled) => { + void client + .manual({ sessionID, enabled }, options()) + .then(back) + .catch(error) + }} + /> + )) + } catch (cause) { + error(cause) + } + } + function error(cause: unknown) { + const message = + cause instanceof Error + ? cause.message + : typeof cause === "object" && cause && "message" in cause + ? String(cause.message) + : String(cause) + show(() => ) + } + ctx.ui.slot({ + append: "app", + render() { + ctx.keymap.layer(() => ({ + mode: "global", + commands: [ + { + id: "dcp.panel", + title: "DCP", + description: "Open DCP panel", + group: "DCP", + palette: true, + slash: { name: "dcp", arguments: true }, + run: async (input) => { + if (!input?.trim()) return open() + const route = ctx.ui.router.current() + if (route.type !== "session") return open() + try { + await ctx.client.session.command({ + sessionID: route.sessionID, + name: "dcp", + text: input, + }) + } catch (cause) { + error(cause) + } + }, + }, + ], + })) + return null + }, + }) +} diff --git a/tui.tsx b/tui.tsx index b719a77f..8a4c89d5 100644 --- a/tui.tsx +++ b/tui.tsx @@ -4,6 +4,8 @@ import type { TuiPluginModule } from "@opencode-ai/plugin/tui" import { registerCommands } from "./lib/tui/commands" import { loadConfig } from "./lib/tui/data" import { openPanelModal } from "./lib/tui/modals" +import type { Plugin } from "@opencode/plugin/tui" +import { setup } from "./lib/v2/tui" const tui: TuiPluginModule["tui"] = async (api) => { const config = loadConfig(api) @@ -22,5 +24,6 @@ const tui: TuiPluginModule["tui"] = async (api) => { export default { id: "opencode-dcp", + setup, tui, -} satisfies TuiPluginModule +} satisfies TuiPluginModule & Plugin.Definition From 30267547a5be8922eaeb23e2a0949bb697f4af95 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 04/15] test: add containerized V1 and V2 integration coverage Exercise packed plugins with HTTP and WebSocket compression, permissions, concurrent sessions, persistence, compaction, and terminal automation. Include isolated live Codex checks and programmatic capture inspection. --- scripts/lab.mjs | 59 ++++++++++++ tests/lab/Dockerfile | 18 ++++ tests/lab/api.mjs | 188 +++++++++++++++++++++++++++++++++++++ tests/lab/inspect.mjs | 47 ++++++++++ tests/lab/live.mjs | 149 ++++++++++++++++++++++++++++++ tests/lab/mock.mjs | 148 ++++++++++++++++++++++++++++++ tests/lab/process.mjs | 30 ++++++ tests/lab/run.mjs | 209 ++++++++++++++++++++++++++++++++++++++++++ tests/lab/ui.py | 106 +++++++++++++++++++++ 9 files changed, 954 insertions(+) create mode 100644 scripts/lab.mjs create mode 100644 tests/lab/Dockerfile create mode 100644 tests/lab/api.mjs create mode 100644 tests/lab/inspect.mjs create mode 100644 tests/lab/live.mjs create mode 100644 tests/lab/mock.mjs create mode 100644 tests/lab/process.mjs create mode 100644 tests/lab/run.mjs create mode 100644 tests/lab/ui.py diff --git a/scripts/lab.mjs b/scripts/lab.mjs new file mode 100644 index 00000000..3dcc8cc6 --- /dev/null +++ b/scripts/lab.mjs @@ -0,0 +1,59 @@ +import { execFileSync } from "node:child_process" +import { mkdirSync, readFileSync, writeFileSync } from "node:fs" +import { homedir } from "node:os" +import { dirname, join, resolve } from "node:path" +import { fileURLToPath } from "node:url" + +const repo = resolve(dirname(fileURLToPath(import.meta.url)), "..") +const root = + process.env.DCP_LAB_DIR || + `/tmp/opencode/dcp-lab/${new Date().toISOString().replace(/[:.]/g, "-")}` +const artifacts = join(root, "artifacts") +const runtime = join(root, "runtime") +const live = process.argv.includes("--live") +mkdirSync(artifacts, { recursive: true, mode: 0o700 }) +mkdirSync(runtime, { recursive: true, mode: 0o700 }) +if (live) { + const auth = JSON.parse(readFileSync(join(homedir(), ".codex/auth.json"), "utf8")) + if (!auth.tokens?.access_token || !auth.tokens.account_id) + throw new Error("Live tests require existing Codex authentication") + const data = join(runtime, "live") + mkdirSync(data, { recursive: true, mode: 0o700 }) + writeFileSync( + join(data, "auth.json"), + JSON.stringify({ access: auth.tokens.access_token, account: auth.tokens.account_id }), + { mode: 0o600 }, + ) +} +execFileSync("npm", ["pack", "--pack-destination", artifacts], { + cwd: resolve(repo, "../opencode-request-logger"), + stdio: "pipe", +}) +if (!live && !process.argv.includes("--built")) { + execFileSync("npm", ["run", "build"], { cwd: repo, stdio: "inherit" }) +} +execFileSync("npm", ["pack", "--ignore-scripts", "--pack-destination", artifacts], { + cwd: repo, + stdio: "pipe", +}) +console.log(`Lab output: ${root}`) +execFileSync( + "docker", + [ + "run", + "--rm", + "--init", + "--user", + `${process.getuid()}:${process.getgid()}`, + "--mount", + `type=bind,source=${runtime},target=/lab`, + "--mount", + `type=bind,source=${artifacts},target=/artifacts,readonly`, + "--mount", + `type=bind,source=${join(repo, "tests/lab")},target=/test,readonly`, + "dcp-lab:2.0.4", + "node", + live ? "/test/live.mjs" : "/test/run.mjs", + ], + { stdio: "inherit" }, +) diff --git a/tests/lab/Dockerfile b/tests/lab/Dockerfile new file mode 100644 index 00000000..3207d9fc --- /dev/null +++ b/tests/lab/Dockerfile @@ -0,0 +1,18 @@ +FROM node:24-bookworm-slim + +ARG V2=2.0.4 +ARG V1=1.18.29 +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* \ + && npm install --prefix /opt/v2 @opencode/cli@${V2} \ + && npm install --prefix /opt/v1 opencode-ai@${V1} + +ENV PATH="/opt/v2/node_modules/.bin:${PATH}" \ + HOME=/lab/home \ + XDG_CONFIG_HOME=/lab/home/config \ + XDG_DATA_HOME=/lab/home/data \ + XDG_CACHE_HOME=/lab/home/cache \ + XDG_STATE_HOME=/lab/home/state \ + REQUEST_LOG_DIR=/lab/logs +WORKDIR /lab/project +CMD ["opencode2", "--version"] diff --git a/tests/lab/api.mjs b/tests/lab/api.mjs new file mode 100644 index 00000000..13f22ca6 --- /dev/null +++ b/tests/lab/api.mjs @@ -0,0 +1,188 @@ +import assert from "node:assert/strict" +import { spawn } from "node:child_process" +import { writeFile } from "node:fs/promises" +import { inspect } from "./inspect.mjs" + +async function serve(cli, options) { + const child = spawn(cli, ["serve", "--hostname", "127.0.0.1", "--port", "0"], { + ...options, + env: { ...options.env, OPENCODE_SERVER_PASSWORD: "dcp-lab" }, + stdio: ["ignore", "pipe", "pipe"], + }) + let output = "" + const url = await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + child.kill("SIGKILL") + reject(new Error(`Server startup timed out: ${output}`)) + }, 30000) + child.on("error", reject) + child.on("exit", (code) => { + clearTimeout(timer) + reject(new Error(`Server exited ${code}: ${output}`)) + }) + for (const stream of [child.stdout, child.stderr]) + stream.on("data", (chunk) => { + output += chunk + const match = output.match(/server listening on (http:\/\/\S+)/) + if (match) { + clearTimeout(timer) + resolve(match[1]) + } + }) + }) + async function request(path, body) { + const response = await fetch(`${url}${path}`, { + method: body === undefined ? "GET" : "POST", + headers: { + "content-type": "application/json", + authorization: `Basic ${Buffer.from("opencode:dcp-lab").toString("base64")}`, + }, + body: body === undefined ? undefined : JSON.stringify(body), + signal: AbortSignal.timeout(30000), + }) + const text = await response.text() + assert.ok(response.ok, `${path}: ${response.status} ${text}`) + return text ? JSON.parse(text) : undefined + } + return { + request, + rpc: async (method, input) => + ( + await request( + `/api/rpc/dcp/${method}?${new URLSearchParams({ "location[directory]": options.cwd })}`, + { input }, + ) + ).output, + async close() { + const exited = new Promise((resolve) => child.once("exit", resolve)) + child.kill("SIGINT") + await exited + await writeFile(`${options.record}.server`, output) + }, + } +} + +export async function commands(cli, options, sessionID) { + let server = await serve(cli, options) + let expected + let compactID + try { + const snapshot = () => server.rpc("snapshot", { sessionID }) + const command = (text) => + server.request(`/api/session/${sessionID}/command`, { name: "dcp", text }) + const before = await snapshot() + assert.equal(before.context.prunedMessageCount, 1) + assert.equal(before.canCompress, true) + await server.rpc("manual", { sessionID, enabled: true }) + assert.equal((await snapshot()).manualMode, true) + await command("manual off") + assert.equal((await snapshot()).manualMode, false) + await command("decompress b1") + assert.equal((await snapshot()).context.prunedMessageCount, 0) + await command("recompress b1") + assert.deepEqual((await snapshot()).stats, before.stats) + + const create = async (effect) => + ( + await server.request("/api/session", { + title: `DCP ${effect}`, + agent: "build", + model: { providerID: "lab", id: "gpt-5.4" }, + location: { directory: options.cwd }, + permissions: [{ action: "compress", resource: "*", effect }], + }) + ).data.id + const prompt = async (id, text) => { + await server.request(`/api/session/${id}/prompt`, { text }) + await server.request(`/api/experimental/session/${id}/wait`, {}) + return (await server.request(`/api/session/${id}/context`)).data + } + for (const effect of ["ask", "deny"]) { + const id = await create(effect) + const messages = await prompt(id, `OLD_PAYLOAD: Test ${effect}`) + const data = await server.rpc("snapshot", { sessionID: id }) + assert.equal(data.canCompress, false) + assert.equal(data.context.prunedMessageCount, 0) + const calls = messages.flatMap((message) => + message.type === "assistant" + ? message.content.filter( + (part) => part.type === "tool" && part.name === "compress", + ) + : [], + ) + if (effect === "deny") assert.equal(calls.length, 0) + else { + assert.equal(calls.length, 1) + assert.equal(calls[0].state.status, "error") + assert.match(JSON.stringify(calls[0].state.error), /not supported/) + } + } + const ids = await Promise.all([create("allow"), create("allow")]) + compactID = ids[0] + const histories = await Promise.all( + ids.map((id, index) => prompt(id, `OLD_PAYLOAD: Independent session ${index}`)), + ) + for (const [index, id] of ids.entries()) { + const data = await server.rpc("snapshot", { sessionID: id }) + assert.equal(data.context.prunedMessageCount, 1) + assert.equal( + histories[index].filter((message) => message.type === "assistant").length, + 2, + ) + } + expected = await snapshot() + } finally { + await server.close() + } + server = await serve(cli, options) + try { + assert.deepEqual( + await server.rpc("snapshot", { sessionID }), + expected, + "DCP state changed after server restart", + ) + await server.request(`/api/session/${compactID}/prompt`, { + text: "Keep this latest exchange.", + }) + await server.request(`/api/experimental/session/${compactID}/wait`, {}) + await server.request(`/api/session/${compactID}/compact`, {}) + await server.request(`/api/experimental/session/${compactID}/wait`, {}) + const messages = (await server.request(`/api/session/${compactID}/context`)).data + assert.ok( + messages.some( + (message) => message.type === "compaction" && message.status === "completed", + ), + ) + const { captures } = await inspect(options.env.REQUEST_LOG_DIR) + const requests = captures + .filter((entry) => entry.type === "http.request" && entry.sessionID === compactID) + .sort((a, b) => a.timestamp.localeCompare(b.timestamp)) + const primary = requests.filter((entry) => entry.kind === "primary").at(-1).body + const compact = requests.find((entry) => entry.kind === "compaction").body + const prefix = compact.input.slice(0, -1) // OpenCode appends its summary instruction after plugin hooks. + assert.ok(JSON.stringify(prefix).includes("LAB_SUMMARY")) + assert.ok(!JSON.stringify(prefix).includes("OLD_PAYLOAD")) + assert.deepEqual( + prefix, + primary.input.slice(0, prefix.length), + "DCP changed the cached compaction prefix", + ) + assert.deepEqual( + compact.instructions, + primary.instructions, + "DCP changed the system prefix", + ) + } finally { + await server.close() + } + return { + rpc: true, + manual: true, + decompress: true, + recompress: true, + permissions: true, + concurrent: true, + restart: true, + compaction: true, + } +} diff --git a/tests/lab/inspect.mjs b/tests/lab/inspect.mjs new file mode 100644 index 00000000..d9327a88 --- /dev/null +++ b/tests/lab/inspect.mjs @@ -0,0 +1,47 @@ +import { readdir, readFile } from "node:fs/promises" +import { join } from "node:path" +import { pathToFileURL } from "node:url" + +export async function inspect(directory) { + const captures = [] + async function visit(folder) { + for (const entry of await readdir(folder, { withFileTypes: true })) { + const file = join(folder, entry.name) + if (entry.isDirectory()) await visit(file) + else if (file.endsWith(".json")) captures.push(JSON.parse(await readFile(file, "utf8"))) + else if (file.endsWith(".jsonl")) { + const text = await readFile(file, "utf8") + captures.push(...text.trim().split("\n").filter(Boolean).map(JSON.parse)) + } + } + } + await visit(directory) + const count = (type) => captures.filter((entry) => entry.type === type).length + return { + captures, + summary: { + sessions: [...new Set(captures.map((entry) => entry.sessionID))], + contexts: count("context"), + httpRequests: count("http.request"), + httpResponses: count("http.response"), + httpCompleted: captures.filter( + (entry) => entry.type === "http.end" && entry.status === "completed", + ).length, + httpCancelled: captures.filter( + (entry) => entry.type === "http.end" && entry.status === "cancelled", + ).length, + wsConnections: count("ws.open"), + wsRequests: captures.filter( + (entry) => entry.type === "ws.frame" && entry.direction === "request", + ).length, + wsResponses: captures.filter( + (entry) => entry.type === "ws.frame" && entry.direction === "response", + ).length, + errors: count("ws.error"), + }, + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + console.log(JSON.stringify((await inspect(process.argv[2])).summary, null, 2)) +} diff --git a/tests/lab/live.mjs b/tests/lab/live.mjs new file mode 100644 index 00000000..eb6c7635 --- /dev/null +++ b/tests/lab/live.mjs @@ -0,0 +1,149 @@ +import assert from "node:assert/strict" +import { mkdir, readFile, writeFile } from "node:fs/promises" +import { join } from "node:path" +import { pathToFileURL } from "node:url" +import { inspect } from "./inspect.mjs" +import { run } from "./process.mjs" + +await run("npm", [ + "install", + "--prefix", + "/lab/plugins", + "--omit=dev", + "--ignore-scripts", + "/artifacts/opencode-request-logger-0.1.0.tgz", + "/artifacts/tarquinen-opencode-dcp-3.1.15.tgz", +]) +const logger = "/lab/plugins/node_modules/opencode-request-logger" +const dcp = "/lab/plugins/node_modules/@tarquinen/opencode-dcp" +const { createRelay } = await import(pathToFileURL(join(logger, "relay.mjs"))) +const root = "/lab/live" +const auth = JSON.parse(await readFile(join(root, "auth.json"), "utf8")) +const directory = join(root, "project") +const config = join(root, "config/opencode") +await Promise.all([directory, config].map((path) => mkdir(path, { recursive: true }))) +for (const transport of ["http", "websocket"]) { + const logs = join(root, transport, "logs") + await mkdir(logs, { recursive: true }) + const relay = createRelay({ directory: logs }) + await new Promise((resolve) => relay.server.listen(0, "127.0.0.1", resolve)) + try { + await writeFile( + join(config, "opencode.json"), + JSON.stringify({ + update: "disable", + model: "openai/gpt-5.6-sol", + plugins: [ + { package: dcp }, + { + package: logger, + options: { + directory: logs, + relay: `ws://127.0.0.1:${relay.server.address().port}`, + }, + }, + ], + permissions: [{ action: "compress", resource: "*", effect: "allow" }], + providers: { + openai: { + settings: { + baseURL: "https://chatgpt.com/backend-api/codex", + apiKey: "{env:DCP_TOKEN}", + }, + headers: { + "chatgpt-account-id": "{env:DCP_ACCOUNT}", + originator: "opencode", + }, + models: { "gpt-5.6-sol": { transport } }, + }, + }, + }), + ) + await writeFile( + join(config, "dcp.json"), + JSON.stringify({ autoUpdate: false, debug: true, pruneNotification: "off" }), + ) + const prompt = + "LIVE_RAW_PAYLOAD: We are testing DCP in an isolated environment. Everything in this user message is disposable test content. Call compress exactly once on this message using its injected message ID, with summary 'DCP_LIVE_SUMMARY: disposable test fixture. Compression is done; reply with exactly DCP_LIVE_OK and do not use more tools.' After the tool completes, reply with exactly DCP_LIVE_OK. Do not use other tools." + const output = await run( + "/opt/v2/node_modules/.bin/opencode2", + ["run", "--standalone", "--format", "json", "--model", "openai/gpt-5.6-sol", prompt], + { + cwd: directory, + record: join(root, transport, "run"), + env: { + ...process.env, + DCP_TOKEN: auth.access, + DCP_ACCOUNT: auth.account, + HOME: root, + PWD: directory, + XDG_CONFIG_HOME: join(root, "config"), + XDG_DATA_HOME: join(root, "data"), + XDG_STATE_HOME: join(root, "state"), + XDG_CACHE_HOME: join(root, "cache"), + OPENCODE_CONFIG_DIR: config, + OPENCODE_LOG_LEVEL: "DEBUG", + }, + }, + ) + const { captures, summary } = await inspect(logs) + const requests = captures + .filter( + (entry) => + entry.kind === "primary" && + (transport === "http" + ? entry.type === "http.request" + : entry.type === "ws.frame" && entry.direction === "request"), + ) + .map((entry) => entry.body) + assert.ok( + requests.length >= 2, + "Live compression did not produce a follow-up model request", + ) + assert.ok( + requests.some( + (body) => + JSON.stringify(body.input).includes("DCP_LIVE_SUMMARY") && + !JSON.stringify(body.input).includes("LIVE_RAW_PAYLOAD"), + ), + "Live compression did not replace the original content on the wire", + ) + const state = JSON.parse( + await readFile( + join(root, "data/opencode/storage/plugin/dcp", `${summary.sessions[0]}.json`), + "utf8", + ), + ) + assert.equal(state.prune.messages.activeBlockIds.length, 1) + const result = { + transport, + ...summary, + reply: output + .split("\n") + .filter(Boolean) + .map((line) => JSON.parse(line)) + .some( + (entry) => entry.type === "text" && entry.part?.text.trim() === "DCP_LIVE_OK", + ), + compression: true, + } + await writeFile(join(root, transport, "result.json"), JSON.stringify(result, null, 2)) + console.log(JSON.stringify(result)) + assert.ok( + result.reply, + "Live model did not return the expected reply; inspect isolated run output", + ) + assert.ok( + captures.some( + (entry) => + entry.kind === "primary" && + (transport === "http" + ? entry.type === "http.request" + : entry.type === "ws.frame" && entry.direction === "request"), + ), + `Live ${transport} traffic was not captured`, + ) + } finally { + await relay.close() + } +} diff --git a/tests/lab/mock.mjs b/tests/lab/mock.mjs new file mode 100644 index 00000000..091fba11 --- /dev/null +++ b/tests/lab/mock.mjs @@ -0,0 +1,148 @@ +import { createServer } from "node:http" +import { randomUUID } from "node:crypto" + +export function events(text = "MOCK_OK", call) { + const id = `resp_${randomUUID()}` + const item = { + id: `msg_${randomUUID()}`, + type: "message", + role: "assistant", + status: "completed", + content: [{ type: "output_text", text, annotations: [] }], + } + const response = { + id, + object: "response", + created_at: Math.floor(Date.now() / 1000), + model: "gpt-5.4", + status: "completed", + output: [item], + usage: { + input_tokens: 100, + output_tokens: 5, + total_tokens: 105, + input_tokens_details: { cached_tokens: 0 }, + output_tokens_details: { reasoning_tokens: 0 }, + }, + } + if (call) { + const item = { + id: `fc_${randomUUID()}`, + type: "function_call", + call_id: `call_${randomUUID()}`, + name: "compress", + arguments: JSON.stringify(call), + status: "completed", + } + return [ + { + type: "response.created", + response: { ...response, status: "in_progress", output: [] }, + }, + { + type: "response.output_item.added", + output_index: 0, + item: { ...item, status: "in_progress", arguments: "" }, + }, + { + type: "response.function_call_arguments.delta", + item_id: item.id, + output_index: 0, + delta: item.arguments, + }, + { + type: "response.function_call_arguments.done", + item_id: item.id, + output_index: 0, + arguments: item.arguments, + }, + { type: "response.output_item.done", output_index: 0, item }, + { type: "response.completed", response: { ...response, output: [item] } }, + ].map((event, sequence_number) => ({ ...event, sequence_number })) + } + return [ + { type: "response.created", response: { ...response, status: "in_progress", output: [] } }, + { + type: "response.output_item.added", + output_index: 0, + item: { ...item, status: "in_progress", content: [] }, + }, + { + type: "response.content_part.added", + item_id: item.id, + output_index: 0, + content_index: 0, + part: { type: "output_text", text: "", annotations: [] }, + }, + { + type: "response.output_text.delta", + item_id: item.id, + output_index: 0, + content_index: 0, + delta: text, + }, + { + type: "response.output_text.done", + item_id: item.id, + output_index: 0, + content_index: 0, + text, + }, + { + type: "response.content_part.done", + item_id: item.id, + output_index: 0, + content_index: 0, + part: item.content[0], + }, + { type: "response.output_item.done", output_index: 0, item }, + { type: "response.completed", response }, + ].map((event, sequence_number) => ({ ...event, sequence_number })) +} + +export async function createMock(WebSocketServer) { + const requests = [] + function respond(body) { + const tool = body.tools?.find((tool) => tool.name === "compress") + const text = JSON.stringify(body.input) + if (text.includes("You MUST summarize the conversation above")) + return events("## Objective\nDCP_NATIVE_SUMMARY: preserve the completed test work.") + if (!tool || text.includes("LAB_SUMMARY") || text.includes("function_call_output")) + return events() + const ref = text.match(/]*>(m\d+)<\/dcp-message-id>/)?.[1] + if (!ref) return events("MISSING_DCP_IDS") + const message = tool.parameters?.properties?.content?.items?.properties?.messageId + const item = message + ? { messageId: ref, topic: "Lab", summary: "LAB_SUMMARY" } + : { startId: ref, endId: ref, summary: "LAB_SUMMARY" } + return events(undefined, { topic: "Lab", content: [item] }) + } + const server = createServer(async (request, response) => { + const chunks = [] + for await (const chunk of request) chunks.push(chunk) + const body = JSON.parse(Buffer.concat(chunks).toString()) + requests.push({ transport: "http", body }) + response.writeHead(200, { "content-type": "text/event-stream" }) + for (const event of respond(body)) + response.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`) + response.end() + }) + const sockets = new WebSocketServer({ server }) + sockets.on("connection", (socket) => { + socket.on("message", (data) => { + const body = JSON.parse(data.toString()) + requests.push({ transport: "websocket", body }) + for (const event of respond(body)) socket.send(JSON.stringify(event)) + }) + }) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + return { + url: `http://127.0.0.1:${server.address().port}/v1`, + requests, + async close() { + for (const socket of sockets.clients) socket.terminate() + sockets.close() + await new Promise((resolve) => server.close(resolve)) + }, + } +} diff --git a/tests/lab/process.mjs b/tests/lab/process.mjs new file mode 100644 index 00000000..f360d4fe --- /dev/null +++ b/tests/lab/process.mjs @@ -0,0 +1,30 @@ +import assert from "node:assert/strict" +import { spawn } from "node:child_process" +import { writeFile } from "node:fs/promises" + +export async function run(file, args, options = {}) { + const child = spawn(file, args, { ...options, stdio: ["ignore", "pipe", "pipe"] }) + let stdout = "" + let stderr = "" + child.stdout.on("data", (data) => { + stdout += data + }) + child.stderr.on("data", (data) => { + stderr += data + }) + const timeout = setTimeout(() => child.kill("SIGKILL"), 120_000) + try { + const code = await new Promise((resolve, reject) => { + child.on("error", reject) + child.on("close", resolve) + }) + if (options.record) { + await writeFile(`${options.record}.stdout`, stdout) + await writeFile(`${options.record}.stderr`, stderr) + } + assert.equal(code, 0, `${file} failed: ${stderr.slice(-6000)}\n${stdout.slice(-2000)}`) + return stdout + } finally { + clearTimeout(timeout) + } +} diff --git a/tests/lab/run.mjs b/tests/lab/run.mjs new file mode 100644 index 00000000..1e0ac610 --- /dev/null +++ b/tests/lab/run.mjs @@ -0,0 +1,209 @@ +import assert from "node:assert/strict" +import { createRequire } from "node:module" +import { mkdir, readFile, writeFile } from "node:fs/promises" +import { join } from "node:path" +import { pathToFileURL } from "node:url" +import { createMock } from "./mock.mjs" +import { inspect } from "./inspect.mjs" +import { run } from "./process.mjs" +import { commands } from "./api.mjs" + +await run("npm", [ + "install", + "--prefix", + "/lab/plugins", + "--omit=dev", + "--ignore-scripts", + "/artifacts/opencode-request-logger-0.1.0.tgz", + "/artifacts/tarquinen-opencode-dcp-3.1.15.tgz", +]) +const logger = "/lab/plugins/node_modules/opencode-request-logger" +const dcp = "/lab/plugins/node_modules/@tarquinen/opencode-dcp" +const require = createRequire(join(logger, "package.json")) +const { WebSocketServer } = require("ws") +const { createRelay } = await import(pathToFileURL(join(logger, "relay.mjs"))) +const mock = await createMock(WebSocketServer) +try { + for (const [version, transport, mode] of [ + ["v2", "http", "range"], + ["v2", "websocket", "range"], + ["v2", "http", "message"], + ["v2", "websocket", "message"], + ["v1", "http", "range"], + ]) { + const root = `/lab/${version}-${transport}-${mode}` + const directory = join(root, "project") + const config = join(root, "config", "opencode") + const logs = join(root, "logs") + await Promise.all([directory, config, logs].map((path) => mkdir(path, { recursive: true }))) + const relay = createRelay({ directory: logs }) + await new Promise((resolve) => relay.server.listen(0, "127.0.0.1", resolve)) + try { + const options = { + directory: logs, + relay: `ws://127.0.0.1:${relay.server.address().port}`, + } + const settings = + version === "v2" + ? { + plugins: [{ package: dcp }, { package: logger, options }], + update: "disable", + model: "lab/gpt-5.4", + permissions: [{ action: "compress", resource: "*", effect: "allow" }], + providers: { + lab: { + package: "@opencode/ai/providers/openai/responses", + env: ["LAB_API_KEY"], + settings: { baseURL: mock.url }, + models: { + "gpt-5.4": { + transport, + compaction: { mode: "local" }, + limit: { context: 200000, output: 32000 }, + }, + }, + }, + }, + } + : { + plugin: [dcp, logger], + autoupdate: false, + model: "lab/gpt-5.4", + small_model: "lab/gpt-5.4", + permission: { compress: "allow" }, + provider: { + lab: { + npm: "@ai-sdk/openai", + options: { baseURL: mock.url, apiKey: "lab" }, + models: { + "gpt-5.4": { limit: { context: 200000, output: 32000 } }, + }, + }, + }, + } + await writeFile(join(config, "opencode.json"), JSON.stringify(settings)) + if (version === "v1") { + await writeFile(join(config, "tui.json"), JSON.stringify({ plugin: [dcp] })) + } + await writeFile( + join(config, "dcp.json"), + JSON.stringify({ + autoUpdate: false, + debug: true, + pruneNotification: "off", + compress: { mode }, + }), + ) + const env = { + ...process.env, + HOME: root, + PWD: directory, + XDG_CONFIG_HOME: join(root, "config"), + XDG_DATA_HOME: join(root, "data"), + XDG_STATE_HOME: join(root, "state"), + XDG_CACHE_HOME: join(root, "cache"), + OPENCODE_CONFIG_DIR: config, + REQUEST_LOG_DIR: logs, + LAB_API_KEY: "lab", + OPENCODE_LOG_LEVEL: "DEBUG", + } + const cli = + version === "v2" + ? "/opt/v2/node_modules/.bin/opencode2" + : "/opt/v1/node_modules/.bin/opencode" + const start = mock.requests.length + const output = await run( + cli, + [ + "run", + ...(version === "v2" ? ["--standalone"] : []), + "--format", + "json", + "--model", + "lab/gpt-5.4", + "OLD_PAYLOAD: This completed material can be compressed. Then reply with MOCK_OK.", + ], + { cwd: directory, env, record: join(root, "run") }, + ) + assert.ok(output.includes("MOCK_OK"), "CLI did not return the mock response") + const { captures, summary } = await inspect(logs) + const sent = mock.requests + .slice(start) + .filter((request) => request.transport === transport) + assert.ok(sent.length > 0, `${version} did not use ${transport}`) + const recorded = captures.filter((entry) => + transport === "http" + ? entry.type === "http.request" + : entry.type === "ws.frame" && entry.direction === "request", + ) + assert.equal(recorded.length, sent.length, "request capture missing or duplicated") + for (const request of sent) + assert.ok( + recorded.some( + (entry) => JSON.stringify(entry.body) === JSON.stringify(request.body), + ), + "wire request differs from captured body", + ) + const primary = sent.filter((request) => + request.body.tools?.some((tool) => tool.name === "compress"), + ) + assert.equal( + primary.length, + 2, + "compression must cause exactly one additional model step", + ) + assert.match(JSON.stringify(primary[0].body.input), /dcp-message-id/) + assert.match(JSON.stringify(primary[1].body.input), /LAB_SUMMARY/) + assert.ok( + !JSON.stringify(primary[1].body.input).includes("OLD_PAYLOAD"), + "compressed source remains in wire context", + ) + assert.ok( + !primary[1].body.previous_response_id, + "compression must reset the WebSocket continuation prefix", + ) + const calls = primary[1].body.input + .filter((item) => item.type === "function_call") + .map((item) => item.call_id) + const results = primary[1].body.input + .filter((item) => item.type === "function_call_output") + .map((item) => item.call_id) + assert.deepEqual(results, calls, "tool calls/results must remain paired") + if (transport === "http") { + assert.equal(summary.httpRequests, summary.httpResponses) + // OpenCode stops consuming at response.completed and can cancel + // before the HTTP stream reaches EOF. Verify protocol completion. + assert.equal(summary.httpResponses, summary.httpCompleted + summary.httpCancelled) + for (const entry of recorded) { + const body = await readFile( + join(logs, entry.sessionID, `${entry.requestID}.response.body`), + "utf8", + ) + const events = body + .split("\n") + .filter((line) => line.startsWith("data: ")) + .map((line) => JSON.parse(line.slice(6))) + assert.ok(events.some((event) => event.type === "response.completed")) + } + } else assert.ok(summary.wsResponses > 0) + if (version === "v2") assert.ok(summary.contexts > 0) + const result = { version, transport, mode, compression: true, ...summary } + if (version === "v2" && transport === "http" && mode === "range") { + Object.assign( + result, + await commands( + cli, + { cwd: directory, env, record: join(root, "api") }, + summary.sessions[0], + ), + ) + } + await writeFile(join(root, "result.json"), JSON.stringify(result, null, 2)) + console.log(JSON.stringify(result)) + } finally { + await relay.close() + } + } +} finally { + await mock.close() +} diff --git a/tests/lab/ui.py b/tests/lab/ui.py new file mode 100644 index 00000000..18ceaea3 --- /dev/null +++ b/tests/lab/ui.py @@ -0,0 +1,106 @@ +"""Run with: uv run --with pexpect --with pyte tests/lab/ui.py LAB_DIR [v1|v2].""" +import json +import os +from pathlib import Path +import sys +import time + +import pexpect +import pyte + +root = Path(sys.argv[1]) / "runtime" +version = sys.argv[2] if len(sys.argv) > 2 else "v2" +scenario = f"{version}-http-range" +folder = root / scenario +session = json.loads((folder / "result.json").read_text())["sessions"][0] +home = f"/lab/{scenario}" +args = ["run", "--rm", "--init", "-it", "--user", f"{os.getuid()}:{os.getgid()}", + "--mount", f"type=bind,source={root},target=/lab", "--workdir", f"{home}/project"] +env = {"HOME": home, "PWD": f"{home}/project", "TERM": "xterm-256color", "COLORTERM": "truecolor", + "XDG_CONFIG_HOME": f"{home}/config", "XDG_DATA_HOME": f"{home}/data", "XDG_STATE_HOME": f"{home}/state", + "XDG_CACHE_HOME": f"{home}/cache", "OPENCODE_CONFIG_DIR": f"{home}/config/opencode", "LAB_API_KEY": "lab"} +for key, value in env.items(): + args.extend(["-e", f"{key}={value}"]) +args.extend(["dcp-lab:2.0.4", f"/opt/{version}/node_modules/.bin/{'opencode2' if version == 'v2' else 'opencode'}"]) +if version == "v2": + args.append("--standalone") +args.extend(["--session", session]) +child = pexpect.spawn("docker", args, encoding="utf-8", codec_errors="replace", dimensions=(60, 130), timeout=1) +screen = pyte.Screen(130, 60) +stream = pyte.Stream(screen) +frames = {} +raw = open(folder / "ui.tty", "w") + +def pump(): + try: + text = child.read_nonblocking(65536, timeout=0.2) + except pexpect.TIMEOUT: + return + raw.write(text) + raw.flush() + stream.feed(text) + if "\x1b[6n" in text: + child.send("\x1b[1;1R") + if "\x1b[c" in text: + child.send("\x1b[?1;2c") + +def wait(check, label): + until = time.monotonic() + 30 + while time.monotonic() < until: + pump() + if check(): + frames[label] = screen.display.copy() + return + raise AssertionError(f"Timed out: {label}\n" + "\n".join(screen.display)) + +def visible(text): + return any(text in line for line in screen.display) + +def click(text): + wait(lambda: visible(text), f"ready-{text}") + for row, line in reversed(list(enumerate(screen.display))): + if text in line: + col = line.index(text) + 1 + child.send(f"\x1b[<0;{col};{row+1}M\x1b[<0;{col};{row+1}m") + return + raise AssertionError(f"Not visible: {text}") + +state_file = folder / "data/opencode/storage/plugin/dcp" / f"{session}.json" +def manual(): + return json.loads(state_file.read_text())["manualMode"] + +try: + wait(lambda: visible("MOCK_OK"), "session") + child.send("/dcp") + wait(lambda: visible("/dcp"), "typed") + if version == "v2": + wait(lambda: visible("Open DCP panel"), "registered") + child.send("\r") + if version == "v2": + # Argument-taking slashes first complete the name; submitting then runs it. + wait(lambda: not visible("Open DCP panel"), "completed") + child.send("\r") + wait(lambda: visible("Session State"), "panel") + click("Context") + wait(lambda: visible("Total in context") and visible("Breakdown"), "context") + click("back") + wait(lambda: visible("Session State"), "panel-back") + click("Stats") + wait(lambda: visible("Compression ratio") and visible("All time"), "stats") + click("back") + wait(lambda: visible("Manual mode"), "manual") + initial = manual() + click("■") + wait(lambda: manual() != initial, "toggled") + child.send("\x1b") + wait(lambda: not visible("Session State"), "closed") + print(json.dumps({"version": version, "panel": True, "context": True, "stats": True, "manual": True, "close": True})) +finally: + (folder / "ui.frames.json").write_text(json.dumps(frames, indent=2)) + child.sendcontrol("c") + child.sendcontrol("c") + try: + child.expect(pexpect.EOF, timeout=10) + except pexpect.TIMEOUT: + child.terminate(force=True) + raw.close() From 353ab3a4eeec89eefe7abea8fa2d5551a6ec01b6 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 05/15] feat: add an isolated dual-version DCP sandbox Provide persistent and fresh profiles, separate V1/V2 state, current Codex authentication, and automatically maintained raw/readable request logs. Rebuild both local plugins on each launch. --- package.json | 1 + scripts/sandbox.mjs | 258 +++++++++++++++++++++++++++++++++++++ scripts/sandbox/Dockerfile | 17 +++ scripts/sandbox/run.mjs | 163 +++++++++++++++++++++++ 4 files changed, 439 insertions(+) create mode 100755 scripts/sandbox.mjs create mode 100644 scripts/sandbox/Dockerfile create mode 100644 scripts/sandbox/run.mjs diff --git a/package.json b/package.json index 89e0b7a4..451430f2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "check:package": "npm run build && npm run verify:package", "prepublishOnly": "npm run check:package", "dev": "opencode plugin dev", + "sandbox": "node scripts/sandbox.mjs", "typecheck": "tsc --noEmit", "test": "node --import tsx --test tests/*.test.ts", "format": "prettier --write .", diff --git a/scripts/sandbox.mjs b/scripts/sandbox.mjs new file mode 100755 index 00000000..afe87eed --- /dev/null +++ b/scripts/sandbox.mjs @@ -0,0 +1,258 @@ +#!/usr/bin/env node +import { execFileSync, spawn } from "node:child_process" +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { homedir } from "node:os" +import { dirname, join, resolve } from "node:path" +import { fileURLToPath } from "node:url" +import { parseArgs } from "node:util" + +const repo = resolve(dirname(fileURLToPath(import.meta.url)), "..") +const root = resolve(process.env.DCP_SANDBOX_DIR || join(homedir(), ".local/state/dcp-sandbox")) +const separator = process.argv.indexOf("--", 2) +const cli = separator === -1 ? [] : process.argv.slice(separator + 1) +const { values } = parseArgs({ + args: process.argv.slice(2, separator === -1 ? undefined : separator), + options: { + help: { type: "boolean", short: "h" }, + fresh: { type: "boolean" }, + update: { type: "boolean" }, + v1: { type: "boolean" }, + v2: { type: "boolean" }, + opencode: { type: "string" }, + model: { type: "string" }, + transport: { type: "string" }, + logs: { type: "boolean" }, + path: { type: "boolean" }, + }, +}) + +if (values.help) { + console.log(`Usage: dcp-sandbox [options] [-- OpenCode arguments] + +Open isolated OpenCode with this checkout's DCP and sibling request logger. +Sessions and scratch files persist. Both plugins are rebuilt on every launch. + + --v1 Use V1 (default: 1.18.29, HTTP), with its own saved state + --v2 Use V2 (default), with its own saved state + --fresh Start a new empty sandbox; keep old ones + --logs Show the latest raw/readable log paths and capture counts + --path Print the current sandbox's host directory + --update Remember the latest release of the selected major version + --opencode VERSION Remember an exact version (V1 requires 1.18.29+) + --model MODEL Remember an OpenAI model (default: openai/gpt-5.6-sol) + --transport TYPE V2: websocket or http; V1: http only + +Examples: + dcp-sandbox + dcp-sandbox --v1 + dcp-sandbox --v1 --logs + dcp-sandbox -- --continue + dcp-sandbox -- run --format json "Reply with OK." + +State: ${root} +Auth: DCP_CODEX_AUTH, or $CODEX_HOME/auth.json, or ~/.codex/auth.json +Override the state directory with DCP_SANDBOX_DIR.`) + process.exit(0) +} + +function json(path, fallback) { + return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : fallback +} + +function save(path, value) { + writeFileSync(path, JSON.stringify(value, null, 2) + "\n", { mode: 0o600 }) +} + +async function main() { + process.umask(0o077) + if (values.v1 && values.v2) throw new Error("Choose --v1 or --v2, not both.") + const major = values.v1 ? 1 : values.v2 ? 2 : Number(values.opencode?.split(".")[0] || 2) + if (![1, 2].includes(major)) throw new Error("Choose an OpenCode 1.x or 2.x version.") + // Each major has independent sessions and settings. + const state = major === 1 ? join(root, "v1") : root + const stamp = new Date().toISOString().replace(/[:.]/g, "-") + const current = join(state, "current.json") + const profile = values.fresh ? stamp : json(current, "default") + const home = join(state, "profiles", profile) + if (values.path) { + console.log(home) + return + } + if (values.logs) { + const latest = json(join(home, "latest.json"), null) + if (!latest) throw new Error(`No launches recorded yet. Run dcp-sandbox --v${major} first.`) + const logs = join(home, "logs", latest) + console.log(`Readable logs: ${join(logs, "readable")}`) + console.log(`Raw logs: ${join(logs, "raw")}`) + const index = json(join(logs, "readable/index.json"), null) + if (index) console.log(JSON.stringify(index.summary, null, 2)) + return + } + + const interactive = process.stdin.isTTY && process.stdout.isTTY + if (!interactive && cli.length === 0) + throw new Error("An interactive terminal is required. For automation, use -- run .") + + const authPath = + process.env.DCP_CODEX_AUTH || + join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json") + const auth = json(authPath, null)?.tokens + if (!auth?.access_token || !auth.account_id) { + throw new Error( + `ChatGPT authentication not found in ${authPath}. Sign in with codex login first.`, + ) + } + const claims = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString()) + if (claims.exp * 1000 <= Date.now()) { + throw new Error( + "Codex access token has expired. Refresh your login in Codex, then launch again.", + ) + } + const settingsPath = join(state, "settings.json") + const settings = json(settingsPath, { + version: major === 1 ? "1.18.29" : "2.0.4", + model: "openai/gpt-5.6-sol", + transport: major === 1 ? "http" : "websocket", + }) + if (values.update && values.opencode) + throw new Error("Choose --update or --opencode, not both.") + if (values.update) { + const versions = JSON.parse( + execFileSync( + "npm", + ["view", major === 1 ? "opencode-ai@1" : "@opencode/cli@2", "version", "--json"], + { + encoding: "utf8", + }, + ), + ) + settings.version = Array.isArray(versions) ? versions.at(-1) : versions + } + if (values.opencode) settings.version = values.opencode + if (values.model) settings.model = values.model + if (values.transport) settings.transport = values.transport + const version = /^(1|2)\.(\d+)\.(\d+)(-[\w.-]+)?$/.exec(settings.version) + if (!version || Number(version[1]) !== major) + throw new Error( + `--opencode requires an exact ${major}.x version matching the selected host.`, + ) + if ( + major === 1 && + (Number(version[2]) < 18 || + (Number(version[2]) === 18 && + (Number(version[3]) < 29 || (Number(version[3]) === 29 && version[4])))) + ) + throw new Error("DCP's shared entrypoint requires OpenCode 1.18.29 or newer.") + if (!settings.model.startsWith("openai/")) + throw new Error("This Codex sandbox requires an openai/ model.") + if (!["websocket", "http"].includes(settings.transport)) + throw new Error("--transport must be websocket or http.") + if (major === 1 && settings.transport !== "http") + throw new Error( + "The V1 sandbox uses HTTP so all requests can be logged. Use --transport http.", + ) + + const input = join(state, "launches", stamp) + mkdirSync(input, { recursive: true, mode: 0o700 }) + mkdirSync(home, { recursive: true, mode: 0o700 }) + // Docker otherwise creates its bind-mounted WORKDIR as root. + mkdirSync(join(home, "project"), { recursive: true, mode: 0o700 }) + const setup = join(input, "setup.log") + function command(file, args, cwd = repo) { + try { + return execFileSync(file, args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }) + } catch (error) { + writeFileSync(setup, `${error.stdout || ""}\n${error.stderr || ""}`, { mode: 0o600 }) + throw new Error(`${file} ${args.join(" ")} failed. Details: ${setup}`, { cause: error }) + } + } + command("docker", ["info", "--format", "{{.ServerVersion}}"]) + const image = `dcp-sandbox:${settings.version}` + console.log(`Preparing OpenCode ${settings.version} container (cached after first build)…`) + command("docker", [ + "build", + "--build-arg", + `VERSION=${settings.version}`, + "--build-arg", + `PACKAGE=${major === 1 ? "opencode-ai" : "@opencode/cli"}`, + "-t", + image, + join(repo, "scripts/sandbox"), + ]) + console.log("Building DCP and request logger…") + const packages = [] + for (const directory of [repo, resolve(repo, "../opencode-request-logger")]) { + if (!existsSync(join(directory, "node_modules"))) + command("npm", ["ci", "--legacy-peer-deps"], directory) + command("npm", ["run", "build"], directory) + const packed = JSON.parse( + command( + "npm", + ["pack", "--ignore-scripts", "--json", "--pack-destination", input], + directory, + ), + ) + packages.push(Object.values(packed)[0].filename) + } + save(settingsPath, settings) + save(current, profile) + save(join(home, "latest.json"), stamp) + save(join(input, "launch.json"), { ...settings, major, packages, stamp, args: cli }) + const token = join(input, "auth.json") + save(token, { access: auth.access_token, account: auth.account_id }) + console.log(`OpenCode ${settings.version} · ${settings.model} · ${settings.transport}`) + console.log(`Workspace: ${join(home, "project")}`) + console.log(`DCP config: ${join(home, "home/config/opencode/dcp.jsonc")}`) + console.log(`Readable logs: ${join(home, "logs", stamp, "readable")}`) + console.log(`Raw logs: ${join(home, "logs", stamp, "raw")}`) + try { + const child = spawn( + "docker", + [ + "run", + "--rm", + "--init", + ...(interactive ? ["-it"] : ["-i"]), + "--user", + `${process.getuid()}:${process.getgid()}`, + "--mount", + `type=bind,source=${home},target=/lab`, + "--mount", + `type=bind,source=${input},target=/input,readonly`, + "--mount", + `type=bind,source=${join(repo, "scripts/sandbox")},target=/launcher,readonly`, + "--env", + `TERM=${process.env.TERM || "xterm-256color"}`, + "--env", + `COLORTERM=${process.env.COLORTERM || "truecolor"}`, + image, + "node", + "/launcher/run.mjs", + ], + { stdio: "inherit" }, + ) + const stop = () => child.kill("SIGTERM") + process.on("SIGTERM", stop) + process.on("SIGINT", stop) + try { + process.exitCode = await new Promise((resolve, reject) => { + child.once("error", reject) + child.once("exit", (code) => resolve(code ?? 1)) + }) + } finally { + process.off("SIGTERM", stop) + process.off("SIGINT", stop) + } + } finally { + rmSync(token, { force: true }) + } +} + +main().catch((error) => { + console.error(`dcp-sandbox: ${error.message}`) + process.exitCode = 1 +}) diff --git a/scripts/sandbox/Dockerfile b/scripts/sandbox/Dockerfile new file mode 100644 index 00000000..7b2935c7 --- /dev/null +++ b/scripts/sandbox/Dockerfile @@ -0,0 +1,17 @@ +FROM node:24-bookworm-slim + +ARG PACKAGE=@opencode/cli +ARG VERSION=2.0.4 +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* \ + && npm install --prefix /opt/opencode ${PACKAGE}@${VERSION} + +ENV HOME=/lab/home \ + XDG_CONFIG_HOME=/lab/home/config \ + XDG_DATA_HOME=/lab/home/data \ + XDG_CACHE_HOME=/lab/home/cache \ + XDG_STATE_HOME=/lab/home/state \ + OPENCODE_CONFIG_DIR=/lab/home/config/opencode \ + PWD=/lab/project +WORKDIR /lab/project +CMD ["node", "/launcher/run.mjs"] diff --git a/scripts/sandbox/run.mjs b/scripts/sandbox/run.mjs new file mode 100644 index 00000000..54af7668 --- /dev/null +++ b/scripts/sandbox/run.mjs @@ -0,0 +1,163 @@ +import { execFileSync, spawn } from "node:child_process" +import { mkdir, readFile, writeFile } from "node:fs/promises" +import { join } from "node:path" +import { pathToFileURL } from "node:url" + +process.umask(0o077) +const launch = JSON.parse(await readFile("/input/launch.json", "utf8")) +const auth = JSON.parse(await readFile("/input/auth.json", "utf8")) +try { + execFileSync( + "npm", + [ + "install", + "--prefix", + "/lab/plugins", + "--omit=dev", + "--ignore-scripts", + "--no-audit", + "--no-fund", + ...launch.packages.map((file) => join("/input", file)), + ], + { stdio: "pipe" }, + ) +} catch (error) { + console.error(error.stderr?.toString() || error.message) + process.exit(1) +} +const logger = "/lab/plugins/node_modules/opencode-request-logger" +const dcp = "/lab/plugins/node_modules/@tarquinen/opencode-dcp" +const { createRelay } = await import(pathToFileURL(join(logger, "relay.mjs"))) +const { watch } = await import(pathToFileURL(join(logger, "readable.mjs"))) +const logs = join("/lab/logs", launch.stamp) +const raw = join(logs, "raw") +const config = process.env.OPENCODE_CONFIG_DIR +await Promise.all([raw, config, "/lab/project"].map((path) => mkdir(path, { recursive: true }))) +const readable = await watch(raw, join(logs, "readable")) +const relay = createRelay({ directory: raw }) +await new Promise((resolve) => relay.server.listen(0, "127.0.0.1", resolve)) +try { + await writeFile( + join(config, "opencode.json"), + JSON.stringify( + launch.major === 1 + ? { + $schema: "https://opencode.ai/config.json", + autoupdate: false, + model: launch.model, + small_model: launch.model, + plugin: [dcp, logger], + permission: { compress: "allow" }, + provider: { + openai: { + options: { + baseURL: "https://chatgpt.com/backend-api/codex", + apiKey: "{env:DCP_TOKEN}", + headers: { + "chatgpt-account-id": "{env:DCP_ACCOUNT}", + originator: "opencode", + }, + }, + }, + }, + } + : { + $schema: "https://opencode.ai/config.json", + update: "disable", + model: launch.model, + plugins: [ + { package: dcp }, + { + package: logger, + options: { + directory: raw, + relay: `ws://127.0.0.1:${relay.server.address().port}`, + }, + }, + ], + permissions: [{ action: "compress", resource: "*", effect: "allow" }], + providers: { + openai: { + settings: { + baseURL: "https://chatgpt.com/backend-api/codex", + apiKey: "{env:DCP_TOKEN}", + }, + headers: { + "chatgpt-account-id": "{env:DCP_ACCOUNT}", + originator: "opencode", + }, + models: { + [launch.model.slice("openai/".length)]: { + transport: launch.transport, + }, + }, + }, + }, + }, + null, + 2, + ) + "\n", + ) + if (launch.major === 1) { + // Register the panel once, retaining later user terminal preferences. + try { + await writeFile( + join(config, "tui.json"), + JSON.stringify({ plugin: [dcp] }, null, 2) + "\n", + { flag: "wx" }, + ) + } catch (error) { + if (error.code !== "EEXIST") throw error + } + } + try { + await writeFile( + join(config, "dcp.jsonc"), + JSON.stringify( + { + $schema: + "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/main/dcp.schema.json", + autoUpdate: false, + debug: true, + pruneNotification: "off", + }, + null, + 2, + ) + "\n", + { flag: "wx" }, + ) + } catch (error) { + if (error.code !== "EEXIST") throw error + } + const child = spawn( + `/opt/opencode/node_modules/.bin/${launch.major === 1 ? "opencode" : "opencode2"}`, + [...launch.args, ...(launch.major === 1 ? [] : ["--standalone"])], + { + cwd: "/lab/project", + stdio: "inherit", + env: { + ...process.env, + DCP_TOKEN: auth.access, + DCP_ACCOUNT: auth.account, + OPENCODE_LOG_LEVEL: "DEBUG", + ...(launch.major === 1 ? { OPENCODE_EXPERIMENTAL_WEBSOCKETS: "false" } : {}), + REQUEST_LOG_DIR: raw, + }, + }, + ) + const stop = () => child.kill("SIGTERM") + process.on("SIGTERM", stop) + process.on("SIGINT", stop) + try { + process.exitCode = await new Promise((resolve, reject) => { + child.once("error", reject) + child.once("exit", (code) => resolve(code ?? 1)) + }) + } finally { + process.off("SIGTERM", stop) + process.off("SIGINT", stop) + } +} finally { + await relay.close() + await readable.close() +} From a252de269444b682e9d2ef41dffa1bfa88de49f4 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:18:26 -0400 Subject: [PATCH 06/15] docs: document V2 migration and sandbox workflows Record architecture decisions, compatibility limits, usage, reproducible checks, and verification evidence for DCP and request logging. --- .gitignore | 3 +- README.md | 104 +++++++++++ docs/migration-v2.md | 423 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 docs/migration-v2.md diff --git a/.gitignore b/.gitignore index 2f165e92..882f0d87 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,8 @@ notes/ test-update.ts # Documentation (local development only) -docs/ +docs/* +!docs/migration-v2.md SCHEMA_NOTES.md repomix-output.xml diff --git a/README.md b/README.md index 771fce67..3a8e4981 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,110 @@ opencode plugin @tarquinen/opencode-dcp@latest --global This installs the package and adds it to your global OpenCode config. +### OpenCode V2 migration + +This working tree targets **OpenCode 2.0.4** and retains **V1 1.18.29+** support +through a shared package entrypoint. This initial migration has passed the +documented integration checks; it has not been published as a new release. + +For a local V2 installation, build this checkout and add its directory to your +V2 `opencode.json`: + +```jsonc +{ + "plugins": [{ "package": "/absolute/path/to/opencode-dynamic-context-pruning" }], + "permissions": [{ "action": "compress", "resource": "*", "effect": "allow" }], +} +``` + +Existing `dcp.jsonc` settings still apply. The V2 adapter supports range/message +compression and exposes the DCP panel through `/dcp`. V2 currently blocks +`compress: ask` because the public plugin API has no permission-request method. +Model-invisible chat reports are omitted; their reporting extension point remains +available for a later implementation. DCP's self-updater remains V1-only. + +For a local V1 installation, register the directory in `opencode.json`'s `plugin` +array. To load the panel, also register it in the separate `tui.json`: + +```jsonc +{ "plugin": ["/absolute/path/to/opencode-dynamic-context-pruning"] } +``` + +See [the migration journal](docs/migration-v2.md) for architecture notes, test +results, remaining gaps, and the isolated container test workflow. + +### Manual V1/V2 sandbox + +From this checkout, run: + +```sh +npm run sandbox +``` + +This builds DCP and the sibling `opencode-request-logger` checkout, prepares a +clean Docker image automatically, and opens OpenCode **2.0.4** with both plugins. +It uses your current ChatGPT token from `~/.codex/auth.json` (or `CODEX_HOME` / +`DCP_CODEX_AUTH`), the `openai/gpt-5.6-sol` model, and WebSockets. Docker and Node/npm +are required. If the token expires, refresh your login in Codex and relaunch. + +Sessions, an empty-to-start scratch workspace, and editable `dcp.jsonc` persist +under `~/.local/state/dcp-sandbox/`. Your host project, normal OpenCode config, +agents, plugins, and service are not mounted or inherited. Each launch has its +own raw and readable JSON logs; the launcher starts and stops the relay for you. +Try `/dcp` for the panel or `/dcp-compress` to request compression manually. + +Use `dcp-sandbox --v1` (or `npm run sandbox -- --v1`) for OpenCode **1.18.29** +over HTTP. V1 has its own sessions, workspace, and settings under the `v1/` +subdirectory. Plain `dcp-sandbox` always selects V2. Each major retains its own +profile database. Add `--v1` to management commands when working with V1. + +```sh +npm run sandbox -- --fresh # New empty sandbox; keep old runs +npm run sandbox -- --logs # Show log paths and capture counts +npm run sandbox -- --v1 --logs # Same for the V1 sandbox +npm run sandbox -- --path # Current sandbox's host directory +npm run sandbox -- -- --continue # Resume a sandbox session +npm run sandbox -- --update # Remember the latest OpenCode 2.x +npm run sandbox -- --v1 --update # Remember the latest OpenCode 1.x +npm run sandbox -- --opencode 2.0.4 # Pin a particular version again +npm run sandbox -- --transport http # Switch transport (remembered) +``` + +Every launch rebuilds both plugins, so relaunch after changing their code. Model, +transport, and OpenCode-version selections are remembered; updates are explicit. +`--fresh` switches subsequent launches to the new sandbox. DCP settings and CLI +preferences are preserved; `opencode.json` is launcher-managed. Override the +state location with `DCP_SANDBOX_DIR` and see `npm run sandbox -- --help` for more. + +Logs have `raw/` and `readable/` directories. Readable requests appear as they are +sent, and assembled responses appear as soon as they finish. The watcher runs +throughout the session, including while a WebSocket stays open for further requests. +`--logs` shows the paths and capture counts without generating or rewriting files. +Start at `readable/index.json`, then a session's numbered request folders: + +```text +readable//0001_primary_websocket/ + request.json # Pretty-printed body actually sent + response.json # Assistant content, parsed tool calls, token totals, errors + meta.json # Timing, transport, completion, raw source, continuation ID +``` + +V2's full pre-transport snapshots are in each session's `context/` directory. +Readable responses omit echoed prompts, tool definitions, encrypted reasoning, +and detailed usage attribution; those remain available in the raw captures. +WebSocket continuation requests remain deltas with `previous_response_id`; the +formatter does not invent a full wire request. Partial/failed responses are marked +in metadata, and original HTTP bytes/WS frames remain in `raw/`. No transcript +Markdown is generated. + +To install an executable shortcut on Linux: + +```sh +chmod +x scripts/sandbox.mjs +ln -s "$PWD/scripts/sandbox.mjs" ~/.local/bin/dcp-sandbox +dcp-sandbox +``` + ## Project Status Development on DCP has slowed because most new context-management work has moved to [Sleev](https://sleev.ai) and the `sleev` CLI. Sleev is a local proxy for Claude Code, Codex, and OpenCode that builds on DCP's core ideas with newer context-management features and will work with any harness/client. diff --git a/docs/migration-v2.md b/docs/migration-v2.md new file mode 100644 index 00000000..d6056451 --- /dev/null +++ b/docs/migration-v2.md @@ -0,0 +1,423 @@ +# OpenCode V2 migration journal + +## Current status + +The first migration pass is implemented for **V2 2.0.4** and **V1 1.18.29+**. +Packed server tests pass for both hosts; V2 range/message compression works over +HTTP and WebSockets. Real Codex-backed compression and both terminal panels have +also passed. Changes are local and unpublished. + +Intentional limitations: V2 `ask` is blocked, invisible chat reports use a no-display +extension point, and the self-updater remains V1-only. See verification and remaining +coverage below before treating this as a release-ready compatibility guarantee. + +## Goal and decisions + +Port DCP to OpenCode V2 while retaining V1 support. Investigate architecture and +existing behavior first, establish an isolated test environment with request and +response capture, then migrate and test features incrementally. + +- 2026-09-16: user selected **OpenCode 1.18.29+** as the V1 compatibility floor. + V2 documents a shared default export with `id`, `setup()` (V2), and `server()` + (V1). The two implementations still use different APIs. +- User selected **2.0.4** as the first V2 target. +- User requested migrating the existing DCP terminal panel in this pass as well. +- Preserve duplicate/error pruning timing: run these strategies when `compress` + runs. The user confirmed this deliberately limits prompt-cache invalidation. +- Compaction must apply the same DCP body edits and preserve the existing cache + prefix. Replay existing nudges, but do not introduce new ones (user clarification). +- User approved omitting V2 displays for V1 `ignored` reports for now. Leave a + small reporting extension point, without building a replacement UI. +- Live smoke tests should use the user's configured `openai/gpt-5.6-sol` model. +- V2 `compress: ask` will be blocked with a clear error for now (user decision). + Custom tools must request permission themselves, but the public V2 plugin + context exposes only permission list/get/reply and hooks. `options.permission` + controls denied-tool visibility, not an ask dialog. +- Keep this journal updated with evidence, decisions, test results, and open + questions. Do not infer feature parity from successful plugin loading alone. + +## Verified environment + +| Item | Evidence | +| --------------------- | -------------------------------------------------------------------------------------------------------- | +| OpenCode V1 source | `/home/dan/src/opencode`, `dev` at `501ff62cbf`; `packages/opencode/package.json` reports `1.18.31` | +| OpenCode V2 source | same repository, `v2` at `cda2bc5100`; `packages/cli/package.json` reports `2.0.4` | +| Source checkout | currently on `dev`; inspect V2 separately | +| Installed V1 | `opencode --version`: `1.18.30` | +| Installed V2 | `opencode2 --version`: `v0.0.0-beta-19425`; this differs from the V2 branch/release | +| Launch configuration | `.bashrc`: `oc1` uses `~/src/opencode-config`; `oc2` uses `~/src/opencode-config/v2` | +| Provider routing | both normal configurations use local provider proxies; clean tests need their own provider configuration | +| Container runtime | Docker available, server `29.7.2` | +| DCP starting state | `dev`, clean working tree, package version `3.1.15` | +| Logger starting state | separate repository at `../opencode-request-logger`, `main`; pre-existing untracked `bun.lock` | + +Credential locations have been identified without copying credentials into this +repository. Test credentials and traffic logs belong in isolated runtime storage. + +## Sources + +- +- +- +- Local OpenCode branch snapshots listed above. + +## Initial architecture findings + +- V1 plugins return a hook/tool object from a function. V2 plugins register domain + hooks and synchronous registry transforms from `setup(ctx)`. +- V2's context is also the server client; V1's separate `ctx.client` and old SDK + request/response envelopes do not carry over. +- V1 system/message transforms converge into V2's `session.hook("context")`. + V2 separates primary requests, compaction, title generation, and transient + generation into distinct hooks. +- Context edits affect outgoing requests, not persisted conversation history. +- V2 commands have registered executors; tools use JSON Schema and structured + results. There is no mutable global config hook. +- V2 has no direct equivalent to `experimental.text.complete`. +- V2 plugin instances are location-scoped and may observe multiple sessions; + DCP's current single mutable session state needs careful review for concurrency. +- Public HTTP request/response hooks support native provider capture. WebSocket + traffic bypasses them; the documented experimental WebSocket hook exposes only + handshake URL/headers. Frame capture requires further source investigation. + +## Existing DCP map (investigation in progress) + +- `index.ts`: initialization, V1 hook wiring, compression tool selection, config + changes, command registration, permission snapshots, update checks. +- `lib/hooks.ts`: system instructions; message cleanup, stable references, + compression sync, tool cache, pruning, subagent results, nudges, message tags; + commands; output tag stripping; compression timing events. +- `lib/state/`: current session, pruning records, compressed blocks, message IDs, + token counters, persisted manual mode, compaction resets. +- `lib/messages/prune.ts`: summary insertion/removal and output/input pruning; + special handling for question/edit/write results. +- `lib/compress/`: validate all ranges before applying state; expand nested block + placeholders and append omitted required summaries; preserve protected user + text, `` sections, tool outputs, and file patterns. Message mode skips + invalid/protected/already-compressed selections and groups diagnostics. +- `lib/compress/pipeline.ts`: manual-mode gate, permission request, history read, + ID assignment, duplicate/error strategies, persistence, and notifications. +- `lib/strategies/`: deduplicate by tool name and normalized/sorted parameters, + keeping the newest; remove old failed-tool inputs while retaining errors. +- `lib/messages/sync.ts`: replay block activation from surviving compression + origins, consumed blocks, and user decompression flags (important for undo). +- `lib/messages/inject/`: stable nudge anchors above configured model/token + thresholds; IDs on text/tool results; user protection; manual trigger injection. +- `lib/token-utils.ts`: provider-reported usage plus approximate tokenizer counts; + V1's token fields and compaction markers need explicit V2 translation. +- `lib/commands/`: context/stats reports, sweep with tool/file protections, + persisted manual toggle, compression prompt, decompress/recompress block groups. +- `lib/ui/notification.ts`: V1 chat output uses `noReply` plus `ignored` text so + reports never enter model context. V2 synthetic messages are model-visible and + are not an equivalent replacement; UI delivery needs an explicit design. +- `tui.tsx` and `lib/tui/`: a separate V1 CLI plugin owns `/dcp` panels and reads + filesystem state. V2 has a different CLI context, theme, keymap, and data API. +- `lib/config.ts` and `lib/prompts/store.ts`: global, config-directory, and nearest + `.opencode` overrides; custom prompt files; V1 toast warnings. Preserve existing + configuration semantics while adapting host dependencies. +- `scripts/verify-package.mjs`: verifies both server and CLI entrypoints and npm + tarball contents. Shared entrypoint changes must also pass packed-host loading. + +## V2 source findings + +V2 is inspected in a detached worktree at `/tmp/opencode/dcp-v2-source`. + +- `packages/ai/src/schema/messages.ts`: canonical model messages have optional + IDs, roles, and typed content (text, reasoning, media, call, result, effort, + provider compaction). Tool results can contain structured multimodal content. +- `packages/core/src/session/runner/to-llm-message.ts`: a persisted assistant + message becomes one assistant message plus tool-result messages. The latter + have no message ID; correlate them through tool call IDs. Hosted tools remain + in assistant content. Preserve native content and provider metadata rather than + round-tripping all V2 messages through V1 shapes. +- Same converter handles provider/model switching, native checkpoints, skills, + shell output, and working-directory changes before DCP sees the request. +- `packages/core/src/session/model-request.ts`: hooks precede provider lowering; + HTTP hooks wrap fallback too. WebSocket handshake hooks do not expose frames. +- `packages/schema/src/session-message.ts`: persisted history has explicit + compaction/idle/model/agent/location events. V1 `step-start` and summary flags + are not present. DCP's current “turn” counters count model steps, not V2 idle + turn markers; keep this distinction explicit. +- `packages/http-recorder/README.md`: upstream has an Effect-layer HTTP/WS + cassette recorder for embedded tests. Public plugins cannot replace the live + server's transport layer through this API. A local recording relay can observe + real CLI traffic without patching OpenCode internals. +- V2 has an `auth.json` migration (`20260805200742_import_legacy_credentials.ts`), + but fresh database bootstrap marks all migrations complete without running them. + Copying `auth.json` into a clean home does **not** import credentials. Confirmed + by the first live test and a read-only inspection of a copied test database. + +## Logging findings + +The old logger wraps global `fetch` and a V1 request option. It captures JSON +request bodies only, uses mutable global session attribution, never captures +responses or WebSocket frames, and its WeakSet deduplication cannot match bodies +parsed into a fresh object each time. V2 migration should use supported hooks +where available and test streaming behavior explicitly. + +## Planned milestones + +1. Complete the architecture and DCP behavior map; resolve semantic ambiguities. +2. Establish a pinned clean V2 container, response/request capture, and a parser + that reports assertions and compact summaries rather than dumping transcripts. +3. Implement the shared package entrypoint and first independently testable DCP + features; preserve V1 behavior through its own API. +4. Migrate compression, protections, commands, persistence, and UI with focused + integration scenarios, including concurrent sessions and WebSocket requests. +5. Test packed installation on V2 and V1 1.18.29, document supported features and + outstanding gaps precisely. + +## Validation so far + +- Existing DCP baseline: **103 tests passed**, zero failed (`npm test`), before + any DCP runtime changes. Output: `/tmp/opencode/dcp-baseline.log`. +- Container image `dcp-lab:2.0.4` built from `tests/lab/Dockerfile`. + Binaries actually executed and report **2.0.4** and **1.18.29**. +- Logger now has the shared V1/V2 entrypoint, native V2 context/HTTP hooks, + streaming response capture, and optional WebSocket recording relay. +- Logger tests: **3 passed** covering interleaved session attribution and exact + response bytes, cancellation propagation, and reusable text/binary WebSockets. +- `scripts/lab.mjs` packs the logger and installs it in an isolated container home. + `tests/lab/run.mjs` drives both host versions against a deterministic Responses + provider. `tests/lab/inspect.mjs` parses captures and prints compact summaries. +- Packed logger integration passed on V2 HTTP, V2 WebSocket, and V1 HTTP in + `/tmp/opencode/dcp-lab/2026-09-16T14-00-14-748Z`. Captured request bodies match + the fake provider's received bodies exactly, without duplicate captures. +- V2 primary/title HTTP responses contain `response.completed`, but OpenCode + cancels consumption before stream EOF. The harness asserts protocol completion + as well as recording the actual stream termination (cancelled vs EOF). +- Packed installation exposed two issues fixed in the logger: include compiled + helpers in npm files; add root `server.js` for V2 local-directory discovery. + `Host.resolve` probes root server/index files for directories, not package main. +- The first live attempt failed with missing authentication. The old OpenCode + token had expired; the Codex token is current. `--live` now copies only Codex's + access token/account into isolated runtime storage and supplies them through + environment substitutions with the Codex endpoint. This tests real model + transport, not OAuth login/refresh or legacy credential migration. +- V2's type package requires newer optional OpenTUI peers than V1. For development, + install with `npm install --legacy-peer-deps`; keep V1's runtime UI dependencies. + V2 imports are type-only at this stage, so they do not load its terminal runtime. + +## First DCP implementation (under test) + +- Shared root entrypoint now exposes V2 `setup` and legacy `server`. +- V2 uses per-session state and serializes operations within a session. Existing + compression, persistence, nudges, protections, and command algorithms are reused. +- Native messages are projected only for DCP bookkeeping. Edits are applied to + the original native structure, preserving reasoning, media, and provider metadata. + ID-less tool results are correlated with their assistant by tool-call ID. +- Compression runs deduplication/error pruning at its original cache-breaking + point. Native compaction checkpoints are excluded from selectable messages. +- V2 reports have a no-display extension point, per the user's decision. The + existing panel's views are shared with a native V2 CLI setup, keymap, dialogs, + and theme adapter. A typed RPC supplies server-owned context/stats/manual state; + the V2 UI does not read or write server persistence files directly. +- Live logger checks passed over HTTP and WebSockets with `openai/gpt-5.6-sol` at + `/tmp/opencode/dcp-lab/2026-09-16T14-05-29-833Z` (expected reply on both). +- The packed lab now exercises real compression tool execution, both compression + modes, actual outgoing context removal, tool-call pairing, and WS prefix reset. + All five scenarios passed at `/tmp/opencode/dcp-lab/2026-09-16T14-12-27-520Z`: + V2 range/message over HTTP/WS, plus packed V1 1.18.29 range compression. +- Unit suite after the first native adapter: **107 passed**, zero failed. +- At this milestone the terminal panel port was implemented but still awaiting + runtime/UI validation; see the later results below. + +### Code Mode and native subagents + +- V2 records nested Code Mode tool names/inputs in `metadata.toolCalls`, but + retains only a combined `execute` output. The user approved protecting that + entire output if any nested tool or file matches a protection rule. Compression, + sweep, deduplication, and error pruning now share this protection check. +- Native `subagent` calls use `metadata.sessionID` and a `` wrapper. + Shared expansion now recognizes these alongside V1's `task`/`sessionId`/ + `` forms. The V2 context hook invokes the shared expansion step. +- Panel integration exposed two boundary details: foreground V2 servers require + Basic authentication (the lab uses its own fixed test password), and RPC outputs + must omit optional fields rather than include properties valued `undefined`. + +### Expanded verification + +- Packed integration passed all five transport/mode scenarios again at + `/tmp/opencode/dcp-lab/2026-09-16T14-28-51-739Z`. The V2 HTTP range scenario also + passed RPC snapshot/manual controls, command-based decompress/recompress, + explicit allow/deny/unsupported-ask behavior, concurrent sessions, and state + restoration after a server restart. +- Actual DCP compression passed against `openai/gpt-5.6-sol` over both HTTP and + WebSockets at `/tmp/opencode/dcp-lab/2026-09-16T14-29-49-827Z`. Each session made + exactly one successful compression, replaced the disposable source text in + subsequent wire context, persisted one active block, and returned the expected + final text. The first fixture accidentally compressed away its own reply + instruction; preserving that instruction in the fixture summary fixed the test. +- Native checkpoint coverage now includes compressing an assistant after all + ordinary user messages were replaced by a checkpoint. Summary construction uses + native session fields without making the checkpoint selectable or modifying it. +- Terminal testing found that V1 requires its separate `tui.json` plugin entry; + adding only a server plugin does not register the panel. The lab now supplies it. +- V2's first terminal run failed while loading a second, older OpenTUI runtime + (`OPENTUI_FORCE_WCWIDTH` registered with different settings). The shared views + now use intrinsic `` elements instead of importing core solely for its bold + enum. V1's complete panel/Context/Stats/manual-toggle/close test now passes. +- V2's next terminal failure (`Keymap.Provider is missing`) exposed a CLI lifecycle + requirement: `keymap.layer` is component-owned and cannot run in async plugin + setup. The adapter now follows V2's built-in stats plugin: register a nonvisual + app slot and create a global keymap layer from its render function. +- Packed checks passed again at `/tmp/opencode/dcp-lab/2026-09-16T14-45-16-512Z`, + including a completed native local compaction. Parsed outgoing requests prove + that its shared message prefix and system instructions match the preceding + primary request. Block activation is synchronized against full durable history, + because a compaction request may select a prefix that excludes block origins. +- The full unit run had 112 passes and one incomplete nudge fixture; after supplying + its missing prompt strings, the targeted nudge test passed. It verifies existing + anchor replay, unchanged message prefix, and no new anchors during compaction. +- V2's complete terminal test now passes too: panel, Context, Stats, manual toggle + verified in persisted state, and close. Its keymap uses global mode, and the PTY + test accounts for V2's two-step autocomplete/submission of argument-taking slash + commands. Final UI fixes were deployed into the isolated installed package for + these narrow tests; server checks used packed artifacts. V2 frames/transcript are + under `/tmp/opencode/dcp-lab/2026-09-16T14-45-16-512Z/runtime/v2-http-range/`; + the successful V1 run used the `2026-09-16T14-28-51-739Z` lab directory. +- Final `npm run check:package` passed (bundle, TypeScript declarations, import + compatibility, packed-file checks); `git diff --check` also passed. +- Repacked the final implementation to + `/tmp/opencode/dcp-ui-final/tarquinen-opencode-dcp-3.1.15.tgz`, installed it into + the `2026-09-16T14-45-16-512Z` lab, and reran both terminal tests. **V1 and V2 + both passed** panel, Context, Stats, persisted manual-mode toggle, and close + against that installed tarball. Final outputs are + `/tmp/opencode/dcp-ui-v1-final.log` and `/tmp/opencode/dcp-ui-v2-final.log`; + frames/transcripts are in the respective scenario directories in that lab. + +## Remaining coverage and API gaps + +- V2 has no public custom-tool permission-request method. `allow` and `deny` are + exercised; `ask` produces an explicit error without compression. +- V1 ignored-message reports have no equivalent in the V2 model history API. The + `report` function in `lib/v2/index.ts` is the agreed extension point; panel data + is available through typed RPC. Self-updating is currently V1-only. +- V2 has no documented counterpart to V1's `experimental.text.complete` cleanup. + Outgoing-context tag cleanup is implemented; generated visible text is not + rewritten by the V2 adapter. +- Real provider checks cover OpenAI Responses via Codex over HTTP/WS. Other + providers and provider-native compaction endpoints/triggers have not been run + end-to-end. Local native compaction and opaque checkpoint preservation are + covered by integration and adapter tests respectively. +- Code Mode combined-output protection and native subagent formats are covered + by focused tests; a complete real-model nested-subagent workflow is not covered. +- Live tests use an existing access token; they do not exercise OAuth refresh or + V1 credential/database migration. Fresh V2 database bootstrap skips the legacy + credential-import migration, as described above. +- Development installation needs `--legacy-peer-deps` because V1 and V2's type + packages advertise different optional OpenTUI peers. Packed runtime installation + and both hosts' panel rendering were exercised with the retained V1 dependencies. + +## Reproducing the lab + +### Reusable manual launcher + +- `scripts/sandbox.mjs` (`npm run sandbox` or `dcp-sandbox`) runs an isolated + Docker environment from `scripts/sandbox/`. Sessions, scratch files, DCP config, + and CLI preferences persist. `--fresh` selects an empty profile without deleting + others. The current Codex access token is read at launch without token refresh. +- Each launch builds and packs the current DCP and sibling logger checkout; npm + pack output determines artifact filenames rather than hardcoding plugin versions. + Only the sandbox, packed inputs, and runner are mounted. The container gets its + own home/XDG paths in an initially empty project. V2 uses a standalone server. +- Default V2 settings are OpenCode 2.0.4, `openai/gpt-5.6-sol`, and WebSockets. + `--v1` selects OpenCode 1.18.29 over HTTP with independent state under `v1/` + and panel registration in `tui.json`. Exact versions below V1 1.18.29 and V1 + WebSocket selection are rejected. `--update` selects the latest release of the + chosen major; version/model/transport choices are remembered per major. +- Minimal token/account inputs are mode 0600 and removed after the container exits; + normal credentials are read only. No token values appear in config or commands. +- `~/.local/bin/dcp-sandbox` is a symlink to the executable launcher. + It can run from any directory; no shell configuration edits are needed. +- The launcher creates the scratch directory before mounting, keeping it writable + by the user's UID. Cached image builds honor Dockerfile changes. + +### Automatic readable logs + +- Each launch has `raw/` and `readable/` directories. The launcher manages the + WebSocket relay and the sibling logger's `readable.mjs` watcher throughout the + session. `--logs` shows paths and capture counts; it does not generate files. +- The watcher publishes requests as they are captured and responses on protocol + completion, including over reusable WebSockets and HTTP streams that remain + open. Context snapshots appear as they are captured. Cancellation, socket loss, + and shutdown preserve available partial output with incomplete metadata. +- Appended stream bytes are read once using per-file offsets and UTF-8 decoding; + only active response assembly state is retained. JSON metadata is published + atomically, so readers see complete documents. Raw captures remain intact. +- A session has numbered HTTP/WS request folders containing `request.json`, + `response.json`, and `meta.json`. Requests retain the actual wire body, including + WebSocket continuation deltas. Responses contain assistant text, readable + thinking, parsed tool input, aggregate usage, and errors. Echoed prompts, tool + definitions, encrypted state, and attribution details remain in raw captures. + IDs, transport, continuation, and completion information belong in metadata. +- Codex SSE is recognized with or without Content-Type. Completed streamed items + supply response content when the final event has an empty output array. + +### Sandbox verification + +- Real Codex-backed runs passed on V1 1.18.29 (HTTP) and V2 2.0.4 (WebSocket): + each compressed once, replaced the raw fixture with its summary on the wire, + returned the expected final reply, and removed its temporary auth input. + Readable output assembled all three requests per run (V2's title used HTTP), + with zero incomplete responses. Evidence: `/tmp/opencode/dcp-dual-check/`, + `/tmp/opencode/dcp-sandbox-v1.log`, `/tmp/opencode/dcp-sandbox-v2-new.log`. +- Readable-log tests cover full/incremental WS requests, mixed HTTP/WS sessions, + split UTF-8, preserved raw bodies, interrupted responses, incomplete trailing + frames, tool-call assembly, and provider errors. Live-publication verification + includes requests before any response and replies before transport shutdown. + All eight logger tests pass, including overlapping HTTP requests completing in + reverse order and partial replies published when a socket closes. The logger + build and whitespace checks in both repositories pass. +- Live sandbox checks confirm readable requests appear while pending and completed + replies are available before closing the terminal: V1 HTTP at + `/tmp/opencode/dcp-dual-check/v1/profiles/default/logs/2026-09-16T16-10-53-529Z` + and V2 WebSocket at + `/tmp/opencode/dcp-dual-check/profiles/default/logs/2026-09-16T16-12-14-679Z`. + Both terminals exited cleanly. Results and terminal frames are recorded in + `/tmp/opencode/live-logs-v1.log`, `/tmp/opencode/live-logs-v2.log`, and adjacent + `.frames.json` files. `--logs` was verified to leave file sizes and modification + times unchanged. +- PTY validation of the actual `dcp-sandbox --v1 -- --continue` shortcut resumed + the V1 test session, opened/closed `/dcp`, and exited cleanly with deliberately + invalid host OpenCode config/password variables. Output: + `/tmp/opencode/dcp-dual-ui-v1.log`. V1 and V2 retain separate profile databases. +- CLI checks confirmed default V2 selection, separate per-major paths, explicit + version inference, V1's minimum version (including prerelease rejection at the + boundary), incompatible major/transport rejection, and V1 update lookup + resolving to 1.18.31. Whitespace checks passed in both repositories. +- PTY environment-isolation checks used deliberately invalid host OpenCode config + and password variables, resumed sessions, opened/closed the panel, and exited + cleanly: `/tmp/opencode/dcp-sandbox-ui.log` and accompanying frames. Fresh-profile + checks covered writable scratch space, retained prior sessions, remembered + settings, version selection, and auth-file cleanup. + +### Automated migration checks + +```sh +npm install --legacy-peer-deps +docker build -t dcp-lab:2.0.4 tests/lab +node scripts/lab.mjs +# After a successful build, test the existing artifact without rebuilding: +node scripts/lab.mjs --built +# Uses the current Codex access token/account in an isolated container: +node scripts/lab.mjs --live +``` + +The driver prints its output directory under `/tmp/opencode/dcp-lab/`. It packs +this plugin and the sibling `opencode-request-logger` repository, installs them in +the container, and keeps configs, databases, raw captures, and compact result JSON +there. `--live` uses the existing build and tests actual compression on the wire; +it does not test OAuth refresh. Inspect captures with +`node tests/lab/inspect.mjs `. + +Terminal tests reopen a completed lab session and exercise the panel, Context, +Stats, manual-mode toggle, and close action: + +```sh +uv run --with pexpect --with pyte tests/lab/ui.py v2 +uv run --with pexpect --with pyte tests/lab/ui.py v1 +``` + +Terminal transcripts/screens are saved beside each scenario's result JSON. These +tests use only the isolated container homes, not the running user's service. From cd8a3244d364091e7c07e5aa286ac329485db43d Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:34:11 -0400 Subject: [PATCH 07/15] chore: keep the migration journal local Ignore local development documentation and remove the public README link to the untracked journal. --- .gitignore | 3 +- README.md | 3 - docs/migration-v2.md | 423 ------------------------------------------- 3 files changed, 1 insertion(+), 428 deletions(-) delete mode 100644 docs/migration-v2.md diff --git a/.gitignore b/.gitignore index 882f0d87..2f165e92 100644 --- a/.gitignore +++ b/.gitignore @@ -42,8 +42,7 @@ notes/ test-update.ts # Documentation (local development only) -docs/* -!docs/migration-v2.md +docs/ SCHEMA_NOTES.md repomix-output.xml diff --git a/README.md b/README.md index 3a8e4981..1c209f59 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,6 @@ array. To load the panel, also register it in the separate `tui.json`: { "plugin": ["/absolute/path/to/opencode-dynamic-context-pruning"] } ``` -See [the migration journal](docs/migration-v2.md) for architecture notes, test -results, remaining gaps, and the isolated container test workflow. - ### Manual V1/V2 sandbox From this checkout, run: diff --git a/docs/migration-v2.md b/docs/migration-v2.md deleted file mode 100644 index d6056451..00000000 --- a/docs/migration-v2.md +++ /dev/null @@ -1,423 +0,0 @@ -# OpenCode V2 migration journal - -## Current status - -The first migration pass is implemented for **V2 2.0.4** and **V1 1.18.29+**. -Packed server tests pass for both hosts; V2 range/message compression works over -HTTP and WebSockets. Real Codex-backed compression and both terminal panels have -also passed. Changes are local and unpublished. - -Intentional limitations: V2 `ask` is blocked, invisible chat reports use a no-display -extension point, and the self-updater remains V1-only. See verification and remaining -coverage below before treating this as a release-ready compatibility guarantee. - -## Goal and decisions - -Port DCP to OpenCode V2 while retaining V1 support. Investigate architecture and -existing behavior first, establish an isolated test environment with request and -response capture, then migrate and test features incrementally. - -- 2026-09-16: user selected **OpenCode 1.18.29+** as the V1 compatibility floor. - V2 documents a shared default export with `id`, `setup()` (V2), and `server()` - (V1). The two implementations still use different APIs. -- User selected **2.0.4** as the first V2 target. -- User requested migrating the existing DCP terminal panel in this pass as well. -- Preserve duplicate/error pruning timing: run these strategies when `compress` - runs. The user confirmed this deliberately limits prompt-cache invalidation. -- Compaction must apply the same DCP body edits and preserve the existing cache - prefix. Replay existing nudges, but do not introduce new ones (user clarification). -- User approved omitting V2 displays for V1 `ignored` reports for now. Leave a - small reporting extension point, without building a replacement UI. -- Live smoke tests should use the user's configured `openai/gpt-5.6-sol` model. -- V2 `compress: ask` will be blocked with a clear error for now (user decision). - Custom tools must request permission themselves, but the public V2 plugin - context exposes only permission list/get/reply and hooks. `options.permission` - controls denied-tool visibility, not an ask dialog. -- Keep this journal updated with evidence, decisions, test results, and open - questions. Do not infer feature parity from successful plugin loading alone. - -## Verified environment - -| Item | Evidence | -| --------------------- | -------------------------------------------------------------------------------------------------------- | -| OpenCode V1 source | `/home/dan/src/opencode`, `dev` at `501ff62cbf`; `packages/opencode/package.json` reports `1.18.31` | -| OpenCode V2 source | same repository, `v2` at `cda2bc5100`; `packages/cli/package.json` reports `2.0.4` | -| Source checkout | currently on `dev`; inspect V2 separately | -| Installed V1 | `opencode --version`: `1.18.30` | -| Installed V2 | `opencode2 --version`: `v0.0.0-beta-19425`; this differs from the V2 branch/release | -| Launch configuration | `.bashrc`: `oc1` uses `~/src/opencode-config`; `oc2` uses `~/src/opencode-config/v2` | -| Provider routing | both normal configurations use local provider proxies; clean tests need their own provider configuration | -| Container runtime | Docker available, server `29.7.2` | -| DCP starting state | `dev`, clean working tree, package version `3.1.15` | -| Logger starting state | separate repository at `../opencode-request-logger`, `main`; pre-existing untracked `bun.lock` | - -Credential locations have been identified without copying credentials into this -repository. Test credentials and traffic logs belong in isolated runtime storage. - -## Sources - -- -- -- -- Local OpenCode branch snapshots listed above. - -## Initial architecture findings - -- V1 plugins return a hook/tool object from a function. V2 plugins register domain - hooks and synchronous registry transforms from `setup(ctx)`. -- V2's context is also the server client; V1's separate `ctx.client` and old SDK - request/response envelopes do not carry over. -- V1 system/message transforms converge into V2's `session.hook("context")`. - V2 separates primary requests, compaction, title generation, and transient - generation into distinct hooks. -- Context edits affect outgoing requests, not persisted conversation history. -- V2 commands have registered executors; tools use JSON Schema and structured - results. There is no mutable global config hook. -- V2 has no direct equivalent to `experimental.text.complete`. -- V2 plugin instances are location-scoped and may observe multiple sessions; - DCP's current single mutable session state needs careful review for concurrency. -- Public HTTP request/response hooks support native provider capture. WebSocket - traffic bypasses them; the documented experimental WebSocket hook exposes only - handshake URL/headers. Frame capture requires further source investigation. - -## Existing DCP map (investigation in progress) - -- `index.ts`: initialization, V1 hook wiring, compression tool selection, config - changes, command registration, permission snapshots, update checks. -- `lib/hooks.ts`: system instructions; message cleanup, stable references, - compression sync, tool cache, pruning, subagent results, nudges, message tags; - commands; output tag stripping; compression timing events. -- `lib/state/`: current session, pruning records, compressed blocks, message IDs, - token counters, persisted manual mode, compaction resets. -- `lib/messages/prune.ts`: summary insertion/removal and output/input pruning; - special handling for question/edit/write results. -- `lib/compress/`: validate all ranges before applying state; expand nested block - placeholders and append omitted required summaries; preserve protected user - text, `` sections, tool outputs, and file patterns. Message mode skips - invalid/protected/already-compressed selections and groups diagnostics. -- `lib/compress/pipeline.ts`: manual-mode gate, permission request, history read, - ID assignment, duplicate/error strategies, persistence, and notifications. -- `lib/strategies/`: deduplicate by tool name and normalized/sorted parameters, - keeping the newest; remove old failed-tool inputs while retaining errors. -- `lib/messages/sync.ts`: replay block activation from surviving compression - origins, consumed blocks, and user decompression flags (important for undo). -- `lib/messages/inject/`: stable nudge anchors above configured model/token - thresholds; IDs on text/tool results; user protection; manual trigger injection. -- `lib/token-utils.ts`: provider-reported usage plus approximate tokenizer counts; - V1's token fields and compaction markers need explicit V2 translation. -- `lib/commands/`: context/stats reports, sweep with tool/file protections, - persisted manual toggle, compression prompt, decompress/recompress block groups. -- `lib/ui/notification.ts`: V1 chat output uses `noReply` plus `ignored` text so - reports never enter model context. V2 synthetic messages are model-visible and - are not an equivalent replacement; UI delivery needs an explicit design. -- `tui.tsx` and `lib/tui/`: a separate V1 CLI plugin owns `/dcp` panels and reads - filesystem state. V2 has a different CLI context, theme, keymap, and data API. -- `lib/config.ts` and `lib/prompts/store.ts`: global, config-directory, and nearest - `.opencode` overrides; custom prompt files; V1 toast warnings. Preserve existing - configuration semantics while adapting host dependencies. -- `scripts/verify-package.mjs`: verifies both server and CLI entrypoints and npm - tarball contents. Shared entrypoint changes must also pass packed-host loading. - -## V2 source findings - -V2 is inspected in a detached worktree at `/tmp/opencode/dcp-v2-source`. - -- `packages/ai/src/schema/messages.ts`: canonical model messages have optional - IDs, roles, and typed content (text, reasoning, media, call, result, effort, - provider compaction). Tool results can contain structured multimodal content. -- `packages/core/src/session/runner/to-llm-message.ts`: a persisted assistant - message becomes one assistant message plus tool-result messages. The latter - have no message ID; correlate them through tool call IDs. Hosted tools remain - in assistant content. Preserve native content and provider metadata rather than - round-tripping all V2 messages through V1 shapes. -- Same converter handles provider/model switching, native checkpoints, skills, - shell output, and working-directory changes before DCP sees the request. -- `packages/core/src/session/model-request.ts`: hooks precede provider lowering; - HTTP hooks wrap fallback too. WebSocket handshake hooks do not expose frames. -- `packages/schema/src/session-message.ts`: persisted history has explicit - compaction/idle/model/agent/location events. V1 `step-start` and summary flags - are not present. DCP's current “turn” counters count model steps, not V2 idle - turn markers; keep this distinction explicit. -- `packages/http-recorder/README.md`: upstream has an Effect-layer HTTP/WS - cassette recorder for embedded tests. Public plugins cannot replace the live - server's transport layer through this API. A local recording relay can observe - real CLI traffic without patching OpenCode internals. -- V2 has an `auth.json` migration (`20260805200742_import_legacy_credentials.ts`), - but fresh database bootstrap marks all migrations complete without running them. - Copying `auth.json` into a clean home does **not** import credentials. Confirmed - by the first live test and a read-only inspection of a copied test database. - -## Logging findings - -The old logger wraps global `fetch` and a V1 request option. It captures JSON -request bodies only, uses mutable global session attribution, never captures -responses or WebSocket frames, and its WeakSet deduplication cannot match bodies -parsed into a fresh object each time. V2 migration should use supported hooks -where available and test streaming behavior explicitly. - -## Planned milestones - -1. Complete the architecture and DCP behavior map; resolve semantic ambiguities. -2. Establish a pinned clean V2 container, response/request capture, and a parser - that reports assertions and compact summaries rather than dumping transcripts. -3. Implement the shared package entrypoint and first independently testable DCP - features; preserve V1 behavior through its own API. -4. Migrate compression, protections, commands, persistence, and UI with focused - integration scenarios, including concurrent sessions and WebSocket requests. -5. Test packed installation on V2 and V1 1.18.29, document supported features and - outstanding gaps precisely. - -## Validation so far - -- Existing DCP baseline: **103 tests passed**, zero failed (`npm test`), before - any DCP runtime changes. Output: `/tmp/opencode/dcp-baseline.log`. -- Container image `dcp-lab:2.0.4` built from `tests/lab/Dockerfile`. - Binaries actually executed and report **2.0.4** and **1.18.29**. -- Logger now has the shared V1/V2 entrypoint, native V2 context/HTTP hooks, - streaming response capture, and optional WebSocket recording relay. -- Logger tests: **3 passed** covering interleaved session attribution and exact - response bytes, cancellation propagation, and reusable text/binary WebSockets. -- `scripts/lab.mjs` packs the logger and installs it in an isolated container home. - `tests/lab/run.mjs` drives both host versions against a deterministic Responses - provider. `tests/lab/inspect.mjs` parses captures and prints compact summaries. -- Packed logger integration passed on V2 HTTP, V2 WebSocket, and V1 HTTP in - `/tmp/opencode/dcp-lab/2026-09-16T14-00-14-748Z`. Captured request bodies match - the fake provider's received bodies exactly, without duplicate captures. -- V2 primary/title HTTP responses contain `response.completed`, but OpenCode - cancels consumption before stream EOF. The harness asserts protocol completion - as well as recording the actual stream termination (cancelled vs EOF). -- Packed installation exposed two issues fixed in the logger: include compiled - helpers in npm files; add root `server.js` for V2 local-directory discovery. - `Host.resolve` probes root server/index files for directories, not package main. -- The first live attempt failed with missing authentication. The old OpenCode - token had expired; the Codex token is current. `--live` now copies only Codex's - access token/account into isolated runtime storage and supplies them through - environment substitutions with the Codex endpoint. This tests real model - transport, not OAuth login/refresh or legacy credential migration. -- V2's type package requires newer optional OpenTUI peers than V1. For development, - install with `npm install --legacy-peer-deps`; keep V1's runtime UI dependencies. - V2 imports are type-only at this stage, so they do not load its terminal runtime. - -## First DCP implementation (under test) - -- Shared root entrypoint now exposes V2 `setup` and legacy `server`. -- V2 uses per-session state and serializes operations within a session. Existing - compression, persistence, nudges, protections, and command algorithms are reused. -- Native messages are projected only for DCP bookkeeping. Edits are applied to - the original native structure, preserving reasoning, media, and provider metadata. - ID-less tool results are correlated with their assistant by tool-call ID. -- Compression runs deduplication/error pruning at its original cache-breaking - point. Native compaction checkpoints are excluded from selectable messages. -- V2 reports have a no-display extension point, per the user's decision. The - existing panel's views are shared with a native V2 CLI setup, keymap, dialogs, - and theme adapter. A typed RPC supplies server-owned context/stats/manual state; - the V2 UI does not read or write server persistence files directly. -- Live logger checks passed over HTTP and WebSockets with `openai/gpt-5.6-sol` at - `/tmp/opencode/dcp-lab/2026-09-16T14-05-29-833Z` (expected reply on both). -- The packed lab now exercises real compression tool execution, both compression - modes, actual outgoing context removal, tool-call pairing, and WS prefix reset. - All five scenarios passed at `/tmp/opencode/dcp-lab/2026-09-16T14-12-27-520Z`: - V2 range/message over HTTP/WS, plus packed V1 1.18.29 range compression. -- Unit suite after the first native adapter: **107 passed**, zero failed. -- At this milestone the terminal panel port was implemented but still awaiting - runtime/UI validation; see the later results below. - -### Code Mode and native subagents - -- V2 records nested Code Mode tool names/inputs in `metadata.toolCalls`, but - retains only a combined `execute` output. The user approved protecting that - entire output if any nested tool or file matches a protection rule. Compression, - sweep, deduplication, and error pruning now share this protection check. -- Native `subagent` calls use `metadata.sessionID` and a `` wrapper. - Shared expansion now recognizes these alongside V1's `task`/`sessionId`/ - `` forms. The V2 context hook invokes the shared expansion step. -- Panel integration exposed two boundary details: foreground V2 servers require - Basic authentication (the lab uses its own fixed test password), and RPC outputs - must omit optional fields rather than include properties valued `undefined`. - -### Expanded verification - -- Packed integration passed all five transport/mode scenarios again at - `/tmp/opencode/dcp-lab/2026-09-16T14-28-51-739Z`. The V2 HTTP range scenario also - passed RPC snapshot/manual controls, command-based decompress/recompress, - explicit allow/deny/unsupported-ask behavior, concurrent sessions, and state - restoration after a server restart. -- Actual DCP compression passed against `openai/gpt-5.6-sol` over both HTTP and - WebSockets at `/tmp/opencode/dcp-lab/2026-09-16T14-29-49-827Z`. Each session made - exactly one successful compression, replaced the disposable source text in - subsequent wire context, persisted one active block, and returned the expected - final text. The first fixture accidentally compressed away its own reply - instruction; preserving that instruction in the fixture summary fixed the test. -- Native checkpoint coverage now includes compressing an assistant after all - ordinary user messages were replaced by a checkpoint. Summary construction uses - native session fields without making the checkpoint selectable or modifying it. -- Terminal testing found that V1 requires its separate `tui.json` plugin entry; - adding only a server plugin does not register the panel. The lab now supplies it. -- V2's first terminal run failed while loading a second, older OpenTUI runtime - (`OPENTUI_FORCE_WCWIDTH` registered with different settings). The shared views - now use intrinsic `` elements instead of importing core solely for its bold - enum. V1's complete panel/Context/Stats/manual-toggle/close test now passes. -- V2's next terminal failure (`Keymap.Provider is missing`) exposed a CLI lifecycle - requirement: `keymap.layer` is component-owned and cannot run in async plugin - setup. The adapter now follows V2's built-in stats plugin: register a nonvisual - app slot and create a global keymap layer from its render function. -- Packed checks passed again at `/tmp/opencode/dcp-lab/2026-09-16T14-45-16-512Z`, - including a completed native local compaction. Parsed outgoing requests prove - that its shared message prefix and system instructions match the preceding - primary request. Block activation is synchronized against full durable history, - because a compaction request may select a prefix that excludes block origins. -- The full unit run had 112 passes and one incomplete nudge fixture; after supplying - its missing prompt strings, the targeted nudge test passed. It verifies existing - anchor replay, unchanged message prefix, and no new anchors during compaction. -- V2's complete terminal test now passes too: panel, Context, Stats, manual toggle - verified in persisted state, and close. Its keymap uses global mode, and the PTY - test accounts for V2's two-step autocomplete/submission of argument-taking slash - commands. Final UI fixes were deployed into the isolated installed package for - these narrow tests; server checks used packed artifacts. V2 frames/transcript are - under `/tmp/opencode/dcp-lab/2026-09-16T14-45-16-512Z/runtime/v2-http-range/`; - the successful V1 run used the `2026-09-16T14-28-51-739Z` lab directory. -- Final `npm run check:package` passed (bundle, TypeScript declarations, import - compatibility, packed-file checks); `git diff --check` also passed. -- Repacked the final implementation to - `/tmp/opencode/dcp-ui-final/tarquinen-opencode-dcp-3.1.15.tgz`, installed it into - the `2026-09-16T14-45-16-512Z` lab, and reran both terminal tests. **V1 and V2 - both passed** panel, Context, Stats, persisted manual-mode toggle, and close - against that installed tarball. Final outputs are - `/tmp/opencode/dcp-ui-v1-final.log` and `/tmp/opencode/dcp-ui-v2-final.log`; - frames/transcripts are in the respective scenario directories in that lab. - -## Remaining coverage and API gaps - -- V2 has no public custom-tool permission-request method. `allow` and `deny` are - exercised; `ask` produces an explicit error without compression. -- V1 ignored-message reports have no equivalent in the V2 model history API. The - `report` function in `lib/v2/index.ts` is the agreed extension point; panel data - is available through typed RPC. Self-updating is currently V1-only. -- V2 has no documented counterpart to V1's `experimental.text.complete` cleanup. - Outgoing-context tag cleanup is implemented; generated visible text is not - rewritten by the V2 adapter. -- Real provider checks cover OpenAI Responses via Codex over HTTP/WS. Other - providers and provider-native compaction endpoints/triggers have not been run - end-to-end. Local native compaction and opaque checkpoint preservation are - covered by integration and adapter tests respectively. -- Code Mode combined-output protection and native subagent formats are covered - by focused tests; a complete real-model nested-subagent workflow is not covered. -- Live tests use an existing access token; they do not exercise OAuth refresh or - V1 credential/database migration. Fresh V2 database bootstrap skips the legacy - credential-import migration, as described above. -- Development installation needs `--legacy-peer-deps` because V1 and V2's type - packages advertise different optional OpenTUI peers. Packed runtime installation - and both hosts' panel rendering were exercised with the retained V1 dependencies. - -## Reproducing the lab - -### Reusable manual launcher - -- `scripts/sandbox.mjs` (`npm run sandbox` or `dcp-sandbox`) runs an isolated - Docker environment from `scripts/sandbox/`. Sessions, scratch files, DCP config, - and CLI preferences persist. `--fresh` selects an empty profile without deleting - others. The current Codex access token is read at launch without token refresh. -- Each launch builds and packs the current DCP and sibling logger checkout; npm - pack output determines artifact filenames rather than hardcoding plugin versions. - Only the sandbox, packed inputs, and runner are mounted. The container gets its - own home/XDG paths in an initially empty project. V2 uses a standalone server. -- Default V2 settings are OpenCode 2.0.4, `openai/gpt-5.6-sol`, and WebSockets. - `--v1` selects OpenCode 1.18.29 over HTTP with independent state under `v1/` - and panel registration in `tui.json`. Exact versions below V1 1.18.29 and V1 - WebSocket selection are rejected. `--update` selects the latest release of the - chosen major; version/model/transport choices are remembered per major. -- Minimal token/account inputs are mode 0600 and removed after the container exits; - normal credentials are read only. No token values appear in config or commands. -- `~/.local/bin/dcp-sandbox` is a symlink to the executable launcher. - It can run from any directory; no shell configuration edits are needed. -- The launcher creates the scratch directory before mounting, keeping it writable - by the user's UID. Cached image builds honor Dockerfile changes. - -### Automatic readable logs - -- Each launch has `raw/` and `readable/` directories. The launcher manages the - WebSocket relay and the sibling logger's `readable.mjs` watcher throughout the - session. `--logs` shows paths and capture counts; it does not generate files. -- The watcher publishes requests as they are captured and responses on protocol - completion, including over reusable WebSockets and HTTP streams that remain - open. Context snapshots appear as they are captured. Cancellation, socket loss, - and shutdown preserve available partial output with incomplete metadata. -- Appended stream bytes are read once using per-file offsets and UTF-8 decoding; - only active response assembly state is retained. JSON metadata is published - atomically, so readers see complete documents. Raw captures remain intact. -- A session has numbered HTTP/WS request folders containing `request.json`, - `response.json`, and `meta.json`. Requests retain the actual wire body, including - WebSocket continuation deltas. Responses contain assistant text, readable - thinking, parsed tool input, aggregate usage, and errors. Echoed prompts, tool - definitions, encrypted state, and attribution details remain in raw captures. - IDs, transport, continuation, and completion information belong in metadata. -- Codex SSE is recognized with or without Content-Type. Completed streamed items - supply response content when the final event has an empty output array. - -### Sandbox verification - -- Real Codex-backed runs passed on V1 1.18.29 (HTTP) and V2 2.0.4 (WebSocket): - each compressed once, replaced the raw fixture with its summary on the wire, - returned the expected final reply, and removed its temporary auth input. - Readable output assembled all three requests per run (V2's title used HTTP), - with zero incomplete responses. Evidence: `/tmp/opencode/dcp-dual-check/`, - `/tmp/opencode/dcp-sandbox-v1.log`, `/tmp/opencode/dcp-sandbox-v2-new.log`. -- Readable-log tests cover full/incremental WS requests, mixed HTTP/WS sessions, - split UTF-8, preserved raw bodies, interrupted responses, incomplete trailing - frames, tool-call assembly, and provider errors. Live-publication verification - includes requests before any response and replies before transport shutdown. - All eight logger tests pass, including overlapping HTTP requests completing in - reverse order and partial replies published when a socket closes. The logger - build and whitespace checks in both repositories pass. -- Live sandbox checks confirm readable requests appear while pending and completed - replies are available before closing the terminal: V1 HTTP at - `/tmp/opencode/dcp-dual-check/v1/profiles/default/logs/2026-09-16T16-10-53-529Z` - and V2 WebSocket at - `/tmp/opencode/dcp-dual-check/profiles/default/logs/2026-09-16T16-12-14-679Z`. - Both terminals exited cleanly. Results and terminal frames are recorded in - `/tmp/opencode/live-logs-v1.log`, `/tmp/opencode/live-logs-v2.log`, and adjacent - `.frames.json` files. `--logs` was verified to leave file sizes and modification - times unchanged. -- PTY validation of the actual `dcp-sandbox --v1 -- --continue` shortcut resumed - the V1 test session, opened/closed `/dcp`, and exited cleanly with deliberately - invalid host OpenCode config/password variables. Output: - `/tmp/opencode/dcp-dual-ui-v1.log`. V1 and V2 retain separate profile databases. -- CLI checks confirmed default V2 selection, separate per-major paths, explicit - version inference, V1's minimum version (including prerelease rejection at the - boundary), incompatible major/transport rejection, and V1 update lookup - resolving to 1.18.31. Whitespace checks passed in both repositories. -- PTY environment-isolation checks used deliberately invalid host OpenCode config - and password variables, resumed sessions, opened/closed the panel, and exited - cleanly: `/tmp/opencode/dcp-sandbox-ui.log` and accompanying frames. Fresh-profile - checks covered writable scratch space, retained prior sessions, remembered - settings, version selection, and auth-file cleanup. - -### Automated migration checks - -```sh -npm install --legacy-peer-deps -docker build -t dcp-lab:2.0.4 tests/lab -node scripts/lab.mjs -# After a successful build, test the existing artifact without rebuilding: -node scripts/lab.mjs --built -# Uses the current Codex access token/account in an isolated container: -node scripts/lab.mjs --live -``` - -The driver prints its output directory under `/tmp/opencode/dcp-lab/`. It packs -this plugin and the sibling `opencode-request-logger` repository, installs them in -the container, and keeps configs, databases, raw captures, and compact result JSON -there. `--live` uses the existing build and tests actual compression on the wire; -it does not test OAuth refresh. Inspect captures with -`node tests/lab/inspect.mjs `. - -Terminal tests reopen a completed lab session and exercise the panel, Context, -Stats, manual-mode toggle, and close action: - -```sh -uv run --with pexpect --with pyte tests/lab/ui.py v2 -uv run --with pexpect --with pyte tests/lab/ui.py v1 -``` - -Terminal transcripts/screens are saved beside each scenario's result JSON. These -tests use only the isolated container homes, not the running user's service. From dcd904dc054a278805bf0b6bcb1a65573dfc4059 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:52:53 -0400 Subject: [PATCH 08/15] docs: separate user and contributor guidance Move local setup, sandbox, and logging instructions into CONTRIBUTING. Keep the README focused on users and avoid release-specific statements and duplicated version pins. --- CONTRIBUTING.md | 166 +++++++++++++++++++++++++++++++++++++++++++++++- README.md | 133 +++++--------------------------------- 2 files changed, 181 insertions(+), 118 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edf734bd..b1541d39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,4 +23,168 @@ This arrangement ensures the project remains Open Source while providing a path 4. Ensure all tests pass and the code is formatted. 5. Submit a Pull Request. -We look forward to your contributions! +## Development Setup + +Use Node.js and npm. From your checkout: + +```sh +npm ci --legacy-peer-deps +npm run build +``` + +The install flag allows development against the different OpenTUI peer versions +used by OpenCode V1 and V2. + +Run the checks relevant to your changes before submitting a pull request: + +```sh +npm test # Unit tests +npm run typecheck # TypeScript validation +npm run check:package # Build and validate the npm package +npm run format:check # Formatting +``` + +## Compatibility + +DCP provides server and terminal integrations for OpenCode V1 and V2, using shared +package entrypoints and `dcp.jsonc` settings. Exercise both hosts when changing +shared behavior. + +Use [package.json](package.json) for dependency requirements and +[the lab Dockerfile](tests/lab/Dockerfile) for pinned integration-test versions. +Host-specific behavior is implemented in [index.ts](index.ts), +[tui.tsx](tui.tsx), and [lib/v2/](lib/v2/). + +## Local Installation + +After building, add this checkout's absolute path to your OpenCode configuration. + +For **V2**, use `opencode.json`: + +```jsonc +{ + "plugins": [{ "package": "/absolute/path/to/opencode-dynamic-context-pruning" }], + "permissions": [{ "action": "compress", "resource": "*", "effect": "allow" }], +} +``` + +For **V1**, add the following to both `opencode.json` (server plugin) and the +separate `tui.json` (panel): + +```jsonc +{ "plugin": ["/absolute/path/to/opencode-dynamic-context-pruning"] } +``` + +## Manual Sandbox + +The sandbox requires Docker, Node/npm, a Codex login, and an +`opencode-request-logger` checkout alongside DCP: + +```text +parent/ + opencode-dynamic-context-pruning/ + opencode-request-logger/ +``` + +From the DCP checkout: + +```sh +npm run sandbox # OpenCode V2 +npm run sandbox -- --v1 # OpenCode V1 +``` + +Each launch rebuilds both plugins and prepares a clean Docker image. Run +`npm run sandbox -- --help` for available options and defaults. Authentication comes +from `~/.codex/auth.json`, or `$CODEX_HOME/auth.json`; `DCP_CODEX_AUTH` overrides the +file path. If the token expires, refresh your Codex login and relaunch. + +The sandbox has its own sessions, scratch workspace, and configuration under +`~/.local/state/dcp-sandbox/`. V1 uses the `v1/` subdirectory, with a separate +database. Your host project and normal OpenCode configuration are not mounted. +Try `/dcp` for the panel or `/dcp-compress` for a compression pass. + +```sh +npm run sandbox -- --fresh # New profile; keep old runs +npm run sandbox -- --logs # Latest log paths and capture counts +npm run sandbox -- --path # Current profile's host directory +npm run sandbox -- -- --continue # Resume a session +npm run sandbox -- --update # Remember the latest release +npm run sandbox -- --opencode VERSION # Pin a release to test +npm run sandbox -- --transport http # Select V2's transport +npm run sandbox -- --model openai/MODEL # Select a model available to your account +``` + +Replace `VERSION` and `MODEL` with the release and model you want to test. +Add `--v1` to manage the V1 sandbox. Model, transport, and version choices persist; +updates are explicit. `--fresh` selects a new profile for subsequent launches. +You can edit `dcp.jsonc` and CLI preferences; `opencode.json` is launcher-managed. +Set `DCP_SANDBOX_DIR` to choose another state directory. + +For a shortcut on Linux, run from the checkout: + +```sh +mkdir -p ~/.local/bin +ln -s "$PWD/scripts/sandbox.mjs" ~/.local/bin/dcp-sandbox +dcp-sandbox +``` + +### Request Logs + +Each launch has `raw/` and `readable/` directories under its timestamped log folder. +The launcher manages the WebSocket relay and readable-log watcher. Requests appear +as they are sent; assembled responses appear when they finish, while the session +stays open. `--logs` only shows paths and capture counts. + +Start at `readable/index.json`, then a session's numbered request folders: + +```text +readable//0001_primary_websocket/ + request.json # Pretty-printed body actually sent + response.json # Assistant content, parsed tool calls, token totals, errors + meta.json # Timing, completion, transport, raw source, continuation ID +``` + +V2's full pre-transport snapshots are in each session's `context/` directory. +WebSocket continuation requests remain deltas with `previous_response_id`. +Partial and failed responses are marked in metadata. Full provider metadata, +original HTTP bytes, and WebSocket frames remain available in `raw/`. + +## Integration Tests + +The containerized lab exercises packed plugins on V1 and V2, including HTTP and +WebSocket compression, commands, permissions, concurrent sessions, persistence, +and native compaction. It uses a local mock provider without live credentials. + +With Docker and the sibling logger checkout available, install its dependencies: + +```sh +npm --prefix ../opencode-request-logger ci --legacy-peer-deps +``` + +Build [tests/lab/Dockerfile](tests/lab/Dockerfile) using the image tag expected by +[scripts/lab.mjs](scripts/lab.mjs), then run: + +```sh +node scripts/lab.mjs +``` + +The runner prints its output directory under `/tmp/opencode/dcp-lab/`. Set +`DCP_LAB_DIR` to override it. Add `--built` to reuse an existing DCP build. +For real-provider checks, `node scripts/lab.mjs --live` uses the current build and +Codex authentication from `~/.codex/auth.json`. + +Inspect capture summaries without opening large transcripts: + +```sh +node tests/lab/inspect.mjs +``` + +Terminal-panel checks require `uv` and reuse a completed lab run: + +```sh +uv run --with pexpect --with pyte tests/lab/ui.py v2 +uv run --with pexpect --with pyte tests/lab/ui.py v1 +``` + +These check the panel, Context, Stats, persisted manual-mode toggle, and closing +the dialog. Terminal transcripts and screen snapshots are saved in the lab output. diff --git a/README.md b/README.md index 1c209f59..e0db46a9 100644 --- a/README.md +++ b/README.md @@ -17,112 +17,10 @@ opencode plugin @tarquinen/opencode-dcp@latest --global This installs the package and adds it to your global OpenCode config. -### OpenCode V2 migration +## Related Project -This working tree targets **OpenCode 2.0.4** and retains **V1 1.18.29+** support -through a shared package entrypoint. This initial migration has passed the -documented integration checks; it has not been published as a new release. - -For a local V2 installation, build this checkout and add its directory to your -V2 `opencode.json`: - -```jsonc -{ - "plugins": [{ "package": "/absolute/path/to/opencode-dynamic-context-pruning" }], - "permissions": [{ "action": "compress", "resource": "*", "effect": "allow" }], -} -``` - -Existing `dcp.jsonc` settings still apply. The V2 adapter supports range/message -compression and exposes the DCP panel through `/dcp`. V2 currently blocks -`compress: ask` because the public plugin API has no permission-request method. -Model-invisible chat reports are omitted; their reporting extension point remains -available for a later implementation. DCP's self-updater remains V1-only. - -For a local V1 installation, register the directory in `opencode.json`'s `plugin` -array. To load the panel, also register it in the separate `tui.json`: - -```jsonc -{ "plugin": ["/absolute/path/to/opencode-dynamic-context-pruning"] } -``` - -### Manual V1/V2 sandbox - -From this checkout, run: - -```sh -npm run sandbox -``` - -This builds DCP and the sibling `opencode-request-logger` checkout, prepares a -clean Docker image automatically, and opens OpenCode **2.0.4** with both plugins. -It uses your current ChatGPT token from `~/.codex/auth.json` (or `CODEX_HOME` / -`DCP_CODEX_AUTH`), the `openai/gpt-5.6-sol` model, and WebSockets. Docker and Node/npm -are required. If the token expires, refresh your login in Codex and relaunch. - -Sessions, an empty-to-start scratch workspace, and editable `dcp.jsonc` persist -under `~/.local/state/dcp-sandbox/`. Your host project, normal OpenCode config, -agents, plugins, and service are not mounted or inherited. Each launch has its -own raw and readable JSON logs; the launcher starts and stops the relay for you. -Try `/dcp` for the panel or `/dcp-compress` to request compression manually. - -Use `dcp-sandbox --v1` (or `npm run sandbox -- --v1`) for OpenCode **1.18.29** -over HTTP. V1 has its own sessions, workspace, and settings under the `v1/` -subdirectory. Plain `dcp-sandbox` always selects V2. Each major retains its own -profile database. Add `--v1` to management commands when working with V1. - -```sh -npm run sandbox -- --fresh # New empty sandbox; keep old runs -npm run sandbox -- --logs # Show log paths and capture counts -npm run sandbox -- --v1 --logs # Same for the V1 sandbox -npm run sandbox -- --path # Current sandbox's host directory -npm run sandbox -- -- --continue # Resume a sandbox session -npm run sandbox -- --update # Remember the latest OpenCode 2.x -npm run sandbox -- --v1 --update # Remember the latest OpenCode 1.x -npm run sandbox -- --opencode 2.0.4 # Pin a particular version again -npm run sandbox -- --transport http # Switch transport (remembered) -``` - -Every launch rebuilds both plugins, so relaunch after changing their code. Model, -transport, and OpenCode-version selections are remembered; updates are explicit. -`--fresh` switches subsequent launches to the new sandbox. DCP settings and CLI -preferences are preserved; `opencode.json` is launcher-managed. Override the -state location with `DCP_SANDBOX_DIR` and see `npm run sandbox -- --help` for more. - -Logs have `raw/` and `readable/` directories. Readable requests appear as they are -sent, and assembled responses appear as soon as they finish. The watcher runs -throughout the session, including while a WebSocket stays open for further requests. -`--logs` shows the paths and capture counts without generating or rewriting files. -Start at `readable/index.json`, then a session's numbered request folders: - -```text -readable//0001_primary_websocket/ - request.json # Pretty-printed body actually sent - response.json # Assistant content, parsed tool calls, token totals, errors - meta.json # Timing, transport, completion, raw source, continuation ID -``` - -V2's full pre-transport snapshots are in each session's `context/` directory. -Readable responses omit echoed prompts, tool definitions, encrypted reasoning, -and detailed usage attribution; those remain available in the raw captures. -WebSocket continuation requests remain deltas with `previous_response_id`; the -formatter does not invent a full wire request. Partial/failed responses are marked -in metadata, and original HTTP bytes/WS frames remain in `raw/`. No transcript -Markdown is generated. - -To install an executable shortcut on Linux: - -```sh -chmod +x scripts/sandbox.mjs -ln -s "$PWD/scripts/sandbox.mjs" ~/.local/bin/dcp-sandbox -dcp-sandbox -``` - -## Project Status - -Development on DCP has slowed because most new context-management work has moved to [Sleev](https://sleev.ai) and the `sleev` CLI. Sleev is a local proxy for Claude Code, Codex, and OpenCode that builds on DCP's core ideas with newer context-management features and will work with any harness/client. - -DCP remains available for OpenCode plugin users, but new features are landing in Sleev first. If you are starting fresh, we recommend trying Sleev: +[Sleev](https://sleev.ai) is a local proxy for coding agents, including Claude Code, +Codex, and OpenCode. It provides context management through the `sleev` CLI: ```bash npm i -g sleev @@ -150,7 +48,7 @@ Identifies repeated tool calls (same tool, same arguments) and keeps only the mo ### Purge Errors -Prunes inputs from errored tool calls after a configurable number of turns (default: 4). Error messages are preserved; only the potentially large input content is removed. Recalculated on compress tool use. +Prunes inputs from errored tool calls after a configurable number of turns. Error messages are preserved; only the potentially large input content is removed. Recalculated on compress tool use. ## Configuration @@ -163,7 +61,7 @@ DCP uses its own config file, searched in order: Each level overrides the previous, so project settings take priority over global. Restart OpenCode after making config changes. > [!NOTE] -> If you use models with smaller context windows, such as GitHub Copilot models or local models, lower `compress.minContextLimit` and `compress.maxContextLimit` in your configuration to match the available context. +> If your model has a smaller context window, lower `compress.minContextLimit` and `compress.maxContextLimit` in your configuration to match the available context. > [!IMPORTANT] > Defaults are applied automatically. Expand this if you want to review or override settings. @@ -239,14 +137,12 @@ Each level overrides the previous, so project settings take priority over global // Accepts: number or "X%". // Example: // "modelMaxLimits": { - // "openai/gpt-5.3-codex": 120000, - // "anthropic/claude-sonnet-4.6": "80%" + // "provider/model": "80%" // }, // Optional per-model override for minContextLimit. // If present, this wins over the global minContextLimit. // "modelMinLimits": { - // "openai/gpt-5.3-codex": 50000, - // "anthropic/claude-sonnet-4.6": "25%" + // "provider/model": "25%" // }, // How often the context-limit nudge fires (1 = every fetch, 5 = every 5th) "nudgeFrequency": 5, @@ -295,7 +191,7 @@ DCP provides a TUI panel and one prompt-producing slash command: ### Prompt Overrides -DCP exposes six editable prompts: +DCP exposes the following editable prompts: - `system` - `compress-range` @@ -325,15 +221,18 @@ For the `compress` tool, `compress.protectedTools` ensures specific tool outputs LLM providers cache prompts based on exact prefix matching. When DCP prunes content, it changes messages, which invalidates cached prefixes from that point forward. -**Trade-off:** You lose some cache reads but gain token savings from reduced context size and fewer hallucinations from stale context. In most cases, especially in long sessions, the savings outweigh the cache miss cost. - -> [!NOTE] -> In testing, cache hit rates were approximately 85% with DCP vs 90% without. +**Trade-off:** Pruning reduces context size but can increase cache misses. The cost +balance depends on your conversation, compression frequency, and provider pricing. **No impact for:** - **Request-based billing** — Some providers charge per request, not tokens. -- **Uniform token pricing** — Providers like Cerebras that bill cached and uncached tokens at the same rate. +- **Uniform token pricing** — Providers that bill cached and uncached tokens at the same rate. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, local installation, +and testing with the V1/V2 sandbox. ## License From 36795a58156910ed64ef7d6f4a2dd40746ef80a9 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:55:44 -0400 Subject: [PATCH 09/15] ci: use compatible dependency installation for both hosts --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 124c9091..9a10077d 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -20,7 +20,7 @@ jobs: cache: "npm" - name: Install dependencies - run: npm ci + run: npm ci --legacy-peer-deps - name: Format check run: npm run format:check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b3eae322..9cd195a1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,7 +48,7 @@ jobs: - name: Install dependencies if: steps.package.outputs.published == 'false' - run: npm ci + run: npm ci --legacy-peer-deps - name: Format check if: steps.package.outputs.published == 'false' From 6bb9381c9eefc10c23e712cceff096628b3badee Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 12:55:44 -0400 Subject: [PATCH 10/15] fix: update vulnerable Browserslist dependency --- package-lock.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12ddcfad..0051a4c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4067,9 +4067,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "version": "2.11.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz", + "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -4122,9 +4122,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", "funding": [ { "type": "opencollective", @@ -4141,11 +4141,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -4314,9 +4314,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "funding": [ { "type": "opencollective", @@ -4571,9 +4571,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.373", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.373.tgz", - "integrity": "sha512-G2Hym8JIf/QreuseqkDibgH8Ci8KfJzqGDKdakbhSx9UltwRBH2cBLAWU/lBX0sCdv0TlhyxQyDCnSfxgMWsjA==", + "version": "1.5.430", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz", + "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -5939,9 +5939,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", "license": "MIT", "engines": { "node": ">=18" @@ -7335,9 +7335,9 @@ "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==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "funding": [ { "type": "opencollective", From 4a1a9a3fa0e50aac57a042e3bc3dd1d42bfe57da Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 13:07:07 -0400 Subject: [PATCH 11/15] test: bundle request logging with the DCP sandbox Keep the logger in a private npm workspace, integrate its checks into CI, and build both plugins from a single checkout. Exclude development logging tools from the published DCP package. --- .github/workflows/pr-checks.yml | 3 + package-lock.json | 27 +- package.json | 7 +- scripts/lab.mjs | 2 +- scripts/sandbox.mjs | 7 +- tests/logger/index.ts | 10 + tests/logger/log.ts | 116 ++++++ tests/logger/package.json | 37 ++ tests/logger/readable.mjs | 525 +++++++++++++++++++++++++++ tests/logger/relay.mjs | 143 ++++++++ tests/logger/server.js | 1 + tests/logger/tests/capture.test.mjs | 150 ++++++++ tests/logger/tests/readable.test.mjs | 440 ++++++++++++++++++++++ tests/logger/tsconfig.json | 21 ++ tests/logger/v1.ts | 42 +++ tests/logger/v2.ts | 58 +++ 16 files changed, 1581 insertions(+), 8 deletions(-) create mode 100644 tests/logger/index.ts create mode 100644 tests/logger/log.ts create mode 100644 tests/logger/package.json create mode 100644 tests/logger/readable.mjs create mode 100644 tests/logger/relay.mjs create mode 100644 tests/logger/server.js create mode 100644 tests/logger/tests/capture.test.mjs create mode 100644 tests/logger/tests/readable.test.mjs create mode 100644 tests/logger/tsconfig.json create mode 100644 tests/logger/v1.ts create mode 100644 tests/logger/v2.ts diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 9a10077d..4633b352 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -31,6 +31,9 @@ jobs: - name: Build run: npm run build + - name: Test + run: npm test + - name: Security audit run: npm audit --audit-level=high continue-on-error: false diff --git a/package-lock.json b/package-lock.json index 0051a4c5..f800c1c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "@tarquinen/opencode-dcp", "version": "3.1.15", "license": "AGPL-3.0-or-later", + "workspaces": [ + "tests/logger" + ], "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.4.3", @@ -6124,6 +6127,10 @@ "node": ">=0.10.0" } }, + "node_modules/opencode-request-logger": { + "resolved": "tests/logger", + "link": true + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -7570,7 +7577,6 @@ "version": "8.21.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7619,6 +7625,25 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "tests/logger": { + "name": "opencode-request-logger", + "version": "0.1.0", + "dependencies": { + "ws": "^8.21.3" + }, + "peerDependencies": { + "@opencode-ai/plugin": ">=1.18.29", + "@opencode/plugin": "^2.0.4" + }, + "peerDependenciesMeta": { + "@opencode-ai/plugin": { + "optional": true + }, + "@opencode/plugin": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 451430f2..984b3c25 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "name": "@tarquinen/opencode-dcp", "version": "3.1.15", "type": "module", + "workspaces": [ + "tests/logger" + ], "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -28,8 +31,8 @@ "prepublishOnly": "npm run check:package", "dev": "opencode plugin dev", "sandbox": "node scripts/sandbox.mjs", - "typecheck": "tsc --noEmit", - "test": "node --import tsx --test tests/*.test.ts", + "typecheck": "tsc --noEmit && npm run typecheck --workspace=tests/logger", + "test": "node --import tsx --test tests/*.test.ts && npm test --workspace=tests/logger", "format": "prettier --write .", "format:check": "prettier --check .", "dcp": "tsx scripts/print.ts" diff --git a/scripts/lab.mjs b/scripts/lab.mjs index 3dcc8cc6..db73578e 100644 --- a/scripts/lab.mjs +++ b/scripts/lab.mjs @@ -26,7 +26,7 @@ if (live) { ) } execFileSync("npm", ["pack", "--pack-destination", artifacts], { - cwd: resolve(repo, "../opencode-request-logger"), + cwd: join(repo, "tests/logger"), stdio: "pipe", }) if (!live && !process.argv.includes("--built")) { diff --git a/scripts/sandbox.mjs b/scripts/sandbox.mjs index afe87eed..7c3ebe09 100755 --- a/scripts/sandbox.mjs +++ b/scripts/sandbox.mjs @@ -29,7 +29,7 @@ const { values } = parseArgs({ if (values.help) { console.log(`Usage: dcp-sandbox [options] [-- OpenCode arguments] -Open isolated OpenCode with this checkout's DCP and sibling request logger. +Open isolated OpenCode with this checkout's DCP and bundled test logger. Sessions and scratch files persist. Both plugins are rebuilt on every launch. --v1 Use V1 (default: 1.18.29, HTTP), with its own saved state @@ -184,10 +184,9 @@ async function main() { join(repo, "scripts/sandbox"), ]) console.log("Building DCP and request logger…") + if (!existsSync(join(repo, "node_modules"))) command("npm", ["ci", "--legacy-peer-deps"]) const packages = [] - for (const directory of [repo, resolve(repo, "../opencode-request-logger")]) { - if (!existsSync(join(directory, "node_modules"))) - command("npm", ["ci", "--legacy-peer-deps"], directory) + for (const directory of [repo, join(repo, "tests/logger")]) { command("npm", ["run", "build"], directory) const packed = JSON.parse( command( diff --git a/tests/logger/index.ts b/tests/logger/index.ts new file mode 100644 index 00000000..128a8da8 --- /dev/null +++ b/tests/logger/index.ts @@ -0,0 +1,10 @@ +import type { Plugin } from "@opencode/plugin" +import { server } from "./v1.js" +import { setup } from "./v2.js" + +// Type-only V2 imports keep the shared entrypoint loadable in V1. +export default { + id: "opencode-request-logger", + setup, + server, +} satisfies Plugin.Plugin & { server: typeof server } diff --git a/tests/logger/log.ts b/tests/logger/log.ts new file mode 100644 index 00000000..9575a1a1 --- /dev/null +++ b/tests/logger/log.ts @@ -0,0 +1,116 @@ +import { randomUUID } from "node:crypto" +import { appendFile, mkdir, rename, writeFile } from "node:fs/promises" +import { homedir } from "node:os" +import { join } from "node:path" + +export const defaultDirectory = () => + process.env.REQUEST_LOG_DIR || + join( + process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), + "opencode", + "logs", + "request-logger", + ) + +export interface Scope { + sessionID: string + kind?: string + agent?: string + model?: unknown +} + +export function decode(text: string): unknown { + try { + return JSON.parse(text) + } catch { + return text + } +} + +export class Capture { + constructor(readonly directory = defaultDirectory()) {} + + async write(scope: Scope, id: string, suffix: string, value: unknown) { + const directory = join(this.directory, encodeURIComponent(scope.sessionID)) + const file = join(directory, `${id}.${suffix}`) + try { + await mkdir(directory, { recursive: true, mode: 0o700 }) + if (value instanceof Uint8Array) { + await appendFile(file, value, { mode: 0o600 }) + } else { + await writeFile( + `${file}.tmp`, + JSON.stringify({ + timestamp: new Date().toISOString(), + ...scope, + requestID: id, + ...(value as object), + }), + { mode: 0o600 }, + ) + await rename(`${file}.tmp`, file) + } + } catch (error) { + console.error("request-logger: cannot write capture", file, error) + } + } + + async request(scope: Scope, request: Request) { + const id = randomUUID() + await this.write(scope, id, "request.json", { + type: "http.request", + url: request.url, + method: request.method, + body: decode(await request.clone().text()), + }) + return id + } + + async response(scope: Scope, id: string, response: Response) { + await this.write(scope, id, "response.json", { + type: "http.response", + status: response.status, + contentType: response.headers.get("content-type"), + }) + const end = (status: string) => + this.write(scope, id, "end.json", { type: "http.end", status }) + if (!response.body) { + await end("completed") + return response + } + const reader = response.body.getReader() + const capture = this + const body = new ReadableStream({ + async pull(controller) { + try { + const chunk = await reader.read() + if (chunk.done) { + await end("completed") + controller.close() + reader.releaseLock() + } else { + await capture.write(scope, id, "response.body", chunk.value) + controller.enqueue(chunk.value) + } + } catch (error) { + await end("error") + controller.error(error) + reader.releaseLock() + } + }, + async cancel(reason) { + await end("cancelled") + try { + await reader.cancel(reason) + } finally { + reader.releaseLock() + } + }, + }) + return new Response(body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }) + } +} diff --git a/tests/logger/package.json b/tests/logger/package.json new file mode 100644 index 00000000..4319ccbd --- /dev/null +++ b/tests/logger/package.json @@ -0,0 +1,37 @@ +{ + "name": "opencode-request-logger", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Request capture for DCP development and sandbox testing", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "server.js", + "relay.mjs", + "readable.mjs" + ], + "scripts": { + "clean": "rm -rf dist", + "build": "npm run clean && tsc", + "prepack": "npm run build", + "typecheck": "tsc --noEmit", + "test": "npm run build && node --test tests/*.test.mjs" + }, + "peerDependencies": { + "@opencode-ai/plugin": ">=1.18.29", + "@opencode/plugin": "^2.0.4" + }, + "peerDependenciesMeta": { + "@opencode-ai/plugin": { + "optional": true + }, + "@opencode/plugin": { + "optional": true + } + }, + "dependencies": { + "ws": "^8.21.3" + } +} diff --git a/tests/logger/readable.mjs b/tests/logger/readable.mjs new file mode 100644 index 00000000..e9026562 --- /dev/null +++ b/tests/logger/readable.mjs @@ -0,0 +1,525 @@ +import { watch as watchFiles } from "node:fs" +import { mkdir, open, readdir, readFile, rename, stat, writeFile } from "node:fs/promises" +import { basename, join, relative, resolve } from "node:path" +import { StringDecoder } from "node:string_decoder" +import { pathToFileURL } from "node:url" + +const json = async (file) => JSON.parse(await readFile(file, "utf8")) +async function optional(file) { + try { + return await json(file) + } catch (error) { + if (error.code === "ENOENT") return undefined + throw error + } +} +async function save(file, value) { + await mkdir(resolve(file, ".."), { recursive: true, mode: 0o700 }) + await writeFile(`${file}.tmp`, JSON.stringify(value, null, 2) + "\n", { + mode: 0o600, + }) + await rename(`${file}.tmp`, file) +} + +// Retain one assembled response, not the potentially huge list of token events. +function response() { + let value = {} + let complete = false + let terminalOutput = false + const items = new Map() + const types = {} + const errors = [] + function accept(event) { + if (!event || typeof event !== "object") return + const type = event.type || "unknown" + types[type] = (types[type] || 0) + 1 + if (event.response) value = { ...value, ...event.response } + if (["response.completed", "response.failed", "response.incomplete"].includes(type)) { + complete = true + terminalOutput = Array.isArray(event.response?.output) + } + if (type === "error") { + errors.push(event) + complete = true + } + const index = event.output_index ?? 0 + if (type === "response.output_item.added" || type === "response.output_item.done") { + items.set(index, structuredClone(event.item)) + } + const item = items.get(index) + if (!item) return + if (["response.content_part.added", "response.content_part.done"].includes(type)) { + ;(item.content ??= [])[event.content_index] = structuredClone(event.part) + } + if ( + [ + "response.reasoning_summary_part.added", + "response.reasoning_summary_part.done", + ].includes(type) + ) { + ;(item.summary ??= [])[event.summary_index] = structuredClone(event.part) + } + if (type === "response.function_call_arguments.delta") + item.arguments = (item.arguments || "") + event.delta + if (type === "response.function_call_arguments.done") item.arguments = event.arguments + if (type === "response.output_text.delta" || type === "response.output_text.done") { + const part = ((item.content ??= [])[event.content_index ?? 0] ??= { + type: "output_text", + text: "", + }) + part.text = type.endsWith(".done") ? event.text : part.text + event.delta + } + if ( + type === "response.reasoning_summary_text.delta" || + type === "response.reasoning_summary_text.done" + ) { + const part = ((item.summary ??= [])[event.summary_index ?? 0] ??= { + type: "summary_text", + text: "", + }) + part.text = type.endsWith(".done") ? event.text : part.text + event.delta + } + } + return { + accept, + get complete() { + return complete + }, + result() { + // Codex may omit output in the final event; the item events still contain it. + const output = + terminalOutput && value.output.length + ? value.output + : [...items].sort(([a], [b]) => a - b).map(([, item]) => item) + return { + body: { + ...value, + status: errors.length ? "failed" : value.status || "incomplete", + output, + ...(errors.length ? { errors } : {}), + }, + complete, + events: types, + supported: Object.keys(types).some( + (type) => type.startsWith("response.") || type === "error", + ), + } + }, + } +} + +function simplify(body) { + if (!Array.isArray(body?.output)) return body + const content = body.output.flatMap((item) => { + if (item.type === "message") { + return (item.content || []).map((part) => + part.type === "output_text" + ? { + type: "text", + text: part.text, + ...(part.annotations?.length ? { annotations: part.annotations } : {}), + } + : part, + ) + } + if (item.type === "reasoning") { + return [...(item.summary || []), ...(item.content || [])] + .filter((part) => part.text) + .map((part) => ({ type: "thinking", thinking: part.text })) + } + if (item.type === "function_call" || item.type === "custom_tool_call") { + let input = item.type === "function_call" ? item.arguments : item.input + if (item.type === "function_call" && typeof input === "string") { + try { + input = JSON.parse(input) + } catch (error) { + if (!(error instanceof SyntaxError)) throw error + } + } + return [{ type: "tool_use", id: item.call_id, name: item.name, input }] + } + // Keep unfamiliar output types visible rather than silently discarding them. + return [item] + }) + const usage = Object.fromEntries( + Object.entries({ + input_tokens: body.usage?.input_tokens, + output_tokens: body.usage?.output_tokens, + total_tokens: body.usage?.total_tokens, + cached_tokens: body.usage?.input_tokens_details?.cached_tokens, + cache_write_tokens: body.usage?.input_tokens_details?.cache_write_tokens, + reasoning_tokens: body.usage?.output_tokens_details?.reasoning_tokens, + }).filter(([, value]) => value !== undefined), + ) + return { + message: { role: "assistant", content }, + ...(Object.keys(usage).length ? { usage } : {}), + ...(body.error ? { error: body.error } : {}), + ...(body.errors?.length + ? { errors: body.errors.map((event) => event.error || event) } + : {}), + ...(body.incomplete_details ? { incomplete_details: body.incomplete_details } : {}), + } +} + +function httpStream(contentType) { + const assembled = response() + let mode = contentType?.includes("text/event-stream") + ? "sse" + : contentType?.includes("json") + ? "json" + : undefined + let buffer = "" + let data = [] + function flush() { + const text = data.join("\n") + data = [] + if (text && text !== "[DONE]") assembled.accept(JSON.parse(text)) + } + return { + get complete() { + return assembled.complete + }, + push(text) { + buffer += text + // Codex may omit Content-Type. Wait for enough bytes to identify SSE. + if (!mode && buffer.includes("\n")) + mode = /^(?:(?:event|data|id|retry):|:)/.test(buffer.trimStart()) ? "sse" : "json" + if (mode !== "sse") return + let index + while ((index = buffer.indexOf("\n")) !== -1) { + const line = buffer.slice(0, index).replace(/\r$/, "") + buffer = buffer.slice(index + 1) + if (!line) flush() + else if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, "")) + } + }, + result(ended = false) { + if (mode === "sse") { + if (ended) { + if (buffer.startsWith("data:")) data.push(buffer.slice(5).replace(/^ /, "")) + try { + flush() + } catch (error) { + if (!(error instanceof SyntaxError)) throw error + } + } + return assembled.result() + } + if (!ended) return + try { + return { body: JSON.parse(buffer), complete: true, supported: true } + } catch (error) { + if (!(error instanceof SyntaxError)) throw error + } + }, + } +} + +/** Watch raw captures, reading appended bytes once and publishing completed responses. */ +export async function watch(directory, destination = join(directory, "readable")) { + directory = resolve(directory) + destination = resolve(destination) + if (directory === destination) + throw new Error("Readable output must differ from the raw directory.") + await mkdir(directory, { recursive: true, mode: 0o700 }) + const sessions = new Map() + const http = new Map() + const sockets = new Map() + const tails = new Map() + const pending = new Set() + const dirty = new Set() + const warnings = [] + let timer + let closing + let running = Promise.resolve() + function session(file) { + const id = basename(resolve(file, "..")) + if (!sessions.has(id)) + sessions.set(id, { + id, + path: join(destination, id), + requests: [], + contexts: new Set(), + }) + return sessions.get(id) + } + function changed(entry) { + dirty.add(entry.session) + } + async function request(file, record, transport) { + const state = session(file) + const kind = record.kind || "request" + const name = `${String(state.requests.length + 1).padStart(4, "0")}_${kind.replace(/[^a-z0-9_-]/gi, "_")}_${transport}` + const path = join(state.path, name) + const meta = { + timestamp: record.timestamp, + transport, + kind, + model: record.body?.model, + requestID: record.requestID, + connectionID: record.connectionID, + sequence: record.sequence, + continuation: record.body?.previous_response_id ? "incremental" : "full", + previous_response_id: record.body?.previous_response_id, + raw: relative(path, file), + protocolComplete: false, + } + const entry = { session: state, path, meta } + state.requests.push(entry) + await save(join(path, "request.json"), record.body) + await finish(entry) + return entry + } + async function finish(entry, assembled) { + if (assembled) { + await save(join(entry.path, "response.json"), simplify(assembled.body)) + Object.assign(entry.meta, { + responseID: assembled.body?.id, + status: assembled.body?.status, + protocolComplete: assembled.complete, + events: assembled.events, + assembled: assembled.supported, + }) + } + await save(join(entry.path, "meta.json"), entry.meta) + changed(entry) + } + async function tail(file, consume) { + let handle + try { + handle = await open(file, "r") + } catch (error) { + if (error.code === "ENOENT") return + throw error + } + if (!tails.has(file)) tails.set(file, { offset: 0, decoder: new StringDecoder("utf8") }) + const state = tails.get(file) + const buffer = Buffer.alloc(65536) + try { + while (true) { + const { bytesRead } = await handle.read(buffer, 0, buffer.length, state.offset) + if (!bytesRead) break + state.offset += bytesRead + await consume(state.decoder.write(buffer.subarray(0, bytesRead))) + } + } finally { + await handle.close() + } + } + async function readHttp(prefix) { + let state = http.get(prefix) + if (state?.ended) return + if (!state) { + const record = await optional(`${prefix}request.json`) + if (!record) return + state = { entry: await request(`${prefix}request.json`, record, "http") } + state.entry.meta.url = record.url + http.set(prefix, state) + } + const end = await optional(`${prefix}end.json`) + if (!state.headers) state.headers = await optional(`${prefix}response.json`) + if (state.headers && !state.rendered) { + state.stream ??= httpStream(state.headers.contentType) + await tail(`${prefix}response.body`, (text) => state.stream.push(text)) + if (state.stream.complete || end) { + await finish(state.entry, state.stream.result(!!end)) + state.rendered = true + state.stream = undefined + tails.delete(`${prefix}response.body`) + } + } + const meta = state.entry.meta + const ending = end?.status || "pending" + if (meta.httpStatus !== state.headers?.status || meta.transportEnd !== ending) { + Object.assign(meta, { + httpStatus: state.headers?.status, + transportEnd: ending, + error: end?.error, + }) + await finish(state.entry) + } + state.ended = !!end + } + async function readSocket(file) { + if (!sockets.has(file)) sockets.set(file, { buffer: "", events: [] }) + const state = sockets.get(file) + await tail(file, async (text) => { + state.buffer += text + let index + while ((index = state.buffer.indexOf("\n")) !== -1) { + const line = state.buffer.slice(0, index) + state.buffer = state.buffer.slice(index + 1) + if (!line.trim()) continue + const frame = JSON.parse(line) + if ( + frame.type === "ws.frame" && + frame.direction === "request" && + frame.body?.type === "response.create" + ) { + if (state.assembled) await finish(state.entry, state.assembled.result()) + state.entry = await request(file, frame, "websocket") + state.assembled = response() + } else if ( + frame.type === "ws.frame" && + frame.direction === "response" && + !frame.binary + ) { + state.assembled?.accept(frame.body) + if (state.assembled?.complete) { + await finish(state.entry, state.assembled.result()) + state.assembled = undefined + } + } else if (frame.type !== "ws.frame") { + state.events.push(frame) + if ( + (frame.type === "ws.close" || frame.type === "ws.error") && + state.assembled + ) { + state.entry.meta.transportEnd = + frame.type === "ws.error" ? "error" : "closed" + await finish(state.entry, state.assembled.result()) + state.assembled = undefined + } + await save( + join( + session(file).path, + "connections", + `${basename(file, ".ws.jsonl")}.json`, + ), + state.events, + ) + } + } + }) + } + async function visit(file) { + if (file === destination || file.startsWith(destination + "/") || file.endsWith(".tmp")) + return + let info + try { + info = await stat(file) + } catch (error) { + if (error.code === "ENOENT") return + throw error + } + if (info.isDirectory()) { + for (const name of await readdir(file)) pending.add(join(file, name)) + } else if (file.endsWith(".context.json")) { + const state = session(file) + if (state.contexts.has(file)) return + const context = await json(file) + state.contexts.add(file) + await save( + join( + state.path, + "context", + `${String(state.contexts.size).padStart(4, "0")}_${context.kind || "context"}.json`, + ), + context, + ) + dirty.add(state) + } else if (file.endsWith(".ws.jsonl")) { + await readSocket(file) + } else if (/\.(request\.json|response\.json|response\.body|end\.json)$/.test(file)) { + await readHttp( + file.replace(/(request\.json|response\.json|response\.body|end\.json)$/, ""), + ) + } + } + async function publish() { + for (const state of dirty) { + await save( + join(state.path, "index.json"), + state.requests.map(({ path, meta }) => ({ + directory: basename(path), + ...meta, + })), + ) + } + dirty.clear() + const states = [...sessions.values()] + const requests = states.flatMap((state) => state.requests) + const summary = { + sessions: states.length, + requests: requests.length, + http: requests.filter(({ meta }) => meta.transport === "http").length, + websocket: requests.filter(({ meta }) => meta.transport === "websocket").length, + contexts: states.reduce((sum, state) => sum + state.contexts.size, 0), + pending: requests.filter(({ meta }) => !meta.protocolComplete).length, + } + await save(join(destination, "index.json"), { + format: "opencode-request-logger/readable", + sessions: states.map((state) => ({ + directory: state.id, + requests: state.requests.length, + contexts: state.contexts.size, + })), + warnings, + summary, + }) + } + function report(file, error) { + warnings.push({ file: relative(directory, file), message: error.message }) + console.error("request-logger: readable capture failed", file, error) + } + async function drain() { + while (pending.size) { + const files = [...pending] + pending.clear() + for (const file of files) { + try { + await visit(file) + } catch (error) { + report(file, error) + } + } + } + if (dirty.size) await publish() + } + const watcher = watchFiles(directory, { recursive: true }, (_event, name) => { + if (!name) return + pending.add(join(directory, name)) + if (!timer) + timer = setTimeout(() => { + timer = undefined + running = running.then(drain).catch((error) => report(directory, error)) + }, 10) + }) + watcher.on("error", (error) => report(directory, error)) + // Watch before scanning so files created during startup cannot be missed. + pending.add(directory) + running = (async () => { + await drain() + await publish() + })() + await running + return { + close() { + return (closing ??= (async () => { + watcher.close() + clearTimeout(timer) + await running + pending.add(directory) + await drain() + for (const state of [...http.values(), ...sockets.values()]) { + const assembled = state.stream?.result(true) || state.assembled?.result() + if (!assembled) continue + state.entry.meta.transportEnd = "interrupted" + await finish(state.entry, assembled) + } + await publish() + })()) + }, + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (!process.argv[2]) + throw new Error( + "Usage: node readable.mjs RAW_DIRECTORY [OUTPUT_DIRECTORY] (watches until stopped)", + ) + const watcher = await watch(process.argv[2], process.argv[3]) + console.log("Watching raw captures for readable requests and responses.") + for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void watcher.close()) +} diff --git a/tests/logger/relay.mjs b/tests/logger/relay.mjs new file mode 100644 index 00000000..76508748 --- /dev/null +++ b/tests/logger/relay.mjs @@ -0,0 +1,143 @@ +import { createServer } from "node:http" +import { randomUUID } from "node:crypto" +import { mkdirSync, appendFileSync } from "node:fs" +import { join } from "node:path" +import { pathToFileURL } from "node:url" +import { WebSocket, WebSocketServer } from "ws" +import { defaultDirectory, decode } from "./dist/log.js" + +// Native V2 exposes a handshake hook, but no hook for the frames themselves. +export function createRelay({ directory = defaultDirectory() } = {}) { + const server = createServer((_request, response) => { + response.writeHead(200, { "content-type": "application/json" }) + response.end('{"ok":true}') + }) + const sockets = new WebSocketServer({ noServer: true }) + const connections = new Set() + + server.on("upgrade", (request, socket, head) => { + const url = new URL(request.url, "http://localhost") + const upstream = url.searchParams.get("upstream") + if (url.pathname !== "/relay" || !upstream || !/^wss?:\/\//.test(upstream)) { + socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n") + return + } + const sessionID = url.searchParams.get("session") || "unknown" + const kind = url.searchParams.get("kind") || "primary" + const connectionID = randomUUID() + const folder = join(directory, encodeURIComponent(sessionID)) + const file = join(folder, `${connectionID}.ws.jsonl`) + let sequence = 0 + function record(event) { + try { + mkdirSync(folder, { recursive: true, mode: 0o700 }) + appendFileSync( + file, + JSON.stringify({ + timestamp: new Date().toISOString(), + sessionID, + kind, + connectionID, + sequence: sequence++, + ...event, + }) + "\n", + { mode: 0o600 }, + ) + } catch (error) { + console.error("request-logger: cannot record WebSocket", error) + } + } + const headers = Object.fromEntries( + Object.entries(request.headers).filter( + ([key]) => + !["host", "connection", "upgrade"].includes(key) && + !key.startsWith("sec-websocket-"), + ), + ) + const protocols = request.headers["sec-websocket-protocol"] + ?.split(",") + .map((item) => item.trim()) + const remote = new WebSocket(upstream, protocols, { headers }) + connections.add(remote) + let local + const close = (target, code, reason) => { + if (!target || target.readyState === WebSocket.CLOSED) return + if ([1005, 1006, 1015].includes(code)) target.terminate() + else target.close(code, reason) + } + const forward = (from, to, direction) => (data, binary) => { + record({ + type: "ws.frame", + direction, + binary, + body: binary ? data.toString("base64") : decode(data.toString()), + }) + from.pause() + to.send(data, { binary }, (error) => { + if (error) { + record({ type: "ws.error", direction, message: error.message }) + from.terminate() + to.terminate() + } else from.resume() + }) + } + remote.on("open", () => { + sockets.handleUpgrade(request, socket, head, (client) => { + local = client + connections.add(client) + record({ type: "ws.open", url: upstream }) + client.on("message", forward(client, remote, "request")) + remote.on("message", forward(remote, client, "response")) + client.on("error", (error) => { + record({ type: "ws.error", direction: "request", message: error.message }) + remote.terminate() + }) + client.on("close", (code, reason) => { + connections.delete(client) + record({ + type: "ws.close", + direction: "request", + code, + reason: reason.toString(), + }) + close(remote, code, reason) + }) + }) + }) + remote.on("unexpected-response", (_req, response) => { + record({ type: "ws.rejected", status: response.statusCode }) + socket.write( + `HTTP/1.1 ${response.statusCode} ${response.statusMessage}\r\nConnection: close\r\n\r\n`, + ) + response.pipe(socket) + connections.delete(remote) + }) + remote.on("error", (error) => { + record({ type: "ws.error", direction: "response", message: error.message }) + if (local) local.terminate() + else socket.end("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n") + }) + remote.on("close", (code, reason) => { + connections.delete(remote) + record({ type: "ws.close", direction: "response", code, reason: reason.toString() }) + close(local, code, reason) + }) + socket.on("error", () => remote.terminate()) + }) + return { + server, + async close() { + for (const socket of connections) socket.terminate() + sockets.close() + await new Promise((resolve) => server.close(resolve)) + }, + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + const relay = createRelay() + relay.server.listen(Number(process.env.PORT || 4097), process.env.HOST || "127.0.0.1", () => { + console.log(`request-logger relay listening on ${JSON.stringify(relay.server.address())}`) + }) + for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => void relay.close()) +} diff --git a/tests/logger/server.js b/tests/logger/server.js new file mode 100644 index 00000000..1842a449 --- /dev/null +++ b/tests/logger/server.js @@ -0,0 +1 @@ +export { default } from "./dist/index.js" diff --git a/tests/logger/tests/capture.test.mjs b/tests/logger/tests/capture.test.mjs new file mode 100644 index 00000000..228b2110 --- /dev/null +++ b/tests/logger/tests/capture.test.mjs @@ -0,0 +1,150 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { mkdtemp, readdir, readFile, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { once } from "node:events" +import { WebSocket, WebSocketServer } from "ws" +import { Capture } from "../dist/log.js" +import { setup } from "../dist/v2.js" +import { createRelay } from "../relay.mjs" + +async function temporary(t) { + const dir = await mkdtemp(join(tmpdir(), "request-logger-")) + t.after(() => rm(dir, { recursive: true, force: true })) + return dir +} + +test("HTTP hooks correlate concurrent sessions and preserve streamed response bytes", async (t) => { + const directory = await temporary(t) + const hooks = new Map() + await setup({ + options: { directory }, + session: { hook: async (name, fn) => hooks.set(name, fn) }, + }) + const requests = ["ses_one", "ses_two"].map((sessionID) => ({ + sessionID, + kind: "primary", + agent: "build", + model: { id: "test", providerID: "test" }, + request: new Request("https://example.com/responses", { + method: "POST", + headers: { authorization: "secret" }, + body: JSON.stringify({ input: sessionID }), + }), + })) + await Promise.all(requests.map((event) => hooks.get("http.request")(event))) + for (const event of requests.reverse()) { + const chunks = [ + "data: ", + JSON.stringify({ text: "héllo 🦊", session: event.sessionID }), + "\n\n", + ] + event.response = new Response( + new ReadableStream({ + start(controller) { + for (const chunk of chunks) controller.enqueue(new TextEncoder().encode(chunk)) + controller.close() + }, + }), + { headers: { "content-type": "text/event-stream" } }, + ) + await hooks.get("http.response")(event) + assert.equal(await event.response.text(), chunks.join("")) + const folder = join(directory, event.sessionID) + const files = await readdir(folder) + const request = JSON.parse( + await readFile( + join( + folder, + files.find((f) => f.endsWith(".request.json")), + ), + "utf8", + ), + ) + assert.equal(request.body.input, event.sessionID) + assert.ok(!JSON.stringify(request).includes("secret")) + assert.equal( + await readFile(join(folder, `${request.requestID}.response.body`), "utf8"), + chunks.join(""), + ) + assert.equal( + JSON.parse(await readFile(join(folder, `${request.requestID}.end.json`))).status, + "completed", + ) + assert.deepEqual( + [...files].sort(), + ["request.json", "response.json", "response.body", "end.json"] + .map((ext) => `${request.requestID}.${ext}`) + .sort(), + ) + } +}) + +test("HTTP capture propagates cancellation to the provider stream", async (t) => { + const directory = await temporary(t) + let cancelled = false + const response = await new Capture(directory).response( + { sessionID: "ses_cancel" }, + "cancel", + new Response( + new ReadableStream({ + pull(controller) { + controller.enqueue(new Uint8Array([65])) + }, + cancel() { + cancelled = true + }, + }), + ), + ) + const reader = response.body.getReader() + await reader.read() + await reader.cancel() + assert.equal(cancelled, true) + assert.equal( + JSON.parse(await readFile(join(directory, "ses_cancel/cancel.end.json"))).status, + "cancelled", + ) +}) + +test("relay preserves text/binary frames, upstream headers, and connection reuse", async (t) => { + const directory = await temporary(t) + const upstream = new WebSocketServer({ port: 0, host: "127.0.0.1" }) + await once(upstream, "listening") + t.after(() => new Promise((resolve) => upstream.close(resolve))) + let connections = 0 + upstream.on("connection", (socket, request) => { + connections++ + assert.equal(request.headers.authorization, "Bearer private") + socket.on("message", (data, binary) => socket.send(data, { binary })) + }) + const relay = createRelay({ directory }) + relay.server.listen(0, "127.0.0.1") + await once(relay.server, "listening") + t.after(() => relay.close()) + const url = new URL(`ws://127.0.0.1:${relay.server.address().port}/relay`) + url.searchParams.set("upstream", `ws://127.0.0.1:${upstream.address().port}`) + url.searchParams.set("session", "ses_ws") + const socket = new WebSocket(url, { headers: { authorization: "Bearer private" } }) + await once(socket, "open") + for (const [data, binary] of [ + [JSON.stringify({ type: "response.create", input: "hello" }), false], + [JSON.stringify({ type: "response.create", previous_response_id: "resp_one" }), false], + [Buffer.from([0, 255, 42]), true], + ]) { + const incoming = once(socket, "message") + socket.send(data, { binary }) + const [result, isBinary] = await incoming + assert.deepEqual(result, Buffer.from(data)) + assert.equal(isBinary, binary) + } + socket.close(1000, "done") + await once(socket, "close") + assert.equal(connections, 1) + const [file] = await readdir(join(directory, "ses_ws")) + const raw = await readFile(join(directory, "ses_ws", file), "utf8") + const events = raw.trim().split("\n").map(JSON.parse) + assert.equal(events.filter((e) => e.type === "ws.frame").length, 6) + assert.ok(!raw.includes("private")) +}) diff --git a/tests/logger/tests/readable.test.mjs b/tests/logger/tests/readable.test.mjs new file mode 100644 index 00000000..a8063966 --- /dev/null +++ b/tests/logger/tests/readable.test.mjs @@ -0,0 +1,440 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { + mkdtemp, + mkdir, + readFile, + readdir, + writeFile, + appendFile, + rename, + rm, +} from "node:fs/promises" +import { join } from "node:path" +import { tmpdir } from "node:os" +import { setTimeout as delay } from "node:timers/promises" +import { watch } from "../readable.mjs" + +async function waitJson(file, ready = () => true) { + const deadline = Date.now() + 5000 + while (Date.now() < deadline) { + try { + const value = JSON.parse(await readFile(file, "utf8")) + if (ready(value)) return value + } catch (error) { + if (error.code !== "ENOENT") throw error + } + await delay(10) + } + assert.fail(`Readable file was not published automatically: ${file}`) +} + +async function fixture(t) { + const root = await mkdtemp(join(tmpdir(), "readable-")) + const raw = join(root, "raw") + const output = join(root, "readable") + const folder = join(raw, "ses_test") + const live = await watch(raw, output) + t.after(async () => { + await live.close() + await rm(root, { recursive: true, force: true }) + }) + await mkdir(folder, { recursive: true }) + const save = async (name, value) => { + await writeFile(join(folder, name + ".tmp"), JSON.stringify(value)) + await rename(join(folder, name + ".tmp"), join(folder, name)) + } + return { root, raw, output, folder, save } +} + +test("readable HTTP/WS requests appear immediately and responses publish before streams close", async (t) => { + const { raw, output, folder, save } = await fixture(t) + const original = { + model: "test", + input: [{ role: "user", content: "one\ntwo" }], + } + const final = { + id: "resp_http", + status: "completed", + instructions: "Echoed instructions belong in raw logs", + tools: [{ type: "function", name: "irrelevant_schema" }], + output: [ + { + type: "reasoning", + id: "rs_opaque", + encrypted_content: "OPAQUE_REASONING", + summary: [{ type: "summary_text", text: "Readable thought" }], + }, + { + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "héllo 🦊\nnext" }], + }, + ], + usage: { + input_tokens: 20, + output_tokens: 3, + input_tokens_details: { cached_tokens: 12 }, + output_tokens_details: { reasoning_tokens: 1 }, + attribution: { items: { huge_internal_breakdown: { input_tokens: 20 } } }, + }, + } + await save("http.request.json", { + timestamp: "2026-09-16T10:00:00Z", + type: "http.request", + requestID: "http", + sessionID: "ses_test", + kind: "title", + body: original, + }) + const title = join(output, "ses_test/0001_title_http") + assert.deepEqual(await waitJson(join(title, "request.json")), original) + await assert.rejects(readFile(join(title, "response.json")), { + code: "ENOENT", + }) + // Real V1/Codex captures may omit this header; the stream still needs assembly. + await save("http.response.json", { status: 200, contentType: null }) + const sse = [ + { type: "response.created", response: { id: "resp_http", output: [] } }, + { type: "response.completed", response: final }, + ] + .map((event) => `data: ${JSON.stringify(event)}\r\n\r\n`) + .join("") + const bytes = Buffer.from(sse) + const split = bytes.indexOf(Buffer.from("🦊")) + 1 + await writeFile(join(folder, "http.response.body"), bytes.subarray(0, split)) + await waitJson(join(title, "meta.json"), (meta) => meta.httpStatus === 200) + await assert.rejects(readFile(join(title, "response.json")), { + code: "ENOENT", + }) + await appendFile(join(folder, "http.response.body"), bytes.subarray(split)) + await waitJson(join(title, "response.json")) + await save("http.end.json", { status: "cancelled" }) + await waitJson(join(title, "meta.json"), (meta) => meta.transportEnd === "cancelled") + const bodies = [ + { + type: "response.create", + model: "test", + input: [{ role: "user", content: "WS_FULL" }], + }, + { + type: "response.create", + model: "test", + previous_response_id: "resp_ws", + input: [{ type: "function_call_output", call_id: "call_1", output: "DONE" }], + }, + ] + const call = { + type: "function_call", + call_id: "call_1", + name: "compress", + arguments: '{"summary":"WS_SUMMARY"}', + } + const frames = [ + { type: "ws.open", timestamp: "2026-09-16T10:00:01Z" }, + { + type: "ws.frame", + timestamp: "2026-09-16T10:00:01Z", + direction: "request", + body: bodies[0], + }, + { + type: "ws.frame", + direction: "response", + body: { type: "response.output_item.done", output_index: 0, item: call }, + }, + { + type: "ws.frame", + direction: "response", + body: { + type: "response.completed", + response: { id: "resp_ws", status: "completed", output: [] }, + }, + }, + { + type: "ws.frame", + timestamp: "2026-09-16T10:00:02Z", + direction: "request", + body: bodies[1], + }, + { + type: "ws.frame", + direction: "response", + body: { + type: "response.completed", + response: { id: "resp_last", status: "completed", output: [] }, + }, + }, + ].map((event, sequence) => ({ + sessionID: "ses_test", + connectionID: "socket", + kind: "primary", + sequence, + ...event, + })) + const transcript = frames.map((frame) => JSON.stringify(frame) + "\n").join("") + await writeFile( + join(folder, "socket.ws.jsonl"), + frames + .slice(0, 2) + .map((frame) => JSON.stringify(frame) + "\n") + .join(""), + ) + const first = join(output, "ses_test/0002_primary_websocket") + assert.deepEqual(await waitJson(join(first, "request.json")), bodies[0]) + await assert.rejects(readFile(join(first, "response.json")), { + code: "ENOENT", + }) + await appendFile( + join(folder, "socket.ws.jsonl"), + frames + .slice(2, 4) + .map((frame) => JSON.stringify(frame) + "\n") + .join(""), + ) + await waitJson(join(first, "response.json")) + await appendFile( + join(folder, "socket.ws.jsonl"), + frames + .slice(4) + .map((frame) => JSON.stringify(frame) + "\n") + .join(""), + ) + await save("context.context.json", { + timestamp: "2026-09-16T10:00:00Z", + type: "context", + kind: "primary", + messages: [{ role: "user", content: "FULL_CONTEXT" }], + }) + const { summary, warnings } = await waitJson( + join(output, "index.json"), + (index) => + index.summary.requests === 3 && + index.summary.pending === 0 && + index.summary.contexts === 1, + ) + assert.deepEqual(warnings, []) + assert.deepEqual(summary, { + sessions: 1, + requests: 3, + http: 1, + websocket: 2, + contexts: 1, + pending: 0, + }) + const session = join(output, "ses_test") + const entries = JSON.parse(await readFile(join(session, "index.json"))) + assert.deepEqual( + entries.map((entry) => entry.directory), + ["0001_title_http", "0002_primary_websocket", "0003_primary_websocket"], + ) + assert.equal(entries[0].transportEnd, "cancelled") + assert.equal(entries[0].protocolComplete, true) + assert.equal(entries[2].continuation, "incremental") + assert.equal(entries[2].previous_response_id, "resp_ws") + assert.deepEqual( + JSON.parse(await readFile(join(session, entries[0].directory, "response.json"))), + { + message: { + role: "assistant", + content: [ + { type: "thinking", thinking: "Readable thought" }, + { type: "text", text: "héllo 🦊\nnext" }, + ], + }, + usage: { + input_tokens: 20, + output_tokens: 3, + cached_tokens: 12, + reasoning_tokens: 1, + }, + }, + ) + for (const [i, body] of [original, ...bodies].entries()) { + const text = await readFile(join(session, entries[i].directory, "request.json"), "utf8") + assert.deepEqual(JSON.parse(text), body) + assert.ok(text.includes('\n "')) + } + assert.deepEqual( + JSON.parse(await readFile(join(session, entries[1].directory, "response.json"))).message + .content, + [ + { + type: "tool_use", + id: "call_1", + name: "compress", + input: { summary: "WS_SUMMARY" }, + }, + ], + ) + assert.equal(await readFile(join(folder, "socket.ws.jsonl"), "utf8"), transcript) + assert.equal(await readFile(join(folder, "http.response.body"), "utf8"), sse) + assert.equal((await readdir(session)).filter((name) => /^\d/.test(name)).length, 3) +}) + +test("partial SSE responses retain text, arguments, and reasoning without claiming completion", async (t) => { + const { raw, output, folder, save } = await fixture(t) + await save("a.request.json", { + timestamp: "2026-09-16", + requestID: "a", + body: { input: "test" }, + }) + await save("a.response.json", { + status: 200, + contentType: "text/event-stream", + }) + const events = [ + { + type: "response.created", + response: { id: "partial", status: "in_progress", output: [] }, + }, + { + type: "response.output_item.added", + output_index: 0, + item: { type: "reasoning", summary: [] }, + }, + { + type: "response.reasoning_summary_text.delta", + output_index: 0, + summary_index: 0, + delta: "Thinking", + }, + { + type: "response.output_item.added", + output_index: 1, + item: { type: "function_call", name: "compress", arguments: "" }, + }, + { + type: "response.function_call_arguments.delta", + output_index: 1, + delta: '{"summary":', + }, + { + type: "response.output_item.added", + output_index: 2, + item: { type: "message", role: "assistant", content: [] }, + }, + { + type: "response.output_text.delta", + output_index: 2, + content_index: 0, + delta: "Partial 🦊", + }, + ] + await writeFile( + join(folder, "a.response.body"), + events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + + 'data: {"type":"response.out', + ) + await save("a.end.json", { status: "cancelled" }) + const body = await waitJson(join(output, "ses_test/0001_request_http/response.json")) + const meta = await waitJson( + join(output, "ses_test/0001_request_http/meta.json"), + (meta) => meta.transportEnd === "cancelled", + ) + assert.equal(meta.protocolComplete, false) + assert.equal(body.message.content[0].thinking, "Thinking") + assert.equal(body.message.content[1].input, '{"summary":') + assert.equal(body.message.content[2].text, "Partial 🦊") +}) + +test("live WS capture tolerates an incomplete final frame and retains provider errors", async (t) => { + const { output, folder } = await fixture(t) + const records = [ + { + type: "ws.frame", + direction: "request", + timestamp: "2026-09-16", + sequence: 1, + body: { type: "response.create", input: [] }, + }, + { + type: "ws.frame", + direction: "response", + sequence: 2, + body: { type: "error", error: { code: "previous_response_not_found" } }, + }, + { type: "ws.error", message: "socket closed" }, + ] + await writeFile( + join(folder, "a.ws.jsonl"), + records.map((event) => JSON.stringify(event) + "\n").join("") + '{"type":"ws.', + ) + const body = await waitJson(join(output, "ses_test/0001_request_websocket/response.json")) + const meta = await waitJson( + join(output, "ses_test/0001_request_websocket/meta.json"), + (meta) => meta.protocolComplete, + ) + assert.equal(meta.status, "failed") + assert.equal(body.errors[0].code, "previous_response_not_found") +}) + +test("a closed socket publishes partial output without waiting for watcher shutdown", async (t) => { + const { output, folder } = await fixture(t) + const records = [ + { + type: "ws.frame", + direction: "request", + sequence: 1, + body: { type: "response.create", input: [] }, + }, + { + type: "ws.frame", + direction: "response", + body: { + type: "response.output_item.added", + output_index: 0, + item: { type: "message", role: "assistant", content: [] }, + }, + }, + { + type: "ws.frame", + direction: "response", + body: { + type: "response.output_text.delta", + output_index: 0, + delta: "Partial reply", + }, + }, + { type: "ws.close", code: 1006 }, + ] + await writeFile( + join(folder, "a.ws.jsonl"), + records.map((frame) => JSON.stringify(frame) + "\n").join(""), + ) + const body = await waitJson(join(output, "ses_test/0001_request_websocket/response.json")) + assert.equal(body.message.content[0].text, "Partial reply") + const meta = await waitJson( + join(output, "ses_test/0001_request_websocket/meta.json"), + (meta) => meta.transportEnd === "closed", + ) + assert.equal(meta.protocolComplete, false) +}) + +test("overlapping HTTP requests retain their own responses when completion order reverses", async (t) => { + const { output, folder, save } = await fixture(t) + for (const id of ["first", "second"]) { + await save(`${id}.request.json`, { requestID: id, body: { input: id } }) + await save(`${id}.response.json`, { + status: 200, + contentType: "application/json", + }) + } + const session = join(output, "ses_test") + const entries = await waitJson(join(session, "index.json"), (entries) => entries.length === 2) + for (const id of ["second", "first"]) { + const body = { + output: [{ type: "message", content: [{ type: "output_text", text: id }] }], + } + await writeFile(join(folder, `${id}.response.body`), JSON.stringify(body)) + await save(`${id}.end.json`, { status: "completed" }) + const entry = entries.find((entry) => entry.requestID === id) + const result = await waitJson(join(session, entry.directory, "response.json")) + assert.equal(result.message.content[0].text, id) + assert.deepEqual(await waitJson(join(session, entry.directory, "request.json")), { + input: id, + }) + } + const index = await waitJson(join(output, "index.json"), (index) => index.summary.pending === 0) + assert.deepEqual(index.warnings, []) +}) diff --git a/tests/logger/tsconfig.json b/tests/logger/tsconfig.json new file mode 100644 index 00000000..c68c34c2 --- /dev/null +++ b/tests/logger/tsconfig.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2023"], + "moduleResolution": "bundler", + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "sourceMap": true, + "types": ["node"] + }, + "include": ["index.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/tests/logger/v1.ts b/tests/logger/v1.ts new file mode 100644 index 00000000..a7f59606 --- /dev/null +++ b/tests/logger/v1.ts @@ -0,0 +1,42 @@ +import type { Plugin } from "@opencode-ai/plugin" +import { Capture } from "./log.js" + +let users = 0 +let restore: (() => void) | undefined + +export const server: Plugin = async () => { + users++ + if (!restore) { + const original = globalThis.fetch + const capture = new Capture() + const wrapped: typeof fetch = async (input, init) => { + const headers = new Headers( + init?.headers ?? (input instanceof Request ? input.headers : undefined), + ) + const sessionID = headers.get("x-opencode-session") + if (!users || !sessionID) return original(input, init) + const request = new Request(input, init) + const scope = { sessionID } + const id = await capture.request(scope, request) + try { + return await capture.response(scope, id, await original(input, init)) + } catch (error) { + await capture.write(scope, id, "end.json", { type: "http.end", status: "error" }) + throw error + } + } + globalThis.fetch = wrapped + restore = () => { + if (globalThis.fetch === wrapped) globalThis.fetch = original + restore = undefined + } + } + return { + "chat.headers": async (input, output) => { + output.headers["x-opencode-session"] = input.sessionID + }, + dispose: async () => { + if (--users === 0) restore?.() + }, + } +} diff --git a/tests/logger/v2.ts b/tests/logger/v2.ts new file mode 100644 index 00000000..c7af8246 --- /dev/null +++ b/tests/logger/v2.ts @@ -0,0 +1,58 @@ +import type { Plugin } from "@opencode/plugin" +import { randomUUID } from "node:crypto" +import { Capture, type Scope } from "./log.js" + +export const setup: Plugin.Plugin["setup"] = async (ctx) => { + const capture = new Capture( + typeof ctx.options.directory === "string" ? ctx.options.directory : undefined, + ) + const requests = new WeakMap() + const scope = (event: Scope): Scope => ({ + sessionID: event.sessionID, + kind: event.kind, + agent: event.agent, + model: event.model, + }) + + for (const kind of ["context", "compaction", "generate", "title"] as const) { + await ctx.session.hook(kind, async (event) => { + await capture.write( + { ...scope(event), kind: kind === "context" ? "primary" : kind }, + randomUUID(), + "context.json", + { + type: "context", + system: event.system, + messages: event.messages, + options: event.options, + ...("tools" in event ? { tools: event.tools } : {}), + }, + ) + }) + } + await ctx.session.hook("http.request", async (event) => { + requests.set(event.request, await capture.request(scope(event), event.request)) + }) + await ctx.session.hook("http.response", async (event) => { + // Another plugin can replace the Request after our request hook. + const id = + requests.get(event.request) ?? (await capture.request(scope(event), event.request)) + event.response = await capture.response(scope(event), id, event.response) + }) + await ctx.session.hook("experimental.ws.handshake", async (event) => { + const url = event.url + if (typeof ctx.options.relay === "string") { + const relay = new URL(ctx.options.relay) + relay.pathname = "/relay" + relay.searchParams.set("upstream", url) + relay.searchParams.set("session", event.sessionID) + relay.searchParams.set("kind", event.kind) + event.url = relay.href + } + await capture.write(scope(event), randomUUID(), "handshake.json", { + type: "ws.handshake", + url, + relayed: event.url !== url, + }) + }) +} From 5e894b510fecc6c8fcda3ab23306fb5f6b259ec9 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 13:07:07 -0400 Subject: [PATCH 12/15] docs: describe the single-checkout sandbox setup --- CONTRIBUTING.md | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1541d39..e301cd65 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,12 +33,13 @@ npm run build ``` The install flag allows development against the different OpenTUI peer versions -used by OpenCode V1 and V2. +used by OpenCode V1 and V2. This also installs the bundled test logger's +dependencies through the [tests/logger](tests/logger/) npm workspace. Run the checks relevant to your changes before submitting a pull request: ```sh -npm test # Unit tests +npm test # DCP and request-logger tests npm run typecheck # TypeScript validation npm run check:package # Build and validate the npm package npm run format:check # Formatting @@ -77,23 +78,16 @@ separate `tui.json` (panel): ## Manual Sandbox -The sandbox requires Docker, Node/npm, a Codex login, and an -`opencode-request-logger` checkout alongside DCP: - -```text -parent/ - opencode-dynamic-context-pruning/ - opencode-request-logger/ -``` - -From the DCP checkout: +The sandbox requires Docker, Node/npm, and a ChatGPT login through `codex login`. +The request logger is included in [tests/logger](tests/logger/); only the DCP +checkout is needed. Complete [Development Setup](#development-setup), then run: ```sh npm run sandbox # OpenCode V2 npm run sandbox -- --v1 # OpenCode V1 ``` -Each launch rebuilds both plugins and prepares a clean Docker image. Run +Each launch rebuilds DCP and the test logger and prepares a clean Docker image. Run `npm run sandbox -- --help` for available options and defaults. Authentication comes from `~/.codex/auth.json`, or `$CODEX_HOME/auth.json`; `DCP_CODEX_AUTH` overrides the file path. If the token expires, refresh your Codex login and relaunch. @@ -130,7 +124,8 @@ dcp-sandbox ### Request Logs -Each launch has `raw/` and `readable/` directories under its timestamped log folder. +The logger is development-only tooling and is excluded from DCP's published npm +package. Each launch has `raw/` and `readable/` directories under its timestamped log folder. The launcher manages the WebSocket relay and readable-log watcher. Requests appear as they are sent; assembled responses appear when they finish, while the session stays open. `--logs` only shows paths and capture counts. @@ -155,13 +150,8 @@ The containerized lab exercises packed plugins on V1 and V2, including HTTP and WebSocket compression, commands, permissions, concurrent sessions, persistence, and native compaction. It uses a local mock provider without live credentials. -With Docker and the sibling logger checkout available, install its dependencies: - -```sh -npm --prefix ../opencode-request-logger ci --legacy-peer-deps -``` - -Build [tests/lab/Dockerfile](tests/lab/Dockerfile) using the image tag expected by +After [Development Setup](#development-setup), build +[tests/lab/Dockerfile](tests/lab/Dockerfile) using the image tag expected by [scripts/lab.mjs](scripts/lab.mjs), then run: ```sh From 06e1365685c41310724f88f54b1e6041ab53d071 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 13:31:17 -0400 Subject: [PATCH 13/15] feat: reuse matching OpenCode authentication in the sandbox --- scripts/sandbox.mjs | 57 +++++++---------- scripts/sandbox/auth.mjs | 135 +++++++++++++++++++++++++++++++++++++++ scripts/sandbox/run.mjs | 69 +++++++------------- 3 files changed, 182 insertions(+), 79 deletions(-) create mode 100644 scripts/sandbox/auth.mjs diff --git a/scripts/sandbox.mjs b/scripts/sandbox.mjs index 7c3ebe09..70ad61b4 100755 --- a/scripts/sandbox.mjs +++ b/scripts/sandbox.mjs @@ -5,6 +5,7 @@ import { homedir } from "node:os" import { dirname, join, resolve } from "node:path" import { fileURLToPath } from "node:url" import { parseArgs } from "node:util" +import { copyAuth } from "./sandbox/auth.mjs" const repo = resolve(dirname(fileURLToPath(import.meta.url)), "..") const root = resolve(process.env.DCP_SANDBOX_DIR || join(homedir(), ".local/state/dcp-sandbox")) @@ -39,7 +40,7 @@ Sessions and scratch files persist. Both plugins are rebuilt on every launch. --path Print the current sandbox's host directory --update Remember the latest release of the selected major version --opencode VERSION Remember an exact version (V1 requires 1.18.29+) - --model MODEL Remember an OpenAI model (default: openai/gpt-5.6-sol) + --model MODEL Remember a provider/model (otherwise OpenCode selects one) --transport TYPE V2: websocket or http; V1: http only Examples: @@ -50,7 +51,7 @@ Examples: dcp-sandbox -- run --format json "Reply with OK." State: ${root} -Auth: DCP_CODEX_AUTH, or $CODEX_HOME/auth.json, or ~/.codex/auth.json +Auth: saved credentials from the selected OpenCode version; override with DCP_AUTH_PATH Override the state directory with DCP_SANDBOX_DIR.`) process.exit(0) } @@ -93,26 +94,10 @@ async function main() { if (!interactive && cli.length === 0) throw new Error("An interactive terminal is required. For automation, use -- run .") - const authPath = - process.env.DCP_CODEX_AUTH || - join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json") - const auth = json(authPath, null)?.tokens - if (!auth?.access_token || !auth.account_id) { - throw new Error( - `ChatGPT authentication not found in ${authPath}. Sign in with codex login first.`, - ) - } - const claims = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString()) - if (claims.exp * 1000 <= Date.now()) { - throw new Error( - "Codex access token has expired. Refresh your login in Codex, then launch again.", - ) - } const settingsPath = join(state, "settings.json") const settings = json(settingsPath, { version: major === 1 ? "1.18.29" : "2.0.4", - model: "openai/gpt-5.6-sol", - transport: major === 1 ? "http" : "websocket", + transport: major === 1 ? "http" : undefined, }) if (values.update && values.opencode) throw new Error("Choose --update or --opencode, not both.") @@ -143,10 +128,12 @@ async function main() { (Number(version[3]) < 29 || (Number(version[3]) === 29 && version[4])))) ) throw new Error("DCP's shared entrypoint requires OpenCode 1.18.29 or newer.") - if (!settings.model.startsWith("openai/")) - throw new Error("This Codex sandbox requires an openai/ model.") - if (!["websocket", "http"].includes(settings.transport)) + if (settings.model && !/^[^/]+\/.+$/.test(settings.model)) + throw new Error("--model must use provider/model format.") + if (settings.transport && !["websocket", "http"].includes(settings.transport)) throw new Error("--transport must be websocket or http.") + if (major === 2 && settings.transport && !settings.model) + throw new Error("Select --model provider/model when overriding its transport.") if (major === 1 && settings.transport !== "http") throw new Error( "The V1 sandbox uses HTTP so all requests can be logged. Use --transport http.", @@ -197,18 +184,20 @@ async function main() { ) packages.push(Object.values(packed)[0].filename) } - save(settingsPath, settings) - save(current, profile) - save(join(home, "latest.json"), stamp) - save(join(input, "launch.json"), { ...settings, major, packages, stamp, args: cli }) - const token = join(input, "auth.json") - save(token, { access: auth.access_token, account: auth.account_id }) - console.log(`OpenCode ${settings.version} · ${settings.model} · ${settings.transport}`) - console.log(`Workspace: ${join(home, "project")}`) - console.log(`DCP config: ${join(home, "home/config/opencode/dcp.jsonc")}`) - console.log(`Readable logs: ${join(home, "logs", stamp, "readable")}`) - console.log(`Raw logs: ${join(home, "logs", stamp, "raw")}`) + const auth = join(input, "auth.json") try { + copyAuth(major, auth, image) + save(settingsPath, settings) + save(current, profile) + save(join(home, "latest.json"), stamp) + save(join(input, "launch.json"), { ...settings, major, packages, stamp, args: cli }) + console.log( + `OpenCode ${settings.version} · ${settings.model || "default model"} · ${settings.transport || "provider transport"}`, + ) + console.log(`Workspace: ${join(home, "project")}`) + console.log(`DCP config: ${join(home, "home/config/opencode/dcp.jsonc")}`) + console.log(`Readable logs: ${join(home, "logs", stamp, "readable")}`) + console.log(`Raw logs: ${join(home, "logs", stamp, "raw")}`) const child = spawn( "docker", [ @@ -247,7 +236,7 @@ async function main() { process.off("SIGINT", stop) } } finally { - rmSync(token, { force: true }) + rmSync(auth, { force: true }) } } diff --git a/scripts/sandbox/auth.mjs b/scripts/sandbox/auth.mjs new file mode 100644 index 00000000..2e87ce78 --- /dev/null +++ b/scripts/sandbox/auth.mjs @@ -0,0 +1,135 @@ +import { execFileSync } from "node:child_process" +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs" +import { homedir } from "node:os" +import { basename, dirname, isAbsolute, join, resolve } from "node:path" +import { fileURLToPath } from "node:url" + +const directory = dirname(fileURLToPath(import.meta.url)) + +function dataPath(env) { + return join(env.XDG_DATA_HOME || join(env.HOME || homedir(), ".local/share"), "opencode") +} + +export function authPath(major, env = process.env) { + if (env.DCP_AUTH_PATH) return resolve(env.DCP_AUTH_PATH) + if (major === 1) return join(dataPath(env), "auth.json") + try { + return execFileSync("opencode2", ["debug", "paths", "db"], { + env, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim() + } catch (error) { + if (error.code !== "ENOENT") throw error + } + const database = env.OPENCODE_DB || "opencode.db" + return isAbsolute(database) ? database : join(dataPath(env), database) +} + +export function copyAuth(major, destination, image) { + const source = authPath(major) + if (!existsSync(source)) + throw new Error( + `OpenCode V${major} authentication not found at ${source}. Sign in with OpenCode or set DCP_AUTH_PATH.`, + ) + mkdirSync(dirname(destination), { recursive: true, mode: 0o700 }) + if (major === 1) { + writeFileSync(destination, readFileSync(source), { mode: 0o600 }) + return + } + // Only this short-lived exporter sees the host database, mounted read-only. + // The sandbox receives credential records, never the host session database. + execFileSync( + "docker", + [ + "run", + "--rm", + "--user", + `${process.getuid()}:${process.getgid()}`, + "--mount", + `type=bind,source=${dirname(source)},target=/source,readonly`, + "--mount", + `type=bind,source=${dirname(destination)},target=/export`, + "--mount", + `type=bind,source=${directory},target=/launcher,readonly`, + image, + "node", + "--disable-warning=ExperimentalWarning", + "/launcher/auth.mjs", + join("/source", basename(source)), + join("/export", basename(destination)), + ], + { stdio: ["ignore", "ignore", "pipe"] }, + ) +} + +export async function exportAuth(source, destination) { + const { DatabaseSync } = await import("node:sqlite") + const db = new DatabaseSync(source, { readOnly: true }) + try { + // A read transaction keeps credentials and provider discovery consistent, + // including committed WAL entries, without copying conversation data. + db.exec("BEGIN") + const credentials = db.prepare("SELECT * FROM credential").all() + const sources = db.prepare("SELECT value FROM kv WHERE key = ?").get("wellknown:sources") + db.exec("COMMIT") + writeFileSync(destination, JSON.stringify({ credentials, sources }), { mode: 0o600 }) + } finally { + db.close() + } +} + +export async function restoreAuth(major, source, cli, env = process.env) { + const data = dataPath(env) + mkdirSync(data, { recursive: true, mode: 0o700 }) + if (major === 1) { + writeFileSync(join(data, "auth.json"), readFileSync(source), { mode: 0o600 }) + return + } + // Let this OpenCode release initialize/migrate its own isolated database, + // then import authentication while its private server is stopped. + execFileSync(cli, ["auth", "list", "--standalone", "--format", "json"], { + env, + cwd: env.PWD, + stdio: ["ignore", "pipe", "pipe"], + timeout: 60000, + }) + const database = execFileSync(cli, ["debug", "paths", "db"], { + env, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim() + const auth = JSON.parse(readFileSync(source, "utf8")) + await importAuth(database, auth) +} + +export async function importAuth(database, auth) { + const { DatabaseSync } = await import("node:sqlite") + const db = new DatabaseSync(database) + try { + db.exec("BEGIN IMMEDIATE") + db.exec("DELETE FROM credential") + for (const credential of auth.credentials) { + const columns = Object.keys(credential) + db.prepare( + `INSERT INTO credential (${columns.map((name) => `"${name.replaceAll('"', '""')}"`).join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, + ).run(...Object.values(credential)) + } + db.prepare("DELETE FROM kv WHERE key = ?").run("wellknown:sources") + if (auth.sources) { + const now = Date.now() + db.prepare( + "INSERT INTO kv (key, value, time_created, time_updated) VALUES (?, ?, ?, ?)", + ).run("wellknown:sources", auth.sources.value, now, now) + } + db.exec("COMMIT") + } finally { + // Closing on failure rolls back rather than leaving partial credentials. + db.close() + } +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + process.umask(0o077) + await exportAuth(process.argv[2], process.argv[3]) +} diff --git a/scripts/sandbox/run.mjs b/scripts/sandbox/run.mjs index 54af7668..0621dc6d 100644 --- a/scripts/sandbox/run.mjs +++ b/scripts/sandbox/run.mjs @@ -2,10 +2,10 @@ import { execFileSync, spawn } from "node:child_process" import { mkdir, readFile, writeFile } from "node:fs/promises" import { join } from "node:path" import { pathToFileURL } from "node:url" +import { restoreAuth } from "./auth.mjs" process.umask(0o077) const launch = JSON.parse(await readFile("/input/launch.json", "utf8")) -const auth = JSON.parse(await readFile("/input/auth.json", "utf8")) try { execFileSync( "npm", @@ -32,6 +32,18 @@ const { watch } = await import(pathToFileURL(join(logger, "readable.mjs"))) const logs = join("/lab/logs", launch.stamp) const raw = join(logs, "raw") const config = process.env.OPENCODE_CONFIG_DIR +const cli = `/opt/opencode/node_modules/.bin/${launch.major === 1 ? "opencode" : "opencode2"}` +const separator = launch.model?.indexOf("/") +const providers = + launch.model && launch.transport + ? { + [launch.model.slice(0, separator)]: { + models: { + [launch.model.slice(separator + 1)]: { transport: launch.transport }, + }, + }, + } + : undefined await Promise.all([raw, config, "/lab/project"].map((path) => mkdir(path, { recursive: true }))) const readable = await watch(raw, join(logs, "readable")) const relay = createRelay({ directory: raw }) @@ -48,18 +60,6 @@ try { small_model: launch.model, plugin: [dcp, logger], permission: { compress: "allow" }, - provider: { - openai: { - options: { - baseURL: "https://chatgpt.com/backend-api/codex", - apiKey: "{env:DCP_TOKEN}", - headers: { - "chatgpt-account-id": "{env:DCP_ACCOUNT}", - originator: "opencode", - }, - }, - }, - }, } : { $schema: "https://opencode.ai/config.json", @@ -76,23 +76,7 @@ try { }, ], permissions: [{ action: "compress", resource: "*", effect: "allow" }], - providers: { - openai: { - settings: { - baseURL: "https://chatgpt.com/backend-api/codex", - apiKey: "{env:DCP_TOKEN}", - }, - headers: { - "chatgpt-account-id": "{env:DCP_ACCOUNT}", - originator: "opencode", - }, - models: { - [launch.model.slice("openai/".length)]: { - transport: launch.transport, - }, - }, - }, - }, + providers, }, null, 2, @@ -129,22 +113,17 @@ try { } catch (error) { if (error.code !== "EEXIST") throw error } - const child = spawn( - `/opt/opencode/node_modules/.bin/${launch.major === 1 ? "opencode" : "opencode2"}`, - [...launch.args, ...(launch.major === 1 ? [] : ["--standalone"])], - { - cwd: "/lab/project", - stdio: "inherit", - env: { - ...process.env, - DCP_TOKEN: auth.access, - DCP_ACCOUNT: auth.account, - OPENCODE_LOG_LEVEL: "DEBUG", - ...(launch.major === 1 ? { OPENCODE_EXPERIMENTAL_WEBSOCKETS: "false" } : {}), - REQUEST_LOG_DIR: raw, - }, + await restoreAuth(launch.major, "/input/auth.json", cli) + const child = spawn(cli, [...launch.args, ...(launch.major === 1 ? [] : ["--standalone"])], { + cwd: "/lab/project", + stdio: "inherit", + env: { + ...process.env, + OPENCODE_LOG_LEVEL: "DEBUG", + ...(launch.major === 1 ? { OPENCODE_EXPERIMENTAL_WEBSOCKETS: "false" } : {}), + REQUEST_LOG_DIR: raw, }, - ) + }) const stop = () => child.kill("SIGTERM") process.on("SIGTERM", stop) process.on("SIGINT", stop) From 3bacbb3ef9fe66b8d4dc7fa130b90fdd593f727f Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 13:31:17 -0400 Subject: [PATCH 14/15] test: cover native authentication copying and provider requests --- scripts/lab.mjs | 64 ++++++------ tests/lab/auth.mjs | 249 +++++++++++++++++++++++++++++++++++++++++++++ tests/lab/live.mjs | 39 +++---- tests/lab/run.mjs | 2 + 4 files changed, 297 insertions(+), 57 deletions(-) create mode 100644 tests/lab/auth.mjs diff --git a/scripts/lab.mjs b/scripts/lab.mjs index db73578e..31c323fd 100644 --- a/scripts/lab.mjs +++ b/scripts/lab.mjs @@ -1,8 +1,8 @@ import { execFileSync } from "node:child_process" -import { mkdirSync, readFileSync, writeFileSync } from "node:fs" -import { homedir } from "node:os" +import { mkdirSync, rmSync } from "node:fs" import { dirname, join, resolve } from "node:path" import { fileURLToPath } from "node:url" +import { copyAuth } from "./sandbox/auth.mjs" const repo = resolve(dirname(fileURLToPath(import.meta.url)), "..") const root = @@ -13,18 +13,6 @@ const runtime = join(root, "runtime") const live = process.argv.includes("--live") mkdirSync(artifacts, { recursive: true, mode: 0o700 }) mkdirSync(runtime, { recursive: true, mode: 0o700 }) -if (live) { - const auth = JSON.parse(readFileSync(join(homedir(), ".codex/auth.json"), "utf8")) - if (!auth.tokens?.access_token || !auth.tokens.account_id) - throw new Error("Live tests require existing Codex authentication") - const data = join(runtime, "live") - mkdirSync(data, { recursive: true, mode: 0o700 }) - writeFileSync( - join(data, "auth.json"), - JSON.stringify({ access: auth.tokens.access_token, account: auth.tokens.account_id }), - { mode: 0o600 }, - ) -} execFileSync("npm", ["pack", "--pack-destination", artifacts], { cwd: join(repo, "tests/logger"), stdio: "pipe", @@ -37,23 +25,31 @@ execFileSync("npm", ["pack", "--ignore-scripts", "--pack-destination", artifacts stdio: "pipe", }) console.log(`Lab output: ${root}`) -execFileSync( - "docker", - [ - "run", - "--rm", - "--init", - "--user", - `${process.getuid()}:${process.getgid()}`, - "--mount", - `type=bind,source=${runtime},target=/lab`, - "--mount", - `type=bind,source=${artifacts},target=/artifacts,readonly`, - "--mount", - `type=bind,source=${join(repo, "tests/lab")},target=/test,readonly`, - "dcp-lab:2.0.4", - "node", - live ? "/test/live.mjs" : "/test/run.mjs", - ], - { stdio: "inherit" }, -) +const auth = join(artifacts, "auth.json") +try { + if (live) copyAuth(2, auth, "dcp-lab:2.0.4") + execFileSync( + "docker", + [ + "run", + "--rm", + "--init", + "--user", + `${process.getuid()}:${process.getgid()}`, + "--mount", + `type=bind,source=${runtime},target=/lab`, + "--mount", + `type=bind,source=${artifacts},target=/artifacts,readonly`, + "--mount", + `type=bind,source=${join(repo, "tests/lab")},target=/test,readonly`, + "--mount", + `type=bind,source=${join(repo, "scripts/sandbox")},target=/sandbox,readonly`, + "dcp-lab:2.0.4", + "node", + live ? "/test/live.mjs" : "/test/run.mjs", + ], + { stdio: "inherit" }, + ) +} finally { + rmSync(auth, { force: true }) +} diff --git a/tests/lab/auth.mjs b/tests/lab/auth.mjs new file mode 100644 index 00000000..90cc435e --- /dev/null +++ b/tests/lab/auth.mjs @@ -0,0 +1,249 @@ +import assert from "node:assert/strict" +import { createHash } from "node:crypto" +import { mkdir, readFile, stat, writeFile } from "node:fs/promises" +import { createServer } from "node:http" +import { join } from "node:path" +import { DatabaseSync } from "node:sqlite" +import { authPath, copyAuth, exportAuth, importAuth, restoreAuth } from "/sandbox/auth.mjs" +import { run } from "./process.mjs" +import { events } from "./mock.mjs" + +export async function authentication() { + const root = "/lab/auth-checks" + await mkdir(root, { recursive: true }) + const env = { + ...process.env, + HOME: root, + PWD: root, + XDG_CONFIG_HOME: join(root, "config"), + XDG_DATA_HOME: join(root, "data"), + XDG_STATE_HOME: join(root, "state"), + XDG_CACHE_HOME: join(root, "cache"), + OPENCODE_CONFIG_DIR: join(root, "config/opencode"), + } + const v1Env = { ...env, XDG_DATA_HOME: join(root, "v1") } + assert.equal( + authPath(1, { HOME: root, CODEX_HOME: "/unused" }), + join(root, ".local/share/opencode/auth.json"), + ) + assert.equal( + authPath(2, { HOME: root, PATH: "/missing", OPENCODE_DB: "custom.db" }), + join(root, ".local/share/opencode/custom.db"), + ) + + const headers = [] + const server = createServer((request, response) => { + request.resume() + if (request.url.endsWith("/.well-known/opencode")) { + response.writeHead(200, { "Content-Type": "application/json" }) + response.end(JSON.stringify({ config: {} })) + return + } + headers.push(request.headers.authorization) + response.writeHead(200, { "Content-Type": "text/event-stream" }) + for (const event of events("AUTH_COPY_OK")) + response.write(`data: ${JSON.stringify(event)}\n\n`) + response.end() + }) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const origin = `http://127.0.0.1:${server.address().port}` + const baseURL = `${origin}/v1` + try { + const legacy = { + lab: { type: "api", key: "fake-key" }, + openai: { type: "api", key: "fake-openai-key" }, + anthropic: { + type: "oauth", + access: "fake-access", + refresh: "fake-refresh", + expires: Date.now() + 3600000, + }, + [origin]: { type: "wellknown", key: "EXAMPLE_TOKEN", token: "fake-token" }, + } + const file = join(root, "v1-auth.json") + await writeFile(file, JSON.stringify(legacy), { mode: 0o600 }) + const previous = process.env.DCP_AUTH_PATH + process.env.DCP_AUTH_PATH = file + const input = join(root, "input.json") + try { + copyAuth(1, input) + } finally { + if (previous === undefined) delete process.env.DCP_AUTH_PATH + else process.env.DCP_AUTH_PATH = previous + } + await restoreAuth(1, input, "/opt/v1/node_modules/.bin/opencode", v1Env) + assert.deepEqual( + JSON.parse(await readFile(join(v1Env.XDG_DATA_HOME, "opencode/auth.json"), "utf8")), + legacy, + ) + assert.equal((await stat(input)).mode & 0o777, 0o600) + + const cli = "/opt/v2/node_modules/.bin/opencode2" + await run(cli, ["auth", "list", "--standalone", "--format", "json"], { env, cwd: root }) + const path = (await run(cli, ["debug", "paths", "db"], { env, cwd: root })).trim() + const source = new DatabaseSync(path) + source.exec("CREATE TABLE private_history (text TEXT)") + source.prepare("INSERT INTO private_history VALUES (?)").run("PRIVATE_SESSION_TEXT") + const credentials = [ + { + id: "cred_key", + integration: "lab", + active: 1, + value: { type: "key", key: "fake-key" }, + }, + { + id: "cred_old", + integration: "openai", + active: 0, + value: { type: "key", key: "fake-inactive-key" }, + }, + { + id: "cred_oauth", + integration: "openai", + active: 1, + value: { + type: "oauth", + methodID: "chatgpt-browser", + access: "fake-access", + refresh: "fake-refresh", + expires: Date.now() + 3600000, + metadata: { accountID: "fake-account" }, + }, + }, + ] + for (const entry of credentials) + source + .prepare( + "INSERT INTO credential (id, integration_id, label, value, active, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", + ) + .run( + entry.id, + entry.integration, + entry.id, + JSON.stringify(entry.value), + entry.active, + 1, + 1, + ) + source + .prepare("INSERT INTO kv VALUES (?, ?, ?, ?)") + .run("wellknown:sources", JSON.stringify([origin]), 1, 1) + source + .prepare("INSERT INTO kv VALUES (?, ?, ?, ?)") + .run("private-setting", '"PRIVATE_CONFIG"', 1, 1) + const rows = source.prepare("SELECT * FROM credential").all() + const hash = () => + readFile(path).then((data) => createHash("sha256").update(data).digest("hex")) + const before = await hash() + await exportAuth(path, input) + assert.equal(await hash(), before) + assert.deepEqual(source.prepare("SELECT * FROM credential").all(), rows) + source.close() + const serialized = await readFile(input, "utf8") + assert.ok( + !serialized.includes("PRIVATE_SESSION_TEXT") && !serialized.includes("PRIVATE_CONFIG"), + ) + const exported = JSON.parse(serialized) + assert.deepEqual( + exported.credentials, + rows.map((row) => ({ ...row })), + ) + + const targetEnv = { ...env, XDG_DATA_HOME: join(root, "target") } + await restoreAuth(2, input, cli, targetEnv) + const targetPath = ( + await run(cli, ["debug", "paths", "db"], { env: targetEnv, cwd: root }) + ).trim() + const target = new DatabaseSync(targetPath) + assert.deepEqual(target.prepare("SELECT * FROM credential").all(), rows) + assert.equal( + target.prepare("SELECT name FROM sqlite_master WHERE name = 'private_history'").get(), + undefined, + ) + assert.equal( + target.prepare("SELECT value FROM kv WHERE key = 'wellknown:sources'").get().value, + JSON.stringify([origin]), + ) + target.prepare("INSERT INTO kv VALUES (?, ?, ?, ?)").run("sandbox-setting", '"KEEP"', 1, 1) + target.close() + await assert.rejects( + importAuth(targetPath, { + credentials: [exported.credentials[0], exported.credentials[0]], + }), + ) + const restored = new DatabaseSync(targetPath) + assert.deepEqual(restored.prepare("SELECT * FROM credential").all(), rows) + assert.equal( + restored.prepare("SELECT value FROM kv WHERE key = 'sandbox-setting'").get().value, + '"KEEP"', + ) + restored.close() + await mkdir(env.OPENCODE_CONFIG_DIR, { recursive: true }) + for (const major of [1, 2]) { + await writeFile( + join(env.OPENCODE_CONFIG_DIR, "opencode.json"), + JSON.stringify( + major === 1 + ? { + autoupdate: false, + model: "lab/gpt-5.4", + small_model: "lab/gpt-5.4", + provider: { + lab: { + npm: "@ai-sdk/openai", + options: { baseURL }, + models: { + "gpt-5.4": { limit: { context: 200000, output: 32000 } }, + }, + }, + }, + } + : { + update: "disable", + model: "lab/gpt-5.4", + agents: { title: { model: "lab/gpt-5.4" } }, + providers: { + lab: { + package: "@opencode/ai/providers/openai/responses", + env: ["LAB_AUTH_KEY"], + settings: { baseURL }, + models: { + "gpt-5.4": { + transport: "http", + limit: { context: 200000, output: 32000 }, + }, + }, + }, + }, + }, + ), + ) + const start = headers.length + const output = await run( + major === 1 ? "/opt/v1/node_modules/.bin/opencode" : cli, + [ + "run", + ...(major === 2 ? ["--standalone"] : []), + "--format", + "json", + "Reply AUTH_COPY_OK.", + ], + { env: major === 1 ? v1Env : targetEnv, cwd: root }, + ) + assert.ok(output.includes("AUTH_COPY_OK")) + assert.ok(headers.length > start) + assert.ok(headers.slice(start).every((header) => header === "Bearer fake-key")) + } + } finally { + await new Promise((resolve) => server.close(resolve)) + } + return { + v1Auth: true, + v2Auth: true, + accounts: true, + sourceReadOnly: true, + sessionsExcluded: true, + rollback: true, + providerAuth: true, + } +} diff --git a/tests/lab/live.mjs b/tests/lab/live.mjs index eb6c7635..a49fcad8 100644 --- a/tests/lab/live.mjs +++ b/tests/lab/live.mjs @@ -4,6 +4,7 @@ import { join } from "node:path" import { pathToFileURL } from "node:url" import { inspect } from "./inspect.mjs" import { run } from "./process.mjs" +import { restoreAuth } from "/sandbox/auth.mjs" await run("npm", [ "install", @@ -18,9 +19,20 @@ const logger = "/lab/plugins/node_modules/opencode-request-logger" const dcp = "/lab/plugins/node_modules/@tarquinen/opencode-dcp" const { createRelay } = await import(pathToFileURL(join(logger, "relay.mjs"))) const root = "/lab/live" -const auth = JSON.parse(await readFile(join(root, "auth.json"), "utf8")) const directory = join(root, "project") const config = join(root, "config/opencode") +const cli = "/opt/v2/node_modules/.bin/opencode2" +const env = { + ...process.env, + HOME: root, + PWD: directory, + XDG_CONFIG_HOME: join(root, "config"), + XDG_DATA_HOME: join(root, "data"), + XDG_STATE_HOME: join(root, "state"), + XDG_CACHE_HOME: join(root, "cache"), + OPENCODE_CONFIG_DIR: config, + OPENCODE_LOG_LEVEL: "DEBUG", +} await Promise.all([directory, config].map((path) => mkdir(path, { recursive: true }))) for (const transport of ["http", "websocket"]) { const logs = join(root, transport, "logs") @@ -46,14 +58,6 @@ for (const transport of ["http", "websocket"]) { permissions: [{ action: "compress", resource: "*", effect: "allow" }], providers: { openai: { - settings: { - baseURL: "https://chatgpt.com/backend-api/codex", - apiKey: "{env:DCP_TOKEN}", - }, - headers: { - "chatgpt-account-id": "{env:DCP_ACCOUNT}", - originator: "opencode", - }, models: { "gpt-5.6-sol": { transport } }, }, }, @@ -63,27 +67,16 @@ for (const transport of ["http", "websocket"]) { join(config, "dcp.json"), JSON.stringify({ autoUpdate: false, debug: true, pruneNotification: "off" }), ) + if (transport === "http") await restoreAuth(2, "/artifacts/auth.json", cli, env) const prompt = "LIVE_RAW_PAYLOAD: We are testing DCP in an isolated environment. Everything in this user message is disposable test content. Call compress exactly once on this message using its injected message ID, with summary 'DCP_LIVE_SUMMARY: disposable test fixture. Compression is done; reply with exactly DCP_LIVE_OK and do not use more tools.' After the tool completes, reply with exactly DCP_LIVE_OK. Do not use other tools." const output = await run( - "/opt/v2/node_modules/.bin/opencode2", + cli, ["run", "--standalone", "--format", "json", "--model", "openai/gpt-5.6-sol", prompt], { cwd: directory, record: join(root, transport, "run"), - env: { - ...process.env, - DCP_TOKEN: auth.access, - DCP_ACCOUNT: auth.account, - HOME: root, - PWD: directory, - XDG_CONFIG_HOME: join(root, "config"), - XDG_DATA_HOME: join(root, "data"), - XDG_STATE_HOME: join(root, "state"), - XDG_CACHE_HOME: join(root, "cache"), - OPENCODE_CONFIG_DIR: config, - OPENCODE_LOG_LEVEL: "DEBUG", - }, + env, }, ) const { captures, summary } = await inspect(logs) diff --git a/tests/lab/run.mjs b/tests/lab/run.mjs index 1e0ac610..6a404dde 100644 --- a/tests/lab/run.mjs +++ b/tests/lab/run.mjs @@ -7,6 +7,7 @@ import { createMock } from "./mock.mjs" import { inspect } from "./inspect.mjs" import { run } from "./process.mjs" import { commands } from "./api.mjs" +import { authentication } from "./auth.mjs" await run("npm", [ "install", @@ -17,6 +18,7 @@ await run("npm", [ "/artifacts/opencode-request-logger-0.1.0.tgz", "/artifacts/tarquinen-opencode-dcp-3.1.15.tgz", ]) +console.log(JSON.stringify(await authentication())) const logger = "/lab/plugins/node_modules/opencode-request-logger" const dcp = "/lab/plugins/node_modules/@tarquinen/opencode-dcp" const require = createRequire(join(logger, "package.json")) From 408379ec69d88db69ded958c7b9984b51fe49ae6 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 16 Sep 2026 13:31:17 -0400 Subject: [PATCH 15/15] docs: describe saved OpenCode authentication in the sandbox --- CONTRIBUTING.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e301cd65..90b3af99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,7 +78,7 @@ separate `tui.json` (panel): ## Manual Sandbox -The sandbox requires Docker, Node/npm, and a ChatGPT login through `codex login`. +The sandbox requires Docker, Node/npm, and saved OpenCode authentication. The request logger is included in [tests/logger](tests/logger/); only the DCP checkout is needed. Complete [Development Setup](#development-setup), then run: @@ -88,9 +88,18 @@ npm run sandbox -- --v1 # OpenCode V1 ``` Each launch rebuilds DCP and the test logger and prepares a clean Docker image. Run -`npm run sandbox -- --help` for available options and defaults. Authentication comes -from `~/.codex/auth.json`, or `$CODEX_HOME/auth.json`; `DCP_CODEX_AUTH` overrides the -file path. If the token expires, refresh your Codex login and relaunch. +`npm run sandbox -- --help` for available options and defaults. Each launch copies +all saved authentication from the matching host version: V1's `auth.json`, or V2's +credential records and account selections. OpenCode handles provider authentication +normally inside the container; copied credentials can be refreshed there without +writing back to the host. + +V1's auth file is under `$XDG_DATA_HOME/opencode` (normally +`~/.local/share/opencode`). V2's database is located with `opencode2 debug paths db`, +or the standard data directory when that command is unavailable. Set `DCP_AUTH_PATH` +to select a different V1 auth file or V2 database. Credentials embedded in host +configuration or environment variables are not copied. Custom provider definitions +can be added to `opencode.json` in the sandbox's scratch workspace. The sandbox has its own sessions, scratch workspace, and configuration under `~/.local/state/dcp-sandbox/`. V1 uses the `v1/` subdirectory, with a separate @@ -105,10 +114,12 @@ npm run sandbox -- -- --continue # Resume a session npm run sandbox -- --update # Remember the latest release npm run sandbox -- --opencode VERSION # Pin a release to test npm run sandbox -- --transport http # Select V2's transport -npm run sandbox -- --model openai/MODEL # Select a model available to your account +npm run sandbox -- --model PROVIDER/MODEL # Select a model available to your account ``` -Replace `VERSION` and `MODEL` with the release and model you want to test. +Replace `VERSION`, `PROVIDER`, and `MODEL` with the release and model you want to test. +Without a saved model choice, OpenCode selects its default. A V2 transport override +applies to the selected model. Add `--v1` to manage the V1 sandbox. Model, transport, and version choices persist; updates are explicit. `--fresh` selects a new profile for subsequent launches. You can edit `dcp.jsonc` and CLI preferences; `opencode.json` is launcher-managed. @@ -146,9 +157,9 @@ original HTTP bytes, and WebSocket frames remain available in `raw/`. ## Integration Tests -The containerized lab exercises packed plugins on V1 and V2, including HTTP and -WebSocket compression, commands, permissions, concurrent sessions, persistence, -and native compaction. It uses a local mock provider without live credentials. +The containerized lab exercises packed plugins on V1 and V2, including saved-auth +copying, HTTP and WebSocket compression, commands, permissions, concurrent sessions, +persistence, and native compaction. It uses a local mock provider without live credentials. After [Development Setup](#development-setup), build [tests/lab/Dockerfile](tests/lab/Dockerfile) using the image tag expected by @@ -161,7 +172,8 @@ node scripts/lab.mjs The runner prints its output directory under `/tmp/opencode/dcp-lab/`. Set `DCP_LAB_DIR` to override it. Add `--built` to reuse an existing DCP build. For real-provider checks, `node scripts/lab.mjs --live` uses the current build and -Codex authentication from `~/.codex/auth.json`. +saved V2 authentication. Its OpenAI Responses scenarios require access to the model +configured in [tests/lab/live.mjs](tests/lab/live.mjs). Inspect capture summaries without opening large transcripts: