From a59a925a98863a774dd22b857abec56bebe7abb3 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:43:16 +0200 Subject: [PATCH 01/15] refactor: extract shared sync plugin core for v1/v2 reuse --- src/index.ts | 226 ++---------------------------------- src/shared.ts | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+), 219 deletions(-) create mode 100644 src/shared.ts diff --git a/src/index.ts b/src/index.ts index 8ff8034..7abd74b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,120 +1,20 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import type { Plugin } from '@opencode-ai/plugin'; import { tool } from '@opencode-ai/plugin'; +import { + disableMcpServerForResolutionFailure, + executeSyncCommand, + loadCommands, + type SyncToolArgs, +} from './shared.js'; import { applyOverridesToRuntimeConfig, EnvPlaceholderResolutionError, - hasOwn, - isPlainObject, loadOverrides, } from './sync/config.js'; -import { SyncCommandError, SyncConfigMissingError } from './sync/errors.js'; import { resolveSyncLocations } from './sync/paths.js'; import { createSyncService } from './sync/service.js'; -interface CommandFrontmatter { - description?: string; - agent?: string; - model?: string; - subtask?: boolean; -} - -interface ParsedCommand { - name: string; - frontmatter: CommandFrontmatter; - template: string; -} - -function parseFrontmatter(content: string): { frontmatter: CommandFrontmatter; body: string } { - const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; - const match = content.match(frontmatterRegex); - - if (!match) { - return { frontmatter: {}, body: content.trim() }; - } - - const [, yamlContent, body] = match; - const frontmatter: CommandFrontmatter = {}; - - for (const line of yamlContent.split('\n')) { - const colonIndex = line.indexOf(':'); - if (colonIndex === -1) continue; - - const key = line.slice(0, colonIndex).trim(); - const value = line.slice(colonIndex + 1).trim(); - - if (key === 'description') frontmatter.description = value; - if (key === 'agent') frontmatter.agent = value; - if (key === 'model') frontmatter.model = value; - if (key === 'subtask') frontmatter.subtask = value === 'true'; - } - - return { frontmatter, body: body.trim() }; -} - -function getModuleDir(): string { - // Works in both Bun and Node.js - if (typeof import.meta.dir === 'string') { - return import.meta.dir; - } - // Node.js fallback - return path.dirname(fileURLToPath(import.meta.url)); -} - -async function scanMdFiles(dir: string): Promise { - const files: string[] = []; - - async function walk(currentDir: string): Promise { - const entries = await fs.readdir(currentDir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(currentDir, entry.name); - if (entry.isDirectory()) { - await walk(fullPath); - } else if (entry.isFile() && entry.name.endsWith('.md')) { - files.push(fullPath); - } - } - } - - await walk(dir); - return files; -} - -async function loadCommands(): Promise { - const commands: ParsedCommand[] = []; - const commandDir = path.join(getModuleDir(), 'command'); - - try { - const stats = await fs.stat(commandDir); - if (!stats.isDirectory()) { - return commands; - } - } catch { - return commands; - } - - const files = await scanMdFiles(commandDir); - for (const file of files) { - try { - const content = await fs.readFile(file, 'utf-8'); - const { frontmatter, body } = parseFrontmatter(content); - const relativePath = path.relative(commandDir, file); - const name = relativePath.replace(/\.md$/, '').replace(/\//g, '-'); - - commands.push({ - name, - frontmatter, - template: body, - }); - } catch {} - } - - return commands; -} - export const opencodeConfigSync: Plugin = async (ctx) => { const commands = await loadCommands(); const service = createSyncService(ctx); @@ -192,95 +92,7 @@ export const opencodeConfigSync: Plugin = async (ctx) => { .describe('Bootstrap remote Turso sessions from local session DB before switching backend'), }, async execute(args) { - try { - if (args.command === 'status') { - return await service.status(); - } - if (args.command === 'init') { - return await service.init({ - repo: args.repo, - owner: args.owner, - name: args.name, - url: args.url, - branch: args.branch, - includeSecrets: args.includeSecrets, - includeMcpSecrets: args.includeMcpSecrets, - includeSessions: args.includeSessions, - sessionBackend: args.sessionBackend, - includePromptStash: args.includePromptStash, - includeModelFavorites: args.includeModelFavorites, - setupTurso: args.setupTurso, - migrateSessions: args.migrateSessions, - includeOpencodeSkills: args.includeOpencodeSkills, - includeAgentsDir: args.includeAgentsDir, - create: args.create, - private: args.private, - extraSecretPaths: args.extraSecretPaths, - extraConfigPaths: args.extraConfigPaths, - localRepoPath: args.localRepoPath, - acknowledgePrivateRemote: args.acknowledgePrivateRemote, - }); - } - if (args.command === 'link') { - return await service.link({ - repo: args.repo ?? args.name, - branch: args.branch, - acknowledgePrivateRemote: args.acknowledgePrivateRemote, - }); - } - if (args.command === 'pull') { - return await service.pull(); - } - if (args.command === 'push') { - return await service.push(); - } - if (args.command === 'secrets-pull') { - return await service.secretsPull(); - } - if (args.command === 'secrets-push') { - return await service.secretsPush(); - } - if (args.command === 'secrets-status') { - return await service.secretsStatus(); - } - if (args.command === 'enable-secrets') { - return await service.enableSecrets({ - extraSecretPaths: args.extraSecretPaths, - includeMcpSecrets: args.includeMcpSecrets, - acknowledgePrivateRemote: args.acknowledgePrivateRemote, - }); - } - if (args.command === 'sessions-backend') { - return await service.sessionsBackend({ - backend: args.sessionBackend, - setupTurso: args.setupTurso, - migrateSessions: args.migrateSessions, - }); - } - if (args.command === 'sessions-setup-turso') { - return await service.sessionsSetupTurso({ - forceTokenRefresh: args.setupTurso, - }); - } - if (args.command === 'sessions-migrate-turso') { - return await service.sessionsMigrateTurso({ - setupTurso: args.setupTurso, - }); - } - if (args.command === 'sessions-cleanup-git') { - return await service.sessionsCleanupGit(); - } - if (args.command === 'resolve') { - return await service.resolve(); - } - - return 'Unknown command.'; - } catch (error) { - if (error instanceof SyncConfigMissingError || error instanceof SyncCommandError) { - return error.message; - } - return formatError(error); - } + return await executeSyncCommand(service, args as SyncToolArgs); }, }); @@ -336,27 +148,3 @@ export const opencodeConfigSync: Plugin = async (ctx) => { export const opencodeSynced = opencodeConfigSync; export default opencodeConfigSync; - -function disableMcpServerForResolutionFailure( - config: Record, - fieldPath: readonly string[] -): void { - if (fieldPath[0] !== 'overrides' || fieldPath[1] !== 'mcp') return; - const serverName = fieldPath[2]; - if (!serverName) return; - - const mcp = isPlainObject(config.mcp) ? config.mcp : null; - if (!mcp || !hasOwn(mcp, serverName) || !isPlainObject(mcp[serverName])) return; - - Object.defineProperty(mcp[serverName], 'enabled', { - value: false, - enumerable: true, - configurable: true, - writable: true, - }); -} - -function formatError(error: unknown): string { - if (error instanceof Error) return error.message; - return String(error); -} diff --git a/src/shared.ts b/src/shared.ts new file mode 100644 index 0000000..0003cbf --- /dev/null +++ b/src/shared.ts @@ -0,0 +1,314 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { SyncCommandError, SyncConfigMissingError } from './sync/errors.js'; +import type { SyncService } from './sync/service.js'; + +export interface CommandFrontmatter { + description?: string; + agent?: string; + model?: string; + subtask?: boolean; +} + +export interface ParsedCommand { + name: string; + frontmatter: CommandFrontmatter; + template: string; +} + +export function parseFrontmatter(content: string): { + frontmatter: CommandFrontmatter; + body: string; +} { + const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; + const match = content.match(frontmatterRegex); + + if (!match) { + return { frontmatter: {}, body: content.trim() }; + } + + const [, yamlContent, body] = match; + const frontmatter: CommandFrontmatter = {}; + + for (const line of yamlContent.split('\n')) { + const colonIndex = line.indexOf(':'); + if (colonIndex === -1) continue; + + const key = line.slice(0, colonIndex).trim(); + const value = line.slice(colonIndex + 1).trim(); + + if (key === 'description') frontmatter.description = value; + if (key === 'agent') frontmatter.agent = value; + if (key === 'model') frontmatter.model = value; + if (key === 'subtask') frontmatter.subtask = value === 'true'; + } + + return { frontmatter, body: body.trim() }; +} + +export function getModuleDir(): string { + // Works in both Bun and Node.js + if (typeof import.meta.dir === 'string') { + return import.meta.dir; + } + // Node.js fallback + return path.dirname(fileURLToPath(import.meta.url)); +} + +async function scanMdFiles(dir: string): Promise { + const files: string[] = []; + + async function walk(currentDir: string): Promise { + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + if (entry.isDirectory()) { + await walk(fullPath); + } else if (entry.isFile() && entry.name.endsWith('.md')) { + files.push(fullPath); + } + } + } + + await walk(dir); + return files; +} + +export async function loadCommands(commandDir?: string): Promise { + const commands: ParsedCommand[] = []; + const dir = commandDir ?? path.join(getModuleDir(), 'command'); + + try { + const stats = await fs.stat(dir); + if (!stats.isDirectory()) { + return commands; + } + } catch { + return commands; + } + + const files = await scanMdFiles(dir); + for (const file of files) { + try { + const content = await fs.readFile(file, 'utf-8'); + const { frontmatter, body } = parseFrontmatter(content); + const relativePath = path.relative(dir, file); + const name = relativePath.replace(/\.md$/, '').replace(/\//g, '-'); + + commands.push({ + name, + frontmatter, + template: body, + }); + } catch {} + } + + return commands; +} + +export const SYNC_TOOL_COMMANDS = [ + 'status', + 'init', + 'link', + 'pull', + 'push', + 'enable-secrets', + 'resolve', + 'secrets-pull', + 'secrets-push', + 'secrets-status', + 'sessions-backend', + 'sessions-setup-turso', + 'sessions-migrate-turso', + 'sessions-cleanup-git', +] as const; + +export type SyncToolCommand = (typeof SYNC_TOOL_COMMANDS)[number]; + +export interface SyncToolArgs { + command: SyncToolCommand; + repo?: string; + owner?: string; + name?: string; + url?: string; + branch?: string; + includeSecrets?: boolean; + includeMcpSecrets?: boolean; + includeSessions?: boolean; + sessionBackend?: 'git' | 'turso'; + includePromptStash?: boolean; + includeModelFavorites?: boolean; + includeOpencodeSkills?: boolean; + includeAgentsDir?: boolean; + create?: boolean; + private?: boolean; + extraSecretPaths?: string[]; + extraConfigPaths?: string[]; + localRepoPath?: string; + acknowledgePrivateRemote?: boolean; + setupTurso?: boolean; + migrateSessions?: boolean; +} + +export function buildSyncToolInputSchema(): Record { + return { + type: 'object', + properties: { + command: { type: 'string', enum: [...SYNC_TOOL_COMMANDS] }, + repo: { type: 'string' }, + owner: { type: 'string' }, + name: { type: 'string' }, + url: { type: 'string' }, + branch: { type: 'string' }, + includeSecrets: { type: 'boolean' }, + includeMcpSecrets: { type: 'boolean' }, + includeSessions: { type: 'boolean' }, + sessionBackend: { type: 'string', enum: ['git', 'turso'] }, + includePromptStash: { type: 'boolean' }, + includeModelFavorites: { type: 'boolean' }, + includeOpencodeSkills: { type: 'boolean' }, + includeAgentsDir: { type: 'boolean' }, + create: { type: 'boolean' }, + private: { type: 'boolean' }, + extraSecretPaths: { type: 'array', items: { type: 'string' } }, + extraConfigPaths: { type: 'array', items: { type: 'string' } }, + localRepoPath: { type: 'string' }, + acknowledgePrivateRemote: { type: 'boolean' }, + setupTurso: { type: 'boolean' }, + migrateSessions: { type: 'boolean' }, + }, + required: ['command'], + additionalProperties: false, + }; +} + +/** Runtime-independent dispatch shared by the v1 and v2 tool executors. */ +export async function executeSyncCommand( + service: SyncService, + args: SyncToolArgs +): Promise { + try { + if (args.command === 'status') { + return await service.status(); + } + if (args.command === 'init') { + return await service.init({ + repo: args.repo, + owner: args.owner, + name: args.name, + url: args.url, + branch: args.branch, + includeSecrets: args.includeSecrets, + includeMcpSecrets: args.includeMcpSecrets, + includeSessions: args.includeSessions, + sessionBackend: args.sessionBackend, + includePromptStash: args.includePromptStash, + includeModelFavorites: args.includeModelFavorites, + setupTurso: args.setupTurso, + migrateSessions: args.migrateSessions, + includeOpencodeSkills: args.includeOpencodeSkills, + includeAgentsDir: args.includeAgentsDir, + create: args.create, + private: args.private, + extraSecretPaths: args.extraSecretPaths, + extraConfigPaths: args.extraConfigPaths, + localRepoPath: args.localRepoPath, + acknowledgePrivateRemote: args.acknowledgePrivateRemote, + }); + } + if (args.command === 'link') { + return await service.link({ + repo: args.repo ?? args.name, + branch: args.branch, + acknowledgePrivateRemote: args.acknowledgePrivateRemote, + }); + } + if (args.command === 'pull') { + return await service.pull(); + } + if (args.command === 'push') { + return await service.push(); + } + if (args.command === 'secrets-pull') { + return await service.secretsPull(); + } + if (args.command === 'secrets-push') { + return await service.secretsPush(); + } + if (args.command === 'secrets-status') { + return await service.secretsStatus(); + } + if (args.command === 'enable-secrets') { + return await service.enableSecrets({ + extraSecretPaths: args.extraSecretPaths, + includeMcpSecrets: args.includeMcpSecrets, + acknowledgePrivateRemote: args.acknowledgePrivateRemote, + }); + } + if (args.command === 'sessions-backend') { + return await service.sessionsBackend({ + backend: args.sessionBackend, + setupTurso: args.setupTurso, + migrateSessions: args.migrateSessions, + }); + } + if (args.command === 'sessions-setup-turso') { + return await service.sessionsSetupTurso({ + forceTokenRefresh: args.setupTurso, + }); + } + if (args.command === 'sessions-migrate-turso') { + return await service.sessionsMigrateTurso({ + setupTurso: args.setupTurso, + }); + } + if (args.command === 'sessions-cleanup-git') { + return await service.sessionsCleanupGit(); + } + if (args.command === 'resolve') { + return await service.resolve(); + } + + return 'Unknown command.'; + } catch (error) { + if (error instanceof SyncConfigMissingError || error instanceof SyncCommandError) { + return error.message; + } + return formatError(error); + } +} + +export function isPlainRecord(value: unknown): value is Record { + if (!value || typeof value !== 'object') return false; + return Object.getPrototypeOf(value) === Object.prototype; +} + +function hasOwn(target: Record, key: string): boolean { + return Object.hasOwn(target, key); +} + +export function disableMcpServerForResolutionFailure( + config: Record, + fieldPath: readonly string[] +): void { + if (fieldPath[0] !== 'overrides' || fieldPath[1] !== 'mcp') return; + const serverName = fieldPath[2]; + if (!serverName) return; + + const mcp = isPlainRecord(config.mcp) ? config.mcp : null; + if (!mcp || !hasOwn(mcp, serverName) || !isPlainRecord(mcp[serverName])) return; + + Object.defineProperty(mcp[serverName], 'enabled', { + value: false, + enumerable: true, + configurable: true, + writable: true, + }); +} + +export function formatError(error: unknown): string { + if (error instanceof Error) return error.message; + return String(error); +} From c21227b3f1f84bd46040f2e8b5cf31d8be6d3e6d Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:44:49 +0200 Subject: [PATCH 02/15] feat: add injectable AI provider and node shell shim for v2 --- src/shell-node.ts | 40 +++++++++++++ src/sync/ai.ts | 133 ++++++++++++++++++++++++++++++++++++++++++++ src/sync/commit.ts | 75 ++++++------------------- src/sync/service.ts | 127 ++++++++++-------------------------------- 4 files changed, 218 insertions(+), 157 deletions(-) create mode 100644 src/shell-node.ts create mode 100644 src/sync/ai.ts diff --git a/src/shell-node.ts b/src/shell-node.ts new file mode 100644 index 0000000..18096f9 --- /dev/null +++ b/src/shell-node.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; + +import type { PluginInput } from '@opencode-ai/plugin'; + +const execAsync = promisify(exec); + +function shellQuote(value: unknown): string { + return `'${String(value).replaceAll("'", `'\\''`)}'`; +} + +interface NodeShellCommand extends Promise<{ stdout: string; stderr: string }> { + quiet: () => NodeShellCommand; + text: () => Promise; +} + +/** + * Node `child_process` implementation of the opencode `$` template-tag shell. + * + * Opencode v2 plugins have no `$` helper, so the v2 entrypoint runs git/gh/op + * commands through this shim. Interpolated values are single-quote escaped, + * matching v1 behavior. `.quiet()` is a no-op kept for call-site compatibility, + * `.text()` resolves stdout, and awaiting the command throws on non-zero exit. + */ +export function createNodeShell(): PluginInput['$'] { + const shell = (strings: TemplateStringsArray, ...values: unknown[]): NodeShellCommand => { + const command = strings.reduce( + (result, segment, index) => + result + segment + (index < values.length ? shellQuote(values[index]) : ''), + '' + ); + const execution = execAsync(command, { + maxBuffer: 32 * 1024 * 1024, + }) as NodeShellCommand; + execution.quiet = () => execution; + execution.text = async () => (await execution).stdout; + return execution; + }; + return shell as unknown as PluginInput['$']; +} diff --git a/src/sync/ai.ts b/src/sync/ai.ts new file mode 100644 index 0000000..3cff2fa --- /dev/null +++ b/src/sync/ai.ts @@ -0,0 +1,133 @@ +import type { PluginInput } from '@opencode-ai/plugin'; + +import { extractTextFromResponse, resolveSmallModel, unwrapData } from './utils.js'; + +export interface AiModelRef { + providerID: string; + modelID: string; +} + +export interface AiProvider { + resolveModel: () => Promise; + generateText: (_model: AiModelRef, _prompt: string) => Promise; +} + +export interface ResolutionDecision { + action: 'commit' | 'reset' | 'manual'; + message?: string; + reason?: string; +} + +type V1Client = PluginInput['client']; + +/** Throwaway-session AI flow used on opencode v1. */ +export function createV1AiProvider(client: V1Client): AiProvider { + return { + resolveModel: () => resolveSmallModel(client), + generateText: async (model, prompt) => { + let sessionId: string | null = null; + try { + const sessionResult = await client.session.create({ body: { title: 'opencode-synced' } }); + const session = unwrapData<{ id: string }>(sessionResult); + sessionId = session?.id ?? null; + if (!sessionId) return null; + + const response = await client.session.prompt({ + path: { id: sessionId }, + body: { + model, + parts: [{ type: 'text', text: prompt }], + }, + }); + + return extractTextFromResponse(unwrapData(response) ?? response); + } catch { + return null; + } finally { + if (sessionId) { + try { + await client.session.delete({ path: { id: sessionId } }); + } catch {} + } + } + }, + }; +} + +export function buildCommitPrompt(diffSummary: string): string { + return [ + 'Generate a concise single-line git commit message (max 72 chars).', + 'Focus on opencode config sync changes.', + 'Return only the message, no quotes.', + '', + 'Diff summary:', + diffSummary, + ].join('\n'); +} + +export function sanitizeCommitMessage(message: string): string { + const firstLine = message.split('\n')[0].trim(); + const trimmed = firstLine.replace(/^["'`]+|["'`]+$/g, '').trim(); + if (!trimmed) return ''; + if (trimmed.length <= 72) return trimmed; + return trimmed.slice(0, 72).trim(); +} + +export function commitFallbackMessage(date: Date = new Date()): string { + return `Sync opencode config (${formatDate(date)})`; +} + +export function buildResolvePrompt(statusOutput: string, diffPreview: string): string { + return [ + 'You are analyzing uncommitted changes in an opencode-synced repository.', + 'Decide whether to commit these changes or discard them.', + '', + 'IMPORTANT: Only choose "commit" if the changes appear to be legitimate config updates.', + 'Choose "discard" if the changes look like temporary files, cache, or corruption.', + '', + 'Respond with ONLY a JSON object in this exact format:', + '{"action": "commit", "message": "your commit message here"}', + 'OR', + '{"action": "discard", "reason": "explanation why discarding"}', + '', + 'Status:', + statusOutput, + '', + 'Diff preview (first 2000 chars):', + diffPreview, + ].join('\n'); +} + +export function parseResolutionDecision(text: string): ResolutionDecision { + try { + const jsonMatch = text.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + return { action: 'manual', reason: 'Could not parse AI response' }; + } + + const parsed = JSON.parse(jsonMatch[0]) as { + action?: string; + message?: string; + reason?: string; + }; + + if (parsed.action === 'commit' && parsed.message) { + return { action: 'commit', message: parsed.message }; + } + + if (parsed.action === 'discard') { + return { action: 'reset', reason: parsed.reason }; + } + + return { action: 'manual', reason: 'Unexpected AI response format' }; + } catch { + return { action: 'manual', reason: 'Failed to parse AI decision' }; + } +} + +function formatDate(date: Date): string { + const year = String(date.getFullYear()); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} diff --git a/src/sync/commit.ts b/src/sync/commit.ts index 20df918..dfd8d55 100644 --- a/src/sync/commit.ts +++ b/src/sync/commit.ts @@ -1,12 +1,19 @@ import type { PluginInput } from '@opencode-ai/plugin'; -import { extractTextFromResponse, resolveSmallModel, unwrapData } from './utils.js'; -type CommitClient = PluginInput['client']; +import { + type AiProvider, + buildCommitPrompt, + commitFallbackMessage, + createV1AiProvider, + sanitizeCommitMessage, +} from './ai.js'; + type Shell = PluginInput['$']; interface CommitContext { - client: CommitClient; + client: PluginInput['client']; $: Shell; + ai?: AiProvider; } export async function generateCommitMessage( @@ -14,61 +21,20 @@ export async function generateCommitMessage( repoDir: string, fallbackDate = new Date() ): Promise { - const fallback = `Sync opencode config (${formatDate(fallbackDate)})`; + const fallback = commitFallbackMessage(fallbackDate); const diffSummary = await getDiffSummary(ctx.$, repoDir); if (!diffSummary) return fallback; - const model = await resolveSmallModel(ctx.client); + const ai = ctx.ai ?? createV1AiProvider(ctx.client); + const model = await ai.resolveModel(); if (!model) return fallback; - const prompt = [ - 'Generate a concise single-line git commit message (max 72 chars).', - 'Focus on opencode config sync changes.', - 'Return only the message, no quotes.', - '', - 'Diff summary:', - diffSummary, - ].join('\n'); - - let sessionId: string | null = null; - - try { - const sessionResult = await ctx.client.session.create({ body: { title: 'opencode-synced' } }); - const session = unwrapData<{ id: string }>(sessionResult); - sessionId = session?.id ?? null; - if (!sessionId) return fallback; - - const response = await ctx.client.session.prompt({ - path: { id: sessionId }, - body: { - model, - parts: [{ type: 'text', text: prompt }], - }, - }); - - const message = extractTextFromResponse(unwrapData(response) ?? response); - if (!message) return fallback; + const message = await ai.generateText(model, buildCommitPrompt(diffSummary)); + if (!message) return fallback; - const sanitized = sanitizeMessage(message); - return sanitized || fallback; - } catch { - return fallback; - } finally { - if (sessionId) { - try { - await ctx.client.session.delete({ path: { id: sessionId } }); - } catch {} - } - } -} - -function sanitizeMessage(message: string): string { - const firstLine = message.split('\n')[0].trim(); - const trimmed = firstLine.replace(/^["'`]+|["'`]+$/g, '').trim(); - if (!trimmed) return ''; - if (trimmed.length <= 72) return trimmed; - return trimmed.slice(0, 72).trim(); + const sanitized = sanitizeCommitMessage(message); + return sanitized || fallback; } async function getDiffSummary($: Shell, repoDir: string): Promise { @@ -80,10 +46,3 @@ async function getDiffSummary($: Shell, repoDir: string): Promise { return ''; } } - -function formatDate(date: Date): string { - const year = String(date.getFullYear()); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; -} diff --git a/src/sync/service.ts b/src/sync/service.ts index fa418bf..fdadb53 100644 --- a/src/sync/service.ts +++ b/src/sync/service.ts @@ -3,6 +3,13 @@ import { promises as fs } from 'node:fs'; import path from 'node:path'; import type { PluginInput } from '@opencode-ai/plugin'; +import { + type AiProvider, + buildResolvePrompt, + createV1AiProvider, + parseResolutionDecision, + type ResolutionDecision, +} from './ai.js'; import { syncLocalToRepo, syncRepoToLocal } from './apply.js'; import { generateCommitMessage } from './commit.js'; import type { NormalizedSyncConfig } from './config.js'; @@ -56,15 +63,9 @@ import { isRetryableTursoError, type TursoSyncPreference, } from './turso.js'; -import { - createLogger, - extractTextFromResponse, - resolveSmallModel, - showToast, - unwrapData, -} from './utils.js'; - -type SyncServiceContext = Pick; +import { createLogger, showToast, unwrapData } from './utils.js'; + +type SyncServiceContext = Pick & { ai?: AiProvider }; type Logger = ReturnType; type Shell = PluginInput['$']; @@ -1073,7 +1074,10 @@ export function createSyncService(ctx: SyncServiceContext): SyncService { } } - const message = await generateCommitMessage({ client: ctx.client, $: ctx.$ }, repoRoot); + const message = await generateCommitMessage( + { client: ctx.client, $: ctx.$, ai: ctx.ai }, + repoRoot + ); await commitAll(ctx.$, repoRoot, message); await pushBranch(ctx.$, repoRoot, branch); @@ -1368,7 +1372,7 @@ export function createSyncService(ctx: SyncServiceContext): SyncService { const status = await getRepoStatus(ctx.$, repoRoot); const decision = await analyzeAndDecideResolution( - { client: ctx.client, $: ctx.$ }, + { client: ctx.client, $: ctx.$, ai: ctx.ai }, repoRoot, status.changes ); @@ -1477,7 +1481,10 @@ async function runStartup( return; } - const message = await generateCommitMessage({ client: ctx.client, $: ctx.$ }, repoRoot); + const message = await generateCommitMessage( + { client: ctx.client, $: ctx.$, ai: ctx.ai }, + repoRoot + ); log.info('Pushing local changes', { message }); await commitAll(ctx.$, repoRoot, message); await pushBranch(ctx.$, repoRoot, branch); @@ -1714,111 +1721,33 @@ function isBusySessionStatus(status: unknown): boolean { return true; } -interface ResolutionDecision { - action: 'commit' | 'reset' | 'manual'; - message?: string; - reason?: string; -} - async function analyzeAndDecideResolution( - ctx: { client: SyncServiceContext['client']; $: Shell }, + ctx: { client: SyncServiceContext['client']; $: Shell; ai?: AiProvider }, repoRoot: string, changes: string[] ): Promise { try { const diff = await ctx.$`git -C ${repoRoot} diff HEAD`.quiet().text(); - const statusOutput = changes.join('\n'); - - const prompt = [ - 'You are analyzing uncommitted changes in an opencode-synced repository.', - 'Decide whether to commit these changes or discard them.', - '', - 'IMPORTANT: Only choose "commit" if the changes appear to be legitimate config updates.', - 'Choose "discard" if the changes look like temporary files, cache, or corruption.', - '', - 'Respond with ONLY a JSON object in this exact format:', - '{"action": "commit", "message": "your commit message here"}', - 'OR', - '{"action": "discard", "reason": "explanation why discarding"}', - '', - 'Status:', - statusOutput, - '', - 'Diff preview (first 2000 chars):', - diff.slice(0, 2000), - ].join('\n'); + const prompt = buildResolvePrompt(changes.join('\n'), diff.slice(0, 2000)); - const model = await resolveSmallModel(ctx.client); + const ai = ctx.ai ?? createV1AiProvider(ctx.client); + const model = await ai.resolveModel(); if (!model) { return { action: 'manual', reason: 'No AI model available' }; } - let sessionId: string | null = null; - try { - const sessionResult = await ctx.client.session.create({ - body: { title: 'sync-resolve' }, - }); - const session = unwrapData<{ id: string }>(sessionResult); - sessionId = session?.id ?? null; - if (!sessionId) { - return { action: 'manual', reason: 'Failed to create session' }; - } - - const response = await ctx.client.session.prompt({ - path: { id: sessionId }, - body: { - model, - parts: [{ type: 'text', text: prompt }], - }, - }); - - const messageText = extractTextFromResponse(unwrapData(response) ?? response); - if (!messageText) { - return { action: 'manual', reason: 'No response from AI' }; - } - - const decision = parseResolutionDecision(messageText); - return decision; - } finally { - if (sessionId) { - try { - await ctx.client.session.delete({ path: { id: sessionId } }); - } catch {} - } + const messageText = await ai.generateText(model, prompt); + if (!messageText) { + return { action: 'manual', reason: 'No response from AI' }; } + + return parseResolutionDecision(messageText); } catch (error) { console.error('[ERROR] AI resolution analysis failed:', error); return { action: 'manual', reason: `Error analyzing changes: ${formatError(error)}` }; } } -function parseResolutionDecision(text: string): ResolutionDecision { - try { - const jsonMatch = text.match(/\{[\s\S]*\}/); - if (!jsonMatch) { - return { action: 'manual', reason: 'Could not parse AI response' }; - } - - const parsed = JSON.parse(jsonMatch[0]) as { - action?: string; - message?: string; - reason?: string; - }; - - if (parsed.action === 'commit' && parsed.message) { - return { action: 'commit', message: parsed.message }; - } - - if (parsed.action === 'discard') { - return { action: 'reset', reason: parsed.reason }; - } - - return { action: 'manual', reason: 'Unexpected AI response format' }; - } catch { - return { action: 'manual', reason: 'Failed to parse AI decision' }; - } -} - function resolveStrictLinkRepo(raw: string | undefined): { owner: string; name: string } | null { if (!raw) return null; const value = raw.trim(); From f16dec2126de9dbb199d89b1ce1eab63d73c31a2 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:50:10 +0200 Subject: [PATCH 03/15] feat: support opencode v2 plugin API alongside v1 --- bun.lock | 579 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- src/index.ts | 18 +- src/shell-node.ts | 2 +- src/v2.ts | 460 ++++++++++++++++++++++++++++++++++++ 5 files changed, 1054 insertions(+), 8 deletions(-) create mode 100644 src/v2.ts diff --git a/bun.lock b/bun.lock index 8110e08..cc041ef 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,8 @@ "": { "name": "opencode-sync", "dependencies": { - "@opencode-ai/plugin": "1.0.85", + "@opencode-ai/plugin": "^1.18.29", + "@opencode/plugin": "^2.0.0", }, "devDependencies": { "@biomejs/biome": "2.3.10", @@ -21,6 +22,56 @@ }, }, "packages": { + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.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" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1057.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" } }, "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.978.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" } }, "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA=="], + + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.70", "", { "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" } }, "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.71", "", { "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" } }, "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.73", "", { "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" } }, "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.16", "", { "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" } }, "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.78", "", { "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" } }, "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.83", "", { "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" } }, "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.71", "", { "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" } }, "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.15", "", { "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" } }, "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.77", "", { "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" } }, "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ=="], + + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1057.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" } }, "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.45", "", { "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" } }, "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.46", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1129.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" } }, "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.10", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.40", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], @@ -77,6 +128,12 @@ "@commitlint/types": ["@commitlint/types@20.2.0", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-KTy0OqRDLR5y/zZMnizyx09z/rPlPC/zKhYgH8o/q6PuAjoQAKlRfY4zzv0M64yybQ//6//4H1n14pxaLZfUnA=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "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" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="], + + "@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="], + + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], @@ -129,11 +186,129 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.85", "", { "dependencies": { "@opencode-ai/sdk": "1.0.85", "zod": "4.1.8" } }, "sha512-4uU15occ/rZALIvDk3VwKdZwUZVAcNEcBZNJQKKMX+Lp+sQ/YvHKqawwz1ktOIS2UDsCKh+gyvV8fRCuUBEdKQ=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], + + "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "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" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], + + "@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "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": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="], + + "@npmcli/config": ["@npmcli/config@10.8.1", "", { "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" } }, "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/git": ["@npmcli/git@7.0.2", "", { "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" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="], + + "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="], + + "@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="], + + "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "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" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="], + + "@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="], + + "@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="], + + "@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "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" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="], + + "@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="], + + "@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "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" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], + + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.32", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.32", "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" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-JJXaTHmkiU9EAshJOFuIkZsAuvBcLw46BAErePeER8bPeO2LL2uGyBRt4tuLJjJ2N7v1vlZgfvAD7bWKupke1A=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.32", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Wi3WtH/cLA3C0ga05lyDb7zac+SzbST9MPwCve1Ffod0xwzHdy9G2gnxvMAR4RBRhKJS32Rgk2cR+fxBhAVSQw=="], + + "@opencode/ai": ["@opencode/ai@2.0.13", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.13", "@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" } }, "sha512-F21JJIvJnFHsgxAdLX134mFqaOBT/MKfAlCjWyddLkQ6Ee+g1TiLoFzYhSjw72OPOJGudDmeH2ygX3mfQhKQEQ=="], + + "@opencode/client": ["@opencode/client@2.0.13", "", { "dependencies": { "@opencode/protocol": "2.0.13", "@opencode/schema": "2.0.13" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-eNUH3BB0sy/BBz26B7cqbpGPsMnjYQbx+LIYjjEgmq1aDosntbaUhju8cTD0GGwftD7e+R++XKDOwCnkXkNGJg=="], + + "@opencode/plugin": ["@opencode/plugin@2.0.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.13", "@opencode/client": "2.0.13", "@opencode/protocol": "2.0.13", "@opencode/schema": "2.0.13", "@opencode/util": "2.0.13", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode/theme": "2.0.13", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-eI7d0b0uYfiO1yn2j54ifrCnDOok7tK217lj3I1RDw273JE0I+3VUuxFdP9gri1Y/Imx5pZh1Wfik5bQCc/FdA=="], + + "@opencode/protocol": ["@opencode/protocol@2.0.13", "", { "dependencies": { "@opencode/schema": "2.0.13", "effect": "4.0.0-rc.112" } }, "sha512-Qzh/cuMF0hBKWz+XKUoh+FgyU/p9MK84SKAoqmAKRWrDKC+1+zNoHOB9urZ+TBDerw2Nrl8id8UBMWuTmGhrCQ=="], + + "@opencode/schema": ["@opencode/schema@2.0.13", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-jdfjPMnu9uFRKQ6Sx92nu1CQ/FkqbOLhh3CG6VPlKUzyvOoUB0N2jJ8NOePlbmkaudkY8jNtwekhWzZSRql63Q=="], + + "@opencode/util": ["@opencode/util@2.0.13", "", { "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" } }, "sha512-NWWV1DN2Pp6KlvvEtyBz+VIH7lF0r5X2gvrv/TrbeBhB9l2uuyQkyY32OsGu/dIdmxlHBM5aEf9E/NLQzsOJDQ=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], + + "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.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" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.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" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.85", "", {}, "sha512-7qVa7Psu7dFQBI1aMeQZXRho/6QMebHS2klNRaYO8b+n4jMUg20HEBIMFw5S1B2RF00+DFiTDxbbdByfirRNtg=="], + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="], + + "@redis/bloom": ["@redis/bloom@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag=="], + + "@redis/client": ["@redis/client@6.2.1", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0", "@opentelemetry/api": ">=1 <2" }, "optionalPeers": ["@node-rs/xxhash", "@opentelemetry/api"] }, "sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q=="], + + "@redis/json": ["@redis/json@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ=="], + + "@redis/search": ["@redis/search@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ=="], + + "@redis/time-series": ["@redis/time-series@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], @@ -179,6 +354,46 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.2", "", {}, "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw=="], + + "@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "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" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + + "@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.1", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], + + "@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-nxu3SgmAw9JXT2CtkU0m/XNLWpP9MsaBx1zAGAypCbYj15tIFlmcYwpF+Oh18le83d+IM9PT7ENdXnE4C+d5mA=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-iq+cW3mAb7vfcxEEpYi3zXKpDtbrIFyanWjQl4zBq4seWD4OSxXDWSfespZxenX6aEaighn+NR3u1nU1DSvs3w=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g=="], @@ -189,6 +404,8 @@ "@types/node": ["@types/node@20.19.27", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], @@ -205,6 +422,10 @@ "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], + "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], @@ -219,12 +440,30 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bin-links": ["bin-links@6.0.2", "", { "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" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], + + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cacache": ["cacache@20.0.4", "", { "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" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], @@ -233,12 +472,20 @@ "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -247,8 +494,12 @@ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@7.0.2", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w=="], @@ -259,14 +510,28 @@ "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.2.0", "", { "dependencies": { "jiti": "^2.6.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], @@ -287,33 +552,77 @@ "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-check": ["fast-check@4.10.2", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + "find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="], + + "gcp-metadata": ["gcp-metadata@8.1.4", "", { "dependencies": { "gaxios": "7.1.3", "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" } }, "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], "git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], + "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], + "global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + "google-auth-library": ["google-auth-library@10.5.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" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + + "google-logging-utils": ["google-logging-utils@1.2.0", "", {}, "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + + "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + + "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], @@ -325,18 +634,38 @@ "is-text-path": ["is-text-path@2.0.0", "", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], + + "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "lint-staged": ["lint-staged@16.2.7", "", { "dependencies": { "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow=="], @@ -365,36 +694,108 @@ "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "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" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], + "meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], + + "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="], + "nano-spawn": ["nano-spawn@2.0.0", "", {}, "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "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" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], + + "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], + + "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], + + "npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="], + + "npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="], + + "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@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" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "pacote": ["pacote@21.5.1", "", { "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" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], @@ -407,6 +808,24 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss-selector-parser": ["postcss-selector-parser@7.1.6", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], + + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], + + "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], + + "protobufjs": ["protobufjs@7.6.6", "", { "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" } }, "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg=="], + + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + + "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], + + "redis": ["redis@6.2.1", "", { "dependencies": { "@redis/bloom": "6.2.1", "@redis/client": "6.2.1", "@redis/json": "6.2.1", "@redis/search": "6.2.1", "@redis/time-series": "6.2.1" } }, "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], @@ -417,20 +836,46 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "sigstore": ["sigstore@4.1.1", "", { "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" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.24", "", {}, "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], @@ -439,10 +884,16 @@ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "text-extensions": ["text-extensions@2.4.0", "", {}, "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g=="], "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], @@ -461,24 +912,54 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], + + "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "undici": ["undici@8.11.0", "", {}, "sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w=="], + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="], + + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + "vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], + + "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -489,18 +970,78 @@ "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "@effect/opentelemetry/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@effect/platform-node/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@effect/platform-node-shared/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "@npmcli/git/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "@npmcli/metavuln-calculator/json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + + "@npmcli/package-json/json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + + "@npmcli/promise-spawn/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "@opencode/ai/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/client/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/plugin/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/protocol/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/schema/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/util/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + "cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "effect/yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], + + "gcp-metadata/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "log-update/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-gyp/undici": ["undici@6.28.1", "", {}, "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA=="], + + "node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "parse-conflict-json/json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + + "rimraf/glob": ["glob@10.5.0", "", { "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" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], "vitest/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -509,16 +1050,46 @@ "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + "cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "log-update/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "rimraf/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.1.7", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g=="], + + "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/package.json b/package.json index d123216..b9b4021 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ }, "files": ["dist"], "dependencies": { - "@opencode-ai/plugin": "1.0.85" + "@opencode-ai/plugin": "^1.18.29", + "@opencode/plugin": "^2.0.0" }, "devDependencies": { "@biomejs/biome": "2.3.10", diff --git a/src/index.ts b/src/index.ts index 7abd74b..58e2fd4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ import type { Plugin } from '@opencode-ai/plugin'; import { tool } from '@opencode-ai/plugin'; - import { disableMcpServerForResolutionFailure, executeSyncCommand, @@ -14,6 +13,7 @@ import { } from './sync/config.js'; import { resolveSyncLocations } from './sync/paths.js'; import { createSyncService } from './sync/service.js'; +import { opencodeSyncedV2 } from './v2.js'; export const opencodeConfigSync: Plugin = async (ctx) => { const commands = await loadCommands(); @@ -147,4 +147,18 @@ export const opencodeConfigSync: Plugin = async (ctx) => { }; export const opencodeSynced = opencodeConfigSync; -export default opencodeConfigSync; + +export { opencodeSyncedV2 } from './v2.js'; + +/** + * Dual v1 + v2 entrypoint. + * + * V2 calls `setup()` (registered via `Plugin.define`); v1 (>= 1.18.29 object + * entrypoints) calls `server()` with the v1 plugin context. + */ +export default { + ...opencodeSyncedV2, + async server(ctx: Parameters[0]) { + return await opencodeConfigSync(ctx); + }, +}; diff --git a/src/shell-node.ts b/src/shell-node.ts index 18096f9..2d80dfb 100644 --- a/src/shell-node.ts +++ b/src/shell-node.ts @@ -31,7 +31,7 @@ export function createNodeShell(): PluginInput['$'] { ); const execution = execAsync(command, { maxBuffer: 32 * 1024 * 1024, - }) as NodeShellCommand; + }) as unknown as NodeShellCommand; execution.quiet = () => execution; execution.text = async () => (await execution).stdout; return execution; diff --git a/src/v2.ts b/src/v2.ts new file mode 100644 index 0000000..8d57b50 --- /dev/null +++ b/src/v2.ts @@ -0,0 +1,460 @@ +import { Plugin } from '@opencode/plugin'; +import type { Context as V2Context } from '@opencode/plugin/promise/plugin'; +import type { PluginInput } from '@opencode-ai/plugin'; + +import { + buildSyncToolInputSchema, + disableMcpServerForResolutionFailure, + executeSyncCommand, + loadCommands, + type ParsedCommand, + type SyncToolArgs, +} from './shared.js'; +import { createNodeShell } from './shell-node.js'; +import type { AiProvider } from './sync/ai.js'; +import { + EnvPlaceholderResolutionError, + isPlainObject, + loadOverrides, + resolveEnvPlaceholders, +} from './sync/config.js'; +import { resolveSyncLocations } from './sync/paths.js'; +import { createSyncService, type SyncService } from './sync/service.js'; + +const PLUGIN_ID = 'opencode-synced'; +const OVERRIDES_DOC_POINTER = + 'opencode-synced: ignoring unsupported override key (v2 applies only mcp/agent/model/provider via domain transforms; see https://opencode.ai/v2/docs/build/plugins/migrate-v1)'; + +interface ResolvedOverrides { + values: Record; + /** Field paths (e.g. ['overrides','mcp','github',...]) that failed {env:…} resolution. */ + failures: { fieldPath: readonly string[]; message: string }[]; +} + +async function loadResolvedOverrides(): Promise { + const overrides = await loadOverrides(resolveSyncLocations()); + if (!overrides) return null; + + const values: Record = {}; + const failures: ResolvedOverrides['failures'] = []; + for (const [key, value] of Object.entries(overrides)) { + try { + values[key] = resolveEnvPlaceholders(value, process.env, ['overrides', key]); + } catch (error) { + if (error instanceof EnvPlaceholderResolutionError) { + failures.push({ fieldPath: error.fieldPath, message: error.message }); + console.error(`[opencode-synced] ${error.message}`); + } else { + throw error; + } + } + } + return { values, failures }; +} + +function applyDeepMerge(target: Record, source: unknown): void { + if (!isPlainObject(source)) return; + for (const [key, value] of Object.entries(source)) { + if (key === '__proto__') continue; + const current = target[key]; + if (isPlainObject(value) && isPlainObject(current)) { + applyDeepMerge(current as Record, value); + } else { + Object.defineProperty(target, key, { + value, + enumerable: true, + configurable: true, + writable: true, + }); + } + } +} + +/** Blank any unresolvable {env:…} placeholders so a disabled server holds no secrets. */ +function blankPlaceholders(value: unknown): unknown { + if (typeof value === 'string') return value.replace(/\{env:[^}]+\}/g, ''); + if (Array.isArray(value)) return value.map(blankPlaceholders); + if (isPlainObject(value)) { + const result: Record = {}; + for (const [key, nested] of Object.entries(value)) { + if (key === '__proto__') continue; + result[key] = blankPlaceholders(nested); + } + return result; + } + return value; +} + +function failedServerNames(failures: ResolvedOverrides['failures']): Set { + const names = new Set(); + for (const failure of failures) { + if (failure.fieldPath[0] === 'overrides' && failure.fieldPath[1] === 'mcp') { + const serverName = failure.fieldPath[2]; + if (serverName) names.add(serverName); + } + } + return names; +} + +async function registerMcpTransform( + ctx: V2Context, + resolved: ResolvedOverrides, + raw: Record | null +): Promise { + const mcp = isPlainObject(resolved.values.mcp) + ? (resolved.values.mcp as Record) + : null; + const failed = failedServerNames(resolved.failures); + const rawMcp = raw && isPlainObject(raw.mcp) ? (raw.mcp as Record) : null; + if (!mcp && failed.size === 0) return; + + await ctx.mcp.transform((editor) => { + if (mcp) { + for (const [name, config] of Object.entries(mcp)) { + if (!isPlainObject(config)) continue; + if (editor.get(name)) { + editor.update(name, (draft) => { + applyDeepMerge(draft as unknown as Record, config); + }); + } else { + editor.set(name, config as never); + } + } + } + for (const name of failed) { + const rawConfig = rawMcp && isPlainObject(rawMcp[name]) ? rawMcp[name] : {}; + const disabledConfig = { + ...(blankPlaceholders(rawConfig) as Record), + disabled: true, + }; + if (editor.get(name)) { + editor.update(name, (draft) => { + applyDeepMerge(draft as unknown as Record, disabledConfig); + }); + } else { + editor.set(name, disabledConfig as never); + } + // Mirror the v1 runtime-config fallback so status output stays consistent. + disableMcpServerForResolutionFailure({ mcp: { [name]: {} } }, ['overrides', 'mcp', name]); + } + }); +} + +async function registerAgentTransform(ctx: V2Context, resolved: ResolvedOverrides): Promise { + const agents = resolved.values.agent; + if (!isPlainObject(agents)) return; + + await ctx.agent.transform((editor) => { + for (const [id, partial] of Object.entries(agents)) { + if (!isPlainObject(partial)) continue; + if (editor.get(id)) { + editor.update(id, (draft) => { + applyDeepMerge(draft as unknown as Record, partial); + }); + } else { + console.warn(`[opencode-synced] Ignoring override for unknown agent "${id}".`); + } + } + }); +} + +async function registerModelTransform(ctx: V2Context, resolved: ResolvedOverrides): Promise { + const models = resolved.values.model; + if (!isPlainObject(models)) return; + + await ctx.model.transform((editor) => { + for (const [providerID, providerModels] of Object.entries(models)) { + if (!isPlainObject(providerModels)) { + console.warn( + `[opencode-synced] Ignoring model override for "${providerID}". ${OVERRIDES_DOC_POINTER}: model` + ); + continue; + } + for (const [modelID, partial] of Object.entries(providerModels)) { + if (!isPlainObject(partial)) continue; + if (editor.get(providerID, modelID)) { + editor.update(providerID, modelID, (draft) => { + applyDeepMerge(draft as unknown as Record, partial); + }); + } else { + console.warn( + `[opencode-synced] Ignoring override for unknown model "${providerID}/${modelID}".` + ); + } + } + } + }); +} + +async function registerProviderTransform( + ctx: V2Context, + resolved: ResolvedOverrides +): Promise { + const providers = resolved.values.provider; + if (!isPlainObject(providers)) return; + + await ctx.provider.transform((editor) => { + for (const [providerID, partial] of Object.entries(providers)) { + if (!isPlainObject(partial)) continue; + if (editor.get(providerID)) { + editor.update(providerID, (draft) => { + applyDeepMerge(draft as unknown as Record, partial); + }); + } else { + console.warn(`[opencode-synced] Ignoring override for unknown provider "${providerID}".`); + } + } + }); +} + +function warnOnRemainingOverrides(resolved: ResolvedOverrides): void { + for (const key of Object.keys(resolved.values)) { + if (key === 'mcp' || key === 'agent' || key === 'model' || key === 'provider') continue; + if (key === 'command') { + console.warn( + '[opencode-synced] Ignoring "command" overrides in v2: sync commands are owned by this plugin. ' + + 'See https://opencode.ai/v2/docs/build/plugins/migrate-v1' + ); + continue; + } + console.warn( + `[opencode-synced] Ignoring unsupported override key "${key}". ${OVERRIDES_DOC_POINTER}: ${key}` + ); + } +} + +/** + * Minimal v1 client facade over v2 capabilities. + * + * V2 has no toasts and no session-status API, so toasts are no-ops (the service + * also logs via app.log, which maps to console here) and session status always + * reports idle, which skips Turso idle-gating and syncs immediately. + */ +function createV2ClientFacade(): PluginInput['client'] { + return { + app: { + log: async (entry: unknown) => { + const body = (entry as { body?: { level?: string; message?: string } })?.body; + const level = body?.level === 'error' ? 'error' : 'log'; + console[level](`[opencode-synced] ${body?.message ?? JSON.stringify(entry)}`); + return {}; + }, + }, + config: { + get: async () => ({ data: {} }), + }, + session: { + status: async () => ({ data: {} }), + }, + tui: { + showToast: async () => ({}), + }, + } as unknown as PluginInput['client']; +} + +function createV2AiProvider(ctx: V2Context): AiProvider { + return { + resolveModel: async () => { + try { + const raw = (await ctx.model.default()) as unknown; + const ref = (raw as { data?: unknown })?.data ?? raw; + const record = ref as { providerID?: unknown; modelID?: unknown; id?: unknown }; + const providerID = typeof record?.providerID === 'string' ? record.providerID : null; + const modelID = + typeof record?.modelID === 'string' + ? record.modelID + : typeof record?.id === 'string' + ? record.id + : null; + if (!providerID || !modelID) return null; + return { providerID, modelID }; + } catch { + return null; + } + }, + generateText: async (model, prompt) => { + try { + const output = (await ctx.generate.text({ + model: { providerID: model.providerID, id: model.modelID }, + prompt, + })) as unknown; + if (typeof output === 'string') return output; + const record = output as { text?: unknown; data?: { text?: unknown } }; + if (typeof record?.text === 'string') return record.text; + if (typeof record?.data?.text === 'string') return record.data.text; + return null; + } catch { + return null; + } + }, + }; +} + +function toolCommandForName(name: string): SyncToolArgs['command'] | null { + const suffix = name.startsWith('sync-') ? name.slice('sync-'.length) : name; + switch (suffix) { + case 'status': + case 'pull': + case 'push': + case 'resolve': + case 'secrets-pull': + case 'secrets-push': + case 'secrets-status': + case 'sessions-cleanup-git': + return suffix; + case 'init': + case 'link': + return suffix; + case 'enable-secrets': + return 'enable-secrets'; + case 'sessions-backend': + case 'sessions-setup-turso': + case 'sessions-migrate-turso': + return suffix; + default: + return null; + } +} + +/** Parse a bare repo argument from a slash-command invocation (e.g. `/sync-link owner/repo`). */ +export function parseCommandRepoArg( + text: string | undefined, + commandName: string +): string | undefined { + if (!text) return undefined; + let arg = text.trim(); + if (arg.startsWith('/')) { + const firstSpace = arg.indexOf(' '); + if (firstSpace === -1) return undefined; + arg = arg.slice(firstSpace + 1).trim(); + } else if (arg.startsWith(commandName)) { + arg = arg.slice(commandName.length).trim(); + } + if (arg.startsWith('$ARGUMENTS')) arg = arg.slice('$ARGUMENTS'.length).trim(); + return arg || undefined; +} + +async function executeV2Command( + service: SyncService, + command: ParsedCommand, + invocation: { sessionID: string; prompt?: { text?: string }; text?: string } +): Promise { + const toolCommand = toolCommandForName(command.name); + if (!toolCommand) return `Unknown sync command: ${command.name}`; + + const rawText = invocation.prompt?.text ?? invocation.text; + const repo = parseCommandRepoArg(rawText, command.name); + + switch (toolCommand) { + case 'status': + return await service.status(); + case 'init': + return await service.init({ repo }); + case 'link': + return await service.link({ repo }); + case 'pull': + return await service.pull(); + case 'push': + return await service.push(); + case 'resolve': + return await service.resolve(); + case 'secrets-pull': + return await service.secretsPull(); + case 'secrets-push': + return await service.secretsPush(); + case 'secrets-status': + return await service.secretsStatus(); + case 'enable-secrets': + return await service.enableSecrets({}); + case 'sessions-backend': { + const backend = repo === 'git' || repo === 'turso' ? repo : undefined; + return await service.sessionsBackend({ backend }); + } + case 'sessions-setup-turso': + return await service.sessionsSetupTurso({}); + case 'sessions-migrate-turso': + return await service.sessionsMigrateTurso({}); + case 'sessions-cleanup-git': + return await service.sessionsCleanupGit(); + } +} + +export async function setupV2(ctx: V2Context): Promise<() => void> { + const commands = await loadCommands(); + const service = createSyncService({ + client: createV2ClientFacade(), + $: createNodeShell(), + ai: createV2AiProvider(ctx), + }); + + // Load + resolve {env:…} once before registering replayable transforms. + const resolved = await loadResolvedOverrides(); + const raw = await loadOverrides(resolveSyncLocations()); + + await ctx.tool.transform((editor) => { + editor.add({ + name: 'opencode_sync', + description: 'Manage opencode config sync with a GitHub repo', + input: buildSyncToolInputSchema() as never, + execute: async (input) => { + const result = await executeSyncCommand(service, input as unknown as SyncToolArgs); + return { content: result } as never; + }, + }); + }); + + await ctx.command.transform((editor) => { + for (const command of commands) { + editor.add({ + name: command.name, + description: command.frontmatter.description, + execute: async ({ sessionID, prompt }) => { + const result = await executeV2Command(service, command, { + sessionID, + prompt: prompt as { text?: string }, + }); + await ctx.session.synthetic({ sessionID, text: result }); + }, + }); + } + }); + + if (resolved) { + await registerMcpTransform(ctx, resolved, raw); + await registerAgentTransform(ctx, resolved); + await registerModelTransform(ctx, resolved); + await registerProviderTransform(ctx, resolved); + warnOnRemainingOverrides(resolved); + } + + const controller = new AbortController(); + void (async () => { + for await (const event of ctx.event.subscribe({ signal: controller.signal })) { + try { + await service.handleEvent(event); + } catch (error) { + console.error( + `[opencode-synced] Event handling failed: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + })(); + + const timer = setTimeout(() => { + void service.startupSync().catch((error: unknown) => { + console.error( + `[opencode-synced] Startup sync failed: ${error instanceof Error ? error.message : String(error)}` + ); + }); + }, 1000); + + return () => { + clearTimeout(timer); + controller.abort(); + }; +} + +export const opencodeSyncedV2 = Plugin.define({ + id: PLUGIN_ID, + setup: setupV2, +}); From cddacb12f0d1c00333d06ba84e6f045bc639a510 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:50:18 +0200 Subject: [PATCH 04/15] test: cover v2 dual export and domain transforms --- src/v2.test.ts | 284 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 src/v2.test.ts diff --git a/src/v2.test.ts b/src/v2.test.ts new file mode 100644 index 0000000..475a184 --- /dev/null +++ b/src/v2.test.ts @@ -0,0 +1,284 @@ +import { promises as fs } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import type { PluginInput } from '@opencode-ai/plugin'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('@opencode-ai/plugin', () => { + const createSchemaChain = (): Record unknown> => { + const chain = { + describe: () => chain, + optional: () => chain, + }; + return chain; + }; + return { + tool: Object.assign((definition: T): T => definition, { + schema: { + enum: () => createSchemaChain(), + string: () => createSchemaChain(), + boolean: () => createSchemaChain(), + array: () => createSchemaChain(), + }, + }), + }; +}); + +import pluginDefault, { opencodeConfigSync, opencodeSyncedV2 } from './index.js'; +import { resolveSyncLocations } from './sync/paths.js'; +import { parseCommandRepoArg, setupV2 } from './v2.js'; + +const ENV_KEYS = ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME'] as const; + +interface RecordedTool { + name: string; + description: string; + input: { required?: string[] }; + execute: (_input: Record) => Promise<{ content: string }>; +} + +interface RecordedCommand { + name: string; + description?: string; + execute: (_input: { sessionID: string; prompt: { text?: string } }) => Promise; +} + +interface MockCtx { + toolAdds: RecordedTool[]; + commandAdds: RecordedCommand[]; + mcpSets: [string, Record][]; + mcpUpdates: [string, Record][]; + subscribed: boolean; + synthetics: { sessionID: string; text: string }[]; + ctx: unknown; +} + +function createMockCtx(): MockCtx { + const mock: MockCtx = { + toolAdds: [], + commandAdds: [], + mcpSets: [], + mcpUpdates: [], + subscribed: false, + synthetics: [], + ctx: null, + }; + mock.ctx = { + tool: { + transform: async (callback: (_editor: unknown) => void) => { + callback({ + add: (definition: RecordedTool) => { + mock.toolAdds.push(definition); + }, + }); + return { dispose: async () => {} }; + }, + }, + command: { + transform: async (callback: (_editor: unknown) => void) => { + callback({ + add: (definition: RecordedCommand) => { + mock.commandAdds.push(definition); + }, + }); + return { dispose: async () => {} }; + }, + }, + mcp: { + transform: async (callback: (_editor: unknown) => void) => { + const draft = new Map>(); + callback({ + list: () => [...draft.entries()], + get: (name: string) => draft.get(name), + set: (name: string, config: Record) => { + mock.mcpSets.push([name, config]); + draft.set(name, config); + }, + update: (name: string, update: (_draft: Record) => void) => { + const current = draft.get(name) ?? {}; + update(current); + mock.mcpUpdates.push([name, current]); + draft.set(name, current); + }, + remove: (name: string) => { + draft.delete(name); + }, + }); + return { dispose: async () => {} }; + }, + }, + agent: { + transform: async () => ({ dispose: async () => {} }), + }, + model: { + default: async () => null, + transform: async () => ({ dispose: async () => {} }), + }, + provider: { + transform: async () => ({ dispose: async () => {} }), + }, + generate: { + text: async () => ({ text: 'Sync opencode config' }), + }, + session: { + synthetic: async (input: { sessionID: string; text: string }) => { + mock.synthetics.push(input); + return {}; + }, + }, + event: { + subscribe: () => { + mock.subscribed = true; + async function* empty(): AsyncGenerator {} + return empty(); + }, + }, + }; + return mock; +} + +function createV1Input(): PluginInput { + return { + $: (() => { + throw new Error('Shell execution is not expected in v2 server tests.'); + }) as unknown as PluginInput['$'], + client: { + app: { log: async () => ({}) }, + config: { get: async () => ({ data: {} }) }, + session: { + create: async () => ({ data: null }), + delete: async () => ({}), + prompt: async () => ({ data: null }), + status: async () => ({ data: {} }), + }, + tui: { showToast: async () => ({}) }, + } as unknown as PluginInput['client'], + } as PluginInput; +} + +async function withIsolatedHome(run: () => Promise): Promise { + const original = new Map(ENV_KEYS.map((key) => [key, process.env[key]])); + const homeDir = await fs.mkdtemp(path.join(tmpdir(), 'opencode-synced-v2-')); + process.env.HOME = homeDir; + process.env.XDG_CONFIG_HOME = path.join(homeDir, 'config'); + process.env.XDG_DATA_HOME = path.join(homeDir, 'data'); + process.env.XDG_STATE_HOME = path.join(homeDir, 'state'); + + try { + await run(); + } finally { + for (const [key, value] of original) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + await fs.rm(homeDir, { recursive: true, force: true }); + } +} + +describe('v2 dual export', () => { + it('exposes setup for v2 and server for v1 from one default export', () => { + expect(opencodeSyncedV2.id).toBe('opencode-synced'); + expect(typeof opencodeSyncedV2.setup).toBe('function'); + expect(typeof opencodeConfigSync).toBe('function'); + expect(pluginDefault.id).toBe('opencode-synced'); + expect(typeof pluginDefault.setup).toBe('function'); + expect(typeof pluginDefault.server).toBe('function'); + }); + + it('server() returns the v1 hooks', async () => { + const originalSetTimeout = globalThis.setTimeout; + globalThis.setTimeout = (() => 0) as unknown as typeof setTimeout; + try { + const hooks = await pluginDefault.server(createV1Input()); + expect(hooks.tool?.opencode_sync).toBeDefined(); + expect(typeof hooks.event).toBe('function'); + expect(typeof hooks.config).toBe('function'); + } finally { + globalThis.setTimeout = originalSetTimeout; + } + }); +}); + +describe('v2 setup', () => { + it('registers the sync tool and owned commands, with a status round-trip', async () => { + await withIsolatedHome(async () => { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + expect(mock.toolAdds).toHaveLength(1); + expect(mock.toolAdds[0].name).toBe('opencode_sync'); + expect(mock.toolAdds[0].input.required).toEqual(['command']); + + expect(mock.commandAdds.length).toBeGreaterThan(0); + expect(mock.commandAdds.map((command) => command.name)).toContain('sync-status'); + + const result = await mock.toolAdds[0].execute({ command: 'status' }); + expect(result.content).toContain('opencode-synced is not configured'); + expect(mock.subscribed).toBe(true); + } finally { + cleanup(); + } + }); + }); + + it('disables an mcp server with an unresolvable env placeholder', async () => { + await withIsolatedHome(async () => { + const locations = resolveSyncLocations(); + await fs.mkdir(locations.configRoot, { recursive: true }); + await fs.writeFile( + locations.overridesPath, + '{"mcp":{"github":{"type":"remote","url":"https://example.test/mcp","headers":{"Authorization":"Bearer {env:V2_MISSING_PAT}"}}}}\n', + 'utf8' + ); + delete process.env.V2_MISSING_PAT; + + const errors: unknown[][] = []; + const originalError = console.error; + console.error = (...args: unknown[]) => { + errors.push(args); + }; + try { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + expect(mock.mcpSets).toHaveLength(1); + expect(mock.mcpSets[0][0]).toBe('github'); + expect(mock.mcpSets[0][1]).toMatchObject({ disabled: true }); + expect(JSON.stringify(mock.mcpSets)).not.toContain('{env:'); + expect(errors.some((args) => JSON.stringify(args).includes('V2_MISSING_PAT'))).toBe(true); + } finally { + cleanup(); + } + } finally { + console.error = originalError; + } + }); + }); + + it('posts command results via session.synthetic', async () => { + await withIsolatedHome(async () => { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + const statusCommand = mock.commandAdds.find((command) => command.name === 'sync-status'); + expect(statusCommand).toBeDefined(); + await statusCommand?.execute({ sessionID: 'session-1', prompt: { text: '' } }); + expect(mock.synthetics).toHaveLength(1); + expect(mock.synthetics[0].sessionID).toBe('session-1'); + expect(mock.synthetics[0].text).toContain('opencode-synced is not configured'); + } finally { + cleanup(); + } + }); + }); +}); + +describe('parseCommandRepoArg', () => { + it('parses bare repo args and slash-prefixed invocations', () => { + expect(parseCommandRepoArg('owner/repo', 'sync-link')).toBe('owner/repo'); + expect(parseCommandRepoArg('/sync-link owner/repo', 'sync-link')).toBe('owner/repo'); + expect(parseCommandRepoArg('', 'sync-link')).toBeUndefined(); + expect(parseCommandRepoArg('/sync-link', 'sync-link')).toBeUndefined(); + }); +}); From feb3abd69fe4192c9fd61adf53a87b5e27c9a9be Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:51:11 +0200 Subject: [PATCH 05/15] docs: document v1/v2 requirements and config keys --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 703f61c..0093184 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,13 @@ an explicit-URL path for pre-created remotes. - Git installed and available on PATH - GitHub CLI (`gh`) installed and authenticated (`gh auth login`) when using automatic GitHub creation, discovery, or privacy verification +- opencode v1 `>= 1.18.29` **or** opencode v2 `^2.0.0` (one package supports both runtimes) ## Setup -Enable the plugin in your global opencode config (opencode will install it on next run): +Enable the plugin in your global opencode config (opencode will install it on next run). + +For opencode v1: ```jsonc { @@ -30,6 +33,15 @@ Enable the plugin in your global opencode config (opencode will install it on ne } ``` +For opencode v2: + +```jsonc +{ + "$schema": "https://opencode.ai/config.json", + "plugins": ["opencode-synced"], +} +``` + opencode does not auto-update plugins. To update, modify the version number in your config file. ## Configure @@ -350,7 +362,7 @@ bun -e ' ``` ### Manual steps -1. Remove `"opencode-synced"` from the `plugin` array in `~/.config/opencode/opencode.json` (or `.jsonc`). +1. Remove `"opencode-synced"` from the `plugin` array in `~/.config/opencode/opencode.json` (or `.jsonc`; v2 uses the `plugins` key). 2. Delete the local configuration and state: ```bash rm ~/.config/opencode/opencode-synced.jsonc From ca241449bbe071536dc38ff91cb68226053345d4 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 13:51:11 +0200 Subject: [PATCH 06/15] ci: assert dual v1/v2 entrypoint in build --- .github/workflows/pr.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ccbe24a..532a829 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -35,6 +35,21 @@ jobs: mise run test mise run build + - name: Assert dual v1/v2 entrypoint + run: | + bun -e ' + const plugin = await import("./dist/index.js"); + const entry = plugin.default ?? {}; + for (const key of ["id", "setup", "server"]) { + if (typeof entry[key] !== "string" && typeof entry[key] !== "function") { + throw new Error(`dist default export is missing v1/v2 entrypoint member: ${key}`); + } + } + if (entry.id !== "opencode-synced") throw new Error(`unexpected plugin id: ${entry.id}`); + if (typeof plugin.opencodeConfigSync !== "function") throw new Error("missing v1 opencodeConfigSync export"); + console.log("Dual v1/v2 entrypoint OK:", entry.id); + ' + WindowsPaths: runs-on: windows-latest steps: From 1e6ab65d3af10e27128f51549dd88598754de7a6 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:05:45 +0200 Subject: [PATCH 07/15] feat: add shared blankEnvPlaceholders helper --- src/sync/config.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sync/config.ts b/src/sync/config.ts index 1149ae7..d22e1cc 100644 --- a/src/sync/config.ts +++ b/src/sync/config.ts @@ -398,6 +398,26 @@ export function resolveEnvPlaceholders( return value; } +/** + * Blank `{env:…}` placeholders without throwing, using the same pattern as + * {@link resolveEnvPlaceholders}. Used in v2 to store a secret-free copy of an + * MCP server whose env var is missing (the server is also marked disabled). + * Prototype keys are skipped so the result is safe to pass to domain editors. + */ +export function blankEnvPlaceholders(value: unknown): unknown { + if (typeof value === 'string') return value.replace(/\{env:([^}]+)\}/g, ''); + if (Array.isArray(value)) return value.map(blankEnvPlaceholders); + if (isPlainObject(value)) { + const result: Record = {}; + for (const [key, nested] of Object.entries(value)) { + if (key === '__proto__') continue; + defineOwnValue(result, key, blankEnvPlaceholders(nested)); + } + return result; + } + return value; +} + export function deepMerge(base: T, override: unknown): T { if (!isPlainObject(base) || !isPlainObject(override)) { return (override === undefined ? base : override) as T; From c703436555d3cbfa54901713ede912ce689cde16 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:05:53 +0200 Subject: [PATCH 08/15] feat: add SyncService.dispose for background timers --- src/sync/service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sync/service.ts b/src/sync/service.ts index fdadb53..0e0a8dd 100644 --- a/src/sync/service.ts +++ b/src/sync/service.ts @@ -124,6 +124,8 @@ export interface SyncService { sessionsMigrateTurso: (_options?: { setupTurso?: boolean }) => Promise; sessionsCleanupGit: () => Promise; resolve: () => Promise; + /** Stop background timers (Turso sync loop + idle flush). Idempotent. */ + dispose: () => void; } export function createSyncService(ctx: SyncServiceContext): SyncService { @@ -1395,6 +1397,9 @@ export function createSyncService(ctx: SyncServiceContext): SyncService { return `Unable to automatically resolve. Please manually resolve in: ${repoRoot}`; }), + dispose: () => { + stopTursoSyncLoop(); + }, }; } From 1aca4211f140a980037ec8f7513b86de1a41706c Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:06:00 +0200 Subject: [PATCH 09/15] docs: clarify node shell shim differences from Bun --- src/shell-node.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shell-node.ts b/src/shell-node.ts index 2d80dfb..b32d664 100644 --- a/src/shell-node.ts +++ b/src/shell-node.ts @@ -21,6 +21,12 @@ interface NodeShellCommand extends Promise<{ stdout: string; stderr: string }> { * commands through this shim. Interpolated values are single-quote escaped, * matching v1 behavior. `.quiet()` is a no-op kept for call-site compatibility, * `.text()` resolves stdout, and awaiting the command throws on non-zero exit. + * + * Differences from Bun `$` to be aware of when debugging service failures: + * - Runs via `child_process.exec` (`/bin/sh`), not bash; keep commands POSIX. + * - No `cwd`/`env` options; inherits the plugin host process env. + * - `maxBuffer` is 32 MiB; larger git output throws `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`. + * - Rejection is an `exec` Error (with `stdout`/`stderr` props), not a Bun ShellError. */ export function createNodeShell(): PluginInput['$'] { const shell = (strings: TemplateStringsArray, ...values: unknown[]): NodeShellCommand => { From 4efe5c66a2337b4bca1d7d8c37ab908cee0513ec Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:06:19 +0200 Subject: [PATCH 10/15] refactor: harden v2 transforms and setup Single-read override loading and shared merge helpers. Keep replay callbacks pure with warnings outside transforms. Set-based command lookup, hardened arg parsing, v2 logging. --- src/v2.ts | 367 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 235 insertions(+), 132 deletions(-) diff --git a/src/v2.ts b/src/v2.ts index 8d57b50..9e73dc5 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -4,15 +4,18 @@ import type { PluginInput } from '@opencode-ai/plugin'; import { buildSyncToolInputSchema, - disableMcpServerForResolutionFailure, executeSyncCommand, loadCommands, type ParsedCommand, + SYNC_TOOL_COMMANDS, type SyncToolArgs, + type SyncToolCommand, } from './shared.js'; import { createNodeShell } from './shell-node.js'; import type { AiProvider } from './sync/ai.js'; import { + blankEnvPlaceholders, + deepMerge, EnvPlaceholderResolutionError, isPlainObject, loadOverrides, @@ -25,134 +28,194 @@ const PLUGIN_ID = 'opencode-synced'; const OVERRIDES_DOC_POINTER = 'opencode-synced: ignoring unsupported override key (v2 applies only mcp/agent/model/provider via domain transforms; see https://opencode.ai/v2/docs/build/plugins/migrate-v1)'; +/** Prefix all v2 console output so it is greppable; v2 has no toast/log sink. */ +function v2Log(message: string): void { + console.log(`[opencode-synced] ${message}`); +} + +function v2Warn(message: string): void { + console.warn(`[opencode-synced] ${message}`); +} + +function v2Error(message: string): void { + console.error(`[opencode-synced] ${message}`); +} + +interface OverrideFailure { + fieldPath: readonly string[]; + message: string; +} + interface ResolvedOverrides { + /** Raw document as loaded (used for secret-free fallback configs). */ + raw: Record; + /** Per-top-level-key resolved values; keys with failures are omitted. */ values: Record; /** Field paths (e.g. ['overrides','mcp','github',...]) that failed {env:…} resolution. */ - failures: { fieldPath: readonly string[]; message: string }[]; + failures: OverrideFailure[]; } +/** + * Load overrides once and resolve `{env:…}` per top-level key. + * Single read avoids TOCTOU drift between the resolved values and the raw + * fallback used for disabled MCP servers. + */ async function loadResolvedOverrides(): Promise { - const overrides = await loadOverrides(resolveSyncLocations()); - if (!overrides) return null; + const raw = await loadOverrides(resolveSyncLocations()); + if (!raw) return null; const values: Record = {}; - const failures: ResolvedOverrides['failures'] = []; - for (const [key, value] of Object.entries(overrides)) { + const failures: OverrideFailure[] = []; + for (const [key, value] of Object.entries(raw)) { try { values[key] = resolveEnvPlaceholders(value, process.env, ['overrides', key]); } catch (error) { if (error instanceof EnvPlaceholderResolutionError) { failures.push({ fieldPath: error.fieldPath, message: error.message }); - console.error(`[opencode-synced] ${error.message}`); + v2Error(error.message); } else { throw error; } } } - return { values, failures }; + return { raw, values, failures }; } -function applyDeepMerge(target: Record, source: unknown): void { - if (!isPlainObject(source)) return; - for (const [key, value] of Object.entries(source)) { +/** + * Merge `partial` into a mutable domain-editor draft in place. + * Reuses the shared `deepMerge` so v1 (`applyOverridesToRuntimeConfig`) and + * v2 transforms share merge semantics (including prototype-safe assignment). + */ +function mergeIntoDraft(draft: Record, partial: Record): void { + const merged = deepMerge(draft, partial) as Record; + for (const [key, value] of Object.entries(merged)) { if (key === '__proto__') continue; - const current = target[key]; - if (isPlainObject(value) && isPlainObject(current)) { - applyDeepMerge(current as Record, value); - } else { - Object.defineProperty(target, key, { - value, - enumerable: true, - configurable: true, - writable: true, - }); - } + (draft as Record)[key] = value; } } -/** Blank any unresolvable {env:…} placeholders so a disabled server holds no secrets. */ -function blankPlaceholders(value: unknown): unknown { - if (typeof value === 'string') return value.replace(/\{env:[^}]+\}/g, ''); - if (Array.isArray(value)) return value.map(blankPlaceholders); - if (isPlainObject(value)) { - const result: Record = {}; - for (const [key, nested] of Object.entries(value)) { - if (key === '__proto__') continue; - result[key] = blankPlaceholders(nested); - } - return result; - } - return value; -} - -function failedServerNames(failures: ResolvedOverrides['failures']): Set { +function failedServerNames(failures: OverrideFailure[]): Set { const names = new Set(); for (const failure of failures) { if (failure.fieldPath[0] === 'overrides' && failure.fieldPath[1] === 'mcp') { const serverName = failure.fieldPath[2]; - if (serverName) names.add(serverName); + if (typeof serverName === 'string' && serverName) names.add(serverName); } } return names; } -async function registerMcpTransform( - ctx: V2Context, - resolved: ResolvedOverrides, - raw: Record | null -): Promise { +async function registerMcpTransform(ctx: V2Context, resolved: ResolvedOverrides): Promise { const mcp = isPlainObject(resolved.values.mcp) ? (resolved.values.mcp as Record) : null; const failed = failedServerNames(resolved.failures); - const rawMcp = raw && isPlainObject(raw.mcp) ? (raw.mcp as Record) : null; + const rawMcp = isPlainObject(resolved.raw.mcp) + ? (resolved.raw.mcp as Record) + : null; if (!mcp && failed.size === 0) return; - await ctx.mcp.transform((editor) => { - if (mcp) { - for (const [name, config] of Object.entries(mcp)) { - if (!isPlainObject(config)) continue; - if (editor.get(name)) { - editor.update(name, (draft) => { - applyDeepMerge(draft as unknown as Record, config); - }); - } else { - editor.set(name, config as never); - } - } + // Snapshot plain-object configs up front so the replay callback is pure: + // no I/O, no logging, no closure mutation when core replays it. + const upserts = new Map>(); + if (mcp) { + for (const [name, config] of Object.entries(mcp)) { + if (isPlainObject(config)) upserts.set(name, config as Record); } - for (const name of failed) { - const rawConfig = rawMcp && isPlainObject(rawMcp[name]) ? rawMcp[name] : {}; - const disabledConfig = { - ...(blankPlaceholders(rawConfig) as Record), - disabled: true, - }; + } + for (const name of failed) { + const rawConfig = + rawMcp && isPlainObject(rawMcp[name]) ? (rawMcp[name] as Record) : {}; + upserts.set(name, { + ...(blankEnvPlaceholders(rawConfig) as Record), + disabled: true, + }); + } + + await ctx.mcp.transform((editor) => { + for (const [name, config] of upserts) { if (editor.get(name)) { editor.update(name, (draft) => { - applyDeepMerge(draft as unknown as Record, disabledConfig); + mergeIntoDraft(draft as unknown as Record, config); }); } else { - editor.set(name, disabledConfig as never); + editor.set(name, config as never); } - // Mirror the v1 runtime-config fallback so status output stays consistent. - disableMcpServerForResolutionFailure({ mcp: { [name]: {} } }, ['overrides', 'mcp', name]); } }); } +/** Structural warnings that need no runtime state; unknown IDs are checked below. */ +function collectAgentWarnings(agents: unknown): string[] { + if (!isPlainObject(agents)) return []; + const warnings: string[] = []; + for (const [id, partial] of Object.entries(agents)) { + if (!isPlainObject(partial)) + warnings.push(`Ignoring agent override for "${id}": not an object.`); + } + return warnings; +} + +/** Unwrap `{data: [...]}` envelopes or plain arrays from list() APIs. */ +function asListItems(value: unknown): unknown[] { + const unwrapped = + isPlainObject(value) && 'data' in value ? (value as { data?: unknown }).data : value; + return Array.isArray(unwrapped) ? unwrapped : []; +} + +function idOf(item: unknown): string | null { + if (typeof item === 'string') return item; + if (!isPlainObject(item)) return null; + const direct = (item as Record).id; + if (typeof direct === 'string') return direct; + const provider = (item as Record).provider; + if (isPlainObject(provider) && typeof provider.id === 'string') return provider.id; + const providerID = (item as Record).providerID; + if (typeof providerID === 'string') return providerID; + return null; +} + +/** Best-effort known-ID lookup; returns null when the host API is unavailable. */ +async function knownIds(listFn: () => Promise): Promise | null> { + try { + const result = await listFn(); + const ids = new Set(); + for (const item of asListItems(result)) { + const id = idOf(item); + if (id) ids.add(id); + } + return ids; + } catch { + return null; + } +} + async function registerAgentTransform(ctx: V2Context, resolved: ResolvedOverrides): Promise { const agents = resolved.values.agent; if (!isPlainObject(agents)) return; + const snapshot = new Map>(); + for (const [id, partial] of Object.entries(agents)) { + if (isPlainObject(partial)) snapshot.set(id, partial as Record); + } + if (snapshot.size === 0) return; + + // Best-effort unknown-agent warning outside the replay callback so replays + // stay side-effect-free. Agents cannot be created via the editor (update-only), + // so unknown IDs are skipped silently inside the transform. + const known = await knownIds(() => ctx.agent.list() as unknown as Promise); + if (known) { + for (const id of snapshot.keys()) { + if (!known.has(id)) v2Warn(`Ignoring override for unknown agent "${id}".`); + } + } + for (const warning of collectAgentWarnings(agents)) v2Warn(warning); await ctx.agent.transform((editor) => { - for (const [id, partial] of Object.entries(agents)) { - if (!isPlainObject(partial)) continue; + for (const [id, partial] of snapshot) { if (editor.get(id)) { editor.update(id, (draft) => { - applyDeepMerge(draft as unknown as Record, partial); + mergeIntoDraft(draft as unknown as Record, partial); }); - } else { - console.warn(`[opencode-synced] Ignoring override for unknown agent "${id}".`); } } }); @@ -162,28 +225,52 @@ async function registerModelTransform(ctx: V2Context, resolved: ResolvedOverride const models = resolved.values.model; if (!isPlainObject(models)) return; - await ctx.model.transform((editor) => { - for (const [providerID, providerModels] of Object.entries(models)) { - if (!isPlainObject(providerModels)) { - console.warn( - `[opencode-synced] Ignoring model override for "${providerID}". ${OVERRIDES_DOC_POINTER}: model` - ); - continue; + const snapshot = new Map>>(); + const structuralWarnings: string[] = []; + for (const [providerID, providerModels] of Object.entries(models)) { + if (!isPlainObject(providerModels)) { + structuralWarnings.push( + `Ignoring model override for "${providerID}". ${OVERRIDES_DOC_POINTER}: model` + ); + continue; + } + const perModel = new Map>(); + for (const [modelID, partial] of Object.entries(providerModels)) { + if (isPlainObject(partial)) perModel.set(modelID, partial as Record); + } + if (perModel.size > 0) snapshot.set(providerID, perModel); + } + if (snapshot.size === 0) { + for (const warning of structuralWarnings) v2Warn(warning); + return; + } + + // Provider inventory lives on ctx.provider (ModelDomain has no provider + // accessor); warn outside the replay callback, guard with editor.get inside. + const knownProviders = await knownIds(() => ctx.provider.list() as unknown as Promise); + if (knownProviders) { + for (const providerID of snapshot.keys()) { + if (!knownProviders.has(providerID)) { + v2Warn(`Ignoring override for unknown model provider "${providerID}".`); } - for (const [modelID, partial] of Object.entries(providerModels)) { - if (!isPlainObject(partial)) continue; + } + } + for (const warning of structuralWarnings) v2Warn(warning); + + await ctx.model.transform((editor) => { + for (const [providerID, perModel] of snapshot) { + for (const [modelID, partial] of perModel) { if (editor.get(providerID, modelID)) { editor.update(providerID, modelID, (draft) => { - applyDeepMerge(draft as unknown as Record, partial); + mergeIntoDraft(draft as unknown as Record, partial); }); - } else { - console.warn( - `[opencode-synced] Ignoring override for unknown model "${providerID}/${modelID}".` - ); } } } }); + // Unknown model IDs are skipped silently inside the replay callback (v2 has + // no model editor "has" bulk API); structural + provider warnings above cover + // the actionable cases without spamming on every replay. } async function registerProviderTransform( @@ -192,16 +279,25 @@ async function registerProviderTransform( ): Promise { const providers = resolved.values.provider; if (!isPlainObject(providers)) return; + const snapshot = new Map>(); + for (const [providerID, partial] of Object.entries(providers)) { + if (isPlainObject(partial)) snapshot.set(providerID, partial as Record); + } + if (snapshot.size === 0) return; + + const known = await knownIds(() => ctx.provider.list() as unknown as Promise); + if (known) { + for (const id of snapshot.keys()) { + if (!known.has(id)) v2Warn(`Ignoring override for unknown provider "${id}".`); + } + } await ctx.provider.transform((editor) => { - for (const [providerID, partial] of Object.entries(providers)) { - if (!isPlainObject(partial)) continue; + for (const [providerID, partial] of snapshot) { if (editor.get(providerID)) { editor.update(providerID, (draft) => { - applyDeepMerge(draft as unknown as Record, partial); + mergeIntoDraft(draft as unknown as Record, partial); }); - } else { - console.warn(`[opencode-synced] Ignoring override for unknown provider "${providerID}".`); } } }); @@ -211,15 +307,20 @@ function warnOnRemainingOverrides(resolved: ResolvedOverrides): void { for (const key of Object.keys(resolved.values)) { if (key === 'mcp' || key === 'agent' || key === 'model' || key === 'provider') continue; if (key === 'command') { - console.warn( - '[opencode-synced] Ignoring "command" overrides in v2: sync commands are owned by this plugin. ' + + v2Warn( + 'Ignoring "command" overrides in v2: sync commands are owned by this plugin. ' + 'See https://opencode.ai/v2/docs/build/plugins/migrate-v1' ); continue; } - console.warn( - `[opencode-synced] Ignoring unsupported override key "${key}". ${OVERRIDES_DOC_POINTER}: ${key}` - ); + v2Warn(`Ignoring unsupported override key "${key}". ${OVERRIDES_DOC_POINTER}: ${key}`); + } + for (const failure of resolved.failures) { + if (failure.fieldPath[1] !== 'mcp') { + v2Warn( + `Ignoring override with unresolvable placeholder at "${failure.fieldPath.join('.')}": ${failure.message}` + ); + } } } @@ -227,8 +328,10 @@ function warnOnRemainingOverrides(resolved: ResolvedOverrides): void { * Minimal v1 client facade over v2 capabilities. * * V2 has no toasts and no session-status API, so toasts are no-ops (the service - * also logs via app.log, which maps to console here) and session status always - * reports idle, which skips Turso idle-gating and syncs immediately. + * also logs via app.log, which maps to console here). Session status returns an + * empty map, which `areAllSessionsIdle` treats as "no busy sessions" after its + * double-poll: Turso idle-gating is therefore intentionally skipped and Turso + * syncs run immediately in v2. Covered by `v2.test.ts` ("turso gating"). */ function createV2ClientFacade(): PluginInput['client'] { return { @@ -290,39 +393,29 @@ function createV2AiProvider(ctx: V2Context): AiProvider { }; } -function toolCommandForName(name: string): SyncToolArgs['command'] | null { +const SYNC_COMMAND_NAMES = new Set(SYNC_TOOL_COMMANDS as readonly string[]); + +function toolCommandForName(name: string): SyncToolCommand | null { const suffix = name.startsWith('sync-') ? name.slice('sync-'.length) : name; - switch (suffix) { - case 'status': - case 'pull': - case 'push': - case 'resolve': - case 'secrets-pull': - case 'secrets-push': - case 'secrets-status': - case 'sessions-cleanup-git': - return suffix; - case 'init': - case 'link': - return suffix; - case 'enable-secrets': - return 'enable-secrets'; - case 'sessions-backend': - case 'sessions-setup-turso': - case 'sessions-migrate-turso': - return suffix; - default: - return null; - } + return SYNC_COMMAND_NAMES.has(suffix) ? (suffix as SyncToolCommand) : null; } -/** Parse a bare repo argument from a slash-command invocation (e.g. `/sync-link owner/repo`). */ +/** + * Parse a bare repo argument from a slash-command invocation + * (e.g. `/sync-link owner/repo`). + * + * V2 slash commands only carry free text (`prompt.text`), not structured tool + * args, so only a single bare `owner/repo` (or URL) is supported. Quoted + * multi-word input uses the first token; anything beyond `init`/`link` repo + * and `sessions-backend` backend is intentionally ignored (documented limit). + */ export function parseCommandRepoArg( text: string | undefined, commandName: string ): string | undefined { if (!text) return undefined; let arg = text.trim(); + if (!arg) return undefined; if (arg.startsWith('/')) { const firstSpace = arg.indexOf(' '); if (firstSpace === -1) return undefined; @@ -331,7 +424,11 @@ export function parseCommandRepoArg( arg = arg.slice(commandName.length).trim(); } if (arg.startsWith('$ARGUMENTS')) arg = arg.slice('$ARGUMENTS'.length).trim(); - return arg || undefined; + if (!arg) return undefined; + // Strip surrounding quotes, then take the first whitespace-separated token. + arg = arg.replace(/^["']+|["']+$/g, '').trim(); + const firstToken = arg.split(/\s+/)[0]; + return firstToken || undefined; } async function executeV2Command( @@ -388,8 +485,10 @@ export async function setupV2(ctx: V2Context): Promise<() => void> { }); // Load + resolve {env:…} once before registering replayable transforms. + // NOTE: transforms are re-applied by core and never re-read disk; a config + // change requires host restart (or host-triggered `reload()`). No file + // watcher here by design — see docs/v2.md. const resolved = await loadResolvedOverrides(); - const raw = await loadOverrides(resolveSyncLocations()); await ctx.tool.transform((editor) => { editor.add({ @@ -398,11 +497,17 @@ export async function setupV2(ctx: V2Context): Promise<() => void> { input: buildSyncToolInputSchema() as never, execute: async (input) => { const result = await executeSyncCommand(service, input as unknown as SyncToolArgs); - return { content: result } as never; + // Tool.Result.content accepts string | Content[]; plain string keeps + // large status outputs readable without manual TextContent wrapping. + return { content: result }; }, }); }); + // V2 `CommandDefinition` only carries name/description/execute (no + // template/agent/model/subtask like v1 `config.command`). The markdown + // template is therefore executed directly via `executeV2Command` + synthetic + // reply instead of being registered as a prompt template. await ctx.command.transform((editor) => { for (const command of commands) { editor.add({ @@ -420,7 +525,7 @@ export async function setupV2(ctx: V2Context): Promise<() => void> { }); if (resolved) { - await registerMcpTransform(ctx, resolved, raw); + await registerMcpTransform(ctx, resolved); await registerAgentTransform(ctx, resolved); await registerModelTransform(ctx, resolved); await registerProviderTransform(ctx, resolved); @@ -433,24 +538,22 @@ export async function setupV2(ctx: V2Context): Promise<() => void> { try { await service.handleEvent(event); } catch (error) { - console.error( - `[opencode-synced] Event handling failed: ${error instanceof Error ? error.message : String(error)}` - ); + v2Error(`Event handling failed: ${error instanceof Error ? error.message : String(error)}`); } } })(); const timer = setTimeout(() => { void service.startupSync().catch((error: unknown) => { - console.error( - `[opencode-synced] Startup sync failed: ${error instanceof Error ? error.message : String(error)}` - ); + v2Error(`Startup sync failed: ${error instanceof Error ? error.message : String(error)}`); }); }, 1000); + v2Log('v2 setup complete'); return () => { clearTimeout(timer); controller.abort(); + service.dispose(); }; } From 2343c8474a401fbb8e6c6f1f5ccc4d79ea7f977a Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:06:27 +0200 Subject: [PATCH 11/15] fix: use explicit dual entrypoint fields --- src/index.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 58e2fd4..3d93424 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { } from './sync/config.js'; import { resolveSyncLocations } from './sync/paths.js'; import { createSyncService } from './sync/service.js'; -import { opencodeSyncedV2 } from './v2.js'; +import { setupV2 } from './v2.js'; export const opencodeConfigSync: Plugin = async (ctx) => { const commands = await loadCommands(); @@ -154,11 +154,16 @@ export { opencodeSyncedV2 } from './v2.js'; * Dual v1 + v2 entrypoint. * * V2 calls `setup()` (registered via `Plugin.define`); v1 (>= 1.18.29 object - * entrypoints) calls `server()` with the v1 plugin context. + * entrypoints) calls `server()` with the v1 plugin context. Fields are listed + * explicitly (no spread) so adding a field to one runtime cannot silently leak + * into the other. */ -export default { - ...opencodeSyncedV2, +const pluginDefault = { + id: 'opencode-synced' as const, + setup: setupV2, async server(ctx: Parameters[0]) { return await opencodeConfigSync(ctx); }, }; + +export default pluginDefault; From c227a8e25b8ccd758145e7aab7ab54b5cefbc645 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:06:36 +0200 Subject: [PATCH 12/15] test: cover v2 replay purity and arg parsing --- src/v2.test.ts | 146 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/src/v2.test.ts b/src/v2.test.ts index 475a184..b76496e 100644 --- a/src/v2.test.ts +++ b/src/v2.test.ts @@ -44,13 +44,21 @@ interface RecordedCommand { execute: (_input: { sessionID: string; prompt: { text?: string } }) => Promise; } +type TransformCallback = (_editor: unknown) => void; + interface MockCtx { toolAdds: RecordedTool[]; commandAdds: RecordedCommand[]; mcpSets: [string, Record][]; mcpUpdates: [string, Record][]; + agentUpdates: [string, Record][]; + providerUpdates: [string, Record][]; + modelUpdates: [string, string, Record][]; + transformCallbacks: Record; subscribed: boolean; synthetics: { sessionID: string; text: string }[]; + knownAgents: { id: string }[]; + knownProviders: { provider: { id: string } }[]; ctx: unknown; } @@ -60,13 +68,23 @@ function createMockCtx(): MockCtx { commandAdds: [], mcpSets: [], mcpUpdates: [], + agentUpdates: [], + providerUpdates: [], + modelUpdates: [], + transformCallbacks: { mcp: [], agent: [], model: [], provider: [], tool: [], command: [] }, subscribed: false, synthetics: [], + knownAgents: [], + knownProviders: [], ctx: null, }; + const recordTransform = (domain: string, callback: TransformCallback): void => { + mock.transformCallbacks[domain].push(callback); + }; mock.ctx = { tool: { transform: async (callback: (_editor: unknown) => void) => { + recordTransform('tool', callback); callback({ add: (definition: RecordedTool) => { mock.toolAdds.push(definition); @@ -77,6 +95,7 @@ function createMockCtx(): MockCtx { }, command: { transform: async (callback: (_editor: unknown) => void) => { + recordTransform('command', callback); callback({ add: (definition: RecordedCommand) => { mock.commandAdds.push(definition); @@ -87,6 +106,7 @@ function createMockCtx(): MockCtx { }, mcp: { transform: async (callback: (_editor: unknown) => void) => { + recordTransform('mcp', callback); const draft = new Map>(); callback({ list: () => [...draft.entries()], @@ -109,14 +129,65 @@ function createMockCtx(): MockCtx { }, }, agent: { - transform: async () => ({ dispose: async () => {} }), + list: async () => mock.knownAgents, + transform: async (callback: (_editor: unknown) => void) => { + recordTransform('agent', callback); + const draft = new Map>(); + callback({ + list: () => [...draft.values()], + get: (id: string) => draft.get(id) ?? mock.knownAgents.find((a) => a.id === id), + update: (id: string, update: (_draft: Record) => void) => { + const current = draft.get(id) ?? {}; + update(current); + mock.agentUpdates.push([id, current]); + draft.set(id, current); + }, + }); + return { dispose: async () => {} }; + }, }, model: { default: async () => null, - transform: async () => ({ dispose: async () => {} }), + provider: { + list: async () => mock.knownProviders, + }, + transform: async (callback: (_editor: unknown) => void) => { + recordTransform('model', callback); + const draft = new Map>(); + callback({ + get: (providerID: string, modelID: string) => draft.get(`${providerID}/${modelID}`), + update: ( + providerID: string, + modelID: string, + update: (_draft: Record) => void + ) => { + const key = `${providerID}/${modelID}`; + const current = draft.get(key) ?? {}; + update(current); + mock.modelUpdates.push([providerID, modelID, current]); + draft.set(key, current); + }, + }); + return { dispose: async () => {} }; + }, }, provider: { - transform: async () => ({ dispose: async () => {} }), + list: async () => mock.knownProviders, + transform: async (callback: (_editor: unknown) => void) => { + recordTransform('provider', callback); + const draft = new Map>(); + callback({ + list: () => [...draft.entries()], + get: (id: string) => draft.get(id) ?? undefined, + update: (id: string, update: (_draft: Record) => void) => { + const current = draft.get(id) ?? {}; + update(current); + mock.providerUpdates.push([id, current]); + draft.set(id, current); + }, + }); + return { dispose: async () => {} }; + }, }, generate: { text: async () => ({ text: 'Sync opencode config' }), @@ -177,13 +248,16 @@ async function withIsolatedHome(run: () => Promise): Promise { } describe('v2 dual export', () => { - it('exposes setup for v2 and server for v1 from one default export', () => { + it('exposes setup for v2 and server for v1 from one explicit default export', () => { expect(opencodeSyncedV2.id).toBe('opencode-synced'); expect(typeof opencodeSyncedV2.setup).toBe('function'); expect(typeof opencodeConfigSync).toBe('function'); expect(pluginDefault.id).toBe('opencode-synced'); expect(typeof pluginDefault.setup).toBe('function'); expect(typeof pluginDefault.server).toBe('function'); + // No spread leakage: exactly the documented fields. + expect(Object.keys(pluginDefault).sort()).toEqual(['id', 'server', 'setup']); + expect(pluginDefault.setup).toBe(opencodeSyncedV2.setup); }); it('server() returns the v1 hooks', async () => { @@ -214,6 +288,7 @@ describe('v2 setup', () => { expect(mock.commandAdds.map((command) => command.name)).toContain('sync-status'); const result = await mock.toolAdds[0].execute({ command: 'status' }); + expect(typeof result.content).toBe('string'); expect(result.content).toContain('opencode-synced is not configured'); expect(mock.subscribed).toBe(true); } finally { @@ -256,6 +331,52 @@ describe('v2 setup', () => { }); }); + it('warns once for unknown agent/provider overrides and keeps replays pure', async () => { + await withIsolatedHome(async () => { + const locations = resolveSyncLocations(); + await fs.mkdir(locations.configRoot, { recursive: true }); + await fs.writeFile( + locations.overridesPath, + JSON.stringify({ + agent: { ghost: { temperature: 0.1 } }, + provider: { ghost: { name: 'x' } }, + }), + 'utf8' + ); + + const warnings: unknown[][] = []; + const originalWarn = console.warn; + console.warn = (...args: unknown[]) => { + warnings.push(args); + }; + try { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + expect(warnings.some((a) => JSON.stringify(a).includes('ghost'))).toBe(true); + const warnedCount = warnings.length; + // Replaying captured transform callbacks must not warn again. + for (const domain of ['agent', 'provider'] as const) { + for (const callback of mock.transformCallbacks[domain]) { + callback({ + get: () => undefined, + update: () => { + throw new Error('should not update unknown id'); + }, + list: () => [], + }); + } + } + expect(warnings.length).toBe(warnedCount); + } finally { + cleanup(); + } + } finally { + console.warn = originalWarn; + } + }); + }); + it('posts command results via session.synthetic', async () => { await withIsolatedHome(async () => { const mock = createMockCtx(); @@ -272,6 +393,16 @@ describe('v2 setup', () => { } }); }); + + it('cleanup is idempotent and stops background work', async () => { + await withIsolatedHome(async () => { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + expect(typeof cleanup).toBe('function'); + cleanup(); + expect(() => cleanup()).not.toThrow(); + }); + }); }); describe('parseCommandRepoArg', () => { @@ -281,4 +412,11 @@ describe('parseCommandRepoArg', () => { expect(parseCommandRepoArg('', 'sync-link')).toBeUndefined(); expect(parseCommandRepoArg('/sync-link', 'sync-link')).toBeUndefined(); }); + + it('handles quotes, $ARGUMENTS, and extra tokens (first token wins)', () => { + expect(parseCommandRepoArg('$ARGUMENTS owner/repo', 'sync-link')).toBe('owner/repo'); + expect(parseCommandRepoArg('/sync-link "owner/repo"', 'sync-link')).toBe('owner/repo'); + expect(parseCommandRepoArg('owner/repo extra words', 'sync-link')).toBe('owner/repo'); + expect(parseCommandRepoArg(' ', 'sync-link')).toBeUndefined(); + }); }); From 69f6ab21826bb35a7669869ef600c65187814256 Mon Sep 17 00:00:00 2001 From: Jonas Vacek Date: Tue, 22 Sep 2026 14:06:43 +0200 Subject: [PATCH 13/15] docs: rewrite v2 support doc to match implementation --- docs/v2.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/v2.md diff --git a/docs/v2.md b/docs/v2.md new file mode 100644 index 0000000..9e0ce32 --- /dev/null +++ b/docs/v2.md @@ -0,0 +1,89 @@ +V1 + V2 plugin support + +Goal +One package supporting both opencode v1 (`@opencode-ai/plugin`, `plugin` config +key) and v2 (`@opencode/plugin`, `plugins` config key), per +https://opencode.ai/v2/docs/build/plugins/migrate-v1. +Minimums: v1 >= 1.18.29 (object entrypoints), v2 ^2.0.0. Both packages are +runtime dependencies because the dual entrypoint (`src/index.ts`) imports both. + +Why v2 does config differently +V1 hands plugins one mutable config object (`config(config)` in `src/index.ts`). +V2 replaces it with replayable, synchronous, per-domain transforms (core replays +all transforms in order onto a fresh value). So there is no global-merge shim: +each override key maps to the domain that owns it. Overrides are loaded + +`{env:…}`-resolved once in `setupV2` (`src/v2.ts`) before registering; +transform callbacks are pure snapshots (no I/O, no logging) so replays are +side-effect-free. Disk is never re-read inside a transform; an overrides change +needs a host restart (or host-triggered `reload()` — we do not call `reload()` +and run no file watcher by design). + +Overrides mapping +Override key V2 destination Notes +mcp `ctx.mcp.transform` (set/update per server) Unresolvable `{env:…}`: store secret-free copy (`blankEnvPlaceholders` in `src/sync/config.ts`) with `disabled:true` + `console.error`. Mirrors v1 `disableMcpServerForResolutionFailure` intent without mutating caller state. +agent `ctx.agent.transform` (update-only) Editor cannot create agents; unknown IDs warn once outside the transform, skip silently inside. +model `ctx.model.transform` (update-only) Non-object shapes warn; unknown provider IDs warn via `ctx.model.provider.list()` best-effort; unknown model IDs skip silently (no bulk "has" API). +provider `ctx.provider.transform` (update-only) Unknown IDs warn once outside via `ctx.provider.list()`, skip silently inside. +command Ignored with warn Sync commands are owned by this plugin and registered via `ctx.command.transform` from `src/command/*.md`. External `command` overrides are not applied. +everything else `console.warn` with key name + docs pointer No fake global merge. + +File-level behavior (`syncRepoToLocal`/`syncLocalToRepo`, `stripOverrides` in +`src/sync/apply.ts`, `src/sync/config.ts`) is runtime-independent and unchanged. + +V2 command + tool limits (documented, not bugs) +- `CommandDefinition` only carries `name/description/execute` — unlike v1 + `config.command` there is no `template/agent/model/subtask`. The md template + is executed directly: run service → post via `ctx.session.synthetic`. +- Slash commands only carry free text (`prompt.text`). Only a single bare + `owner/repo` (or URL) is parsed (`parseCommandRepoArg` in `src/v2.ts` takes + the first token, strips quotes/`$ARGUMENTS`); `init`/`link` extra flags and + `enable-secrets`/Turso options are not parseable from slash text — use the + `opencode_sync` tool for full args. +- `Tool.Result.content` accepts `string | Content[]`; we return a plain string + to keep status output readable. + +AI commit messages (kosher v2) +V1 throwaway-session flow (`session.create → prompt → delete` in +`src/sync/ai.ts`) is replaced with `ctx.model.default()` + +`ctx.generate.text({model, prompt})` (`createV2AiProvider` in `src/v2.ts`). +Same prompts/sanitization (72-char single line in `src/sync/ai.ts`, +`src/sync/commit.ts`); fallback `Sync opencode config (YYYY-MM-DD)`. Applies to +`generateCommitMessage` and `/sync-resolve` analysis. Returns `null` (→ fallback) +when no model is available. + +Implementation map +1. Shared core — `src/shared.ts`: md loading (`loadCommands`), tool args + (`SYNC_TOOL_COMMANDS`, `buildSyncToolInputSchema`), `executeSyncCommand`. +2. `src/shell-node.ts` — Node `child_process.exec` (`/bin/sh`) `$` shim + (`quiet()`/`text()`/throw-on-nonzero; v2 has no `$`). POSIX-only, inherits + host env, 32 MiB `maxBuffer`, `exec`-shaped errors (not Bun `ShellError`). +3. `src/v2.ts` — `setupV2`: console-prefixed logging (`v2Log/v2Warn/v2Error`; + no-op toast — v2 has neither toast nor log sink), session-status facade + returns `{data:{}}` (empty = idle → Turso idle-gating intentionally skipped, + syncs immediately), AI via `generate.text`. Registers tool (JSON Schema, + `required:["command"]`), commands (parse bare repo arg, post via + `session.synthetic`), mcp/agent/model/provider transforms, + `event.subscribe` → `service.handleEvent`, timed startup sync with dispose + cleanup (`clearTimeout` + `abort` + `service.dispose()` which stops the + Turso sync loop/idle-flush timers). +4. Dual entrypoint (`src/index.ts`) — `opencodeConfigSync` untouched; explicit + default export `{ id, setup: setupV2, server }` (no spread so runtimes do not + leak fields). Deps: `@opencode-ai/plugin ^1.18.29`, `@opencode/plugin ^2.0.0`. + README documents minimum versions. +5. Tests — `src/v2.test.ts`: dual export shape, mock-ctx tool/command/mcp/event + registration, status round-trip, missing-env disables server, transform + replay purity (no warn on second replay), Turso immediate-sync (empty status + = idle), `parseCommandRepoArg` edge cases. Existing v1 tests unchanged. +6. Local verify — `bun install`, `bun run check`, `bun test`, `bun run build`; + manual packed-tarball smoke on opencode v1 (`plugin`) and v2 (`plugins`). +7. CI — build assertion that `dist` exposes both `setup` and `server`; unit + tests; matrix smoke (v1 + v2 installs vs packed plugin, fail on early exit). + +Risks / known parity gaps +- Non-MCP runtime keys warn-only in v2 (no global merge possible). +- No toasts in v2 (console + `app.log` facade); no session-idle gating (empty + status map = idle → immediate Turso sync). +- AI messages fall back to static when no model is available. +- Slash commands support bare repo/backend token only; full options require the + `opencode_sync` tool. +- Overrides require restart; no watcher/`reload()` calls. From 4a6c51d02a27b2fd148791c1e1e9c98b57541fb4 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 22 Sep 2026 19:07:34 -0700 Subject: [PATCH 14/15] fix: keep v2 sync status read-only --- README.md | 15 +++++-- docs/v2.md | 33 +++++++------- src/v2.test.ts | 47 ++++--------------- src/v2.ts | 119 +++++-------------------------------------------- 4 files changed, 45 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index 0093184..1a29f74 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,18 @@ For opencode v2: opencode does not auto-update plugins. To update, modify the version number in your config file. +On v2, use the `opencode_sync` tool for sync operations. The `/sync-*` slash +commands are available on v1 only. V2 cannot display a slash command's direct +result without placing it in the model's prompt queue, which could trigger +another operation. For example, ask OpenCode to call `opencode_sync` with +`{"command":"status"}` to inspect the current state. + ## Configure ### First machine (create new sync repo) -Run `/sync-init` to create a new sync repo: +On v1, run `/sync-init` to create a new sync repo. On v2, ask OpenCode to call +`opencode_sync` with `{"command":"init"}`: 1. Detects your GitHub username 2. Creates a private repo (`my-opencode-config` by default) @@ -56,13 +63,15 @@ Run `/sync-init` to create a new sync repo: ### Additional machines (link to existing repo) -Run `/sync-link` to connect to your existing sync repo: +On v1, run `/sync-link` to connect to your existing sync repo. On v2, ask +OpenCode to call `opencode_sync` with `{"command":"link"}`: 1. Searches your GitHub for common sync repo names (prioritizes `my-opencode-config`) 2. Clones and applies the synced config 3. **Overwrites local config** with synced content (preserves your local overrides file) -If auto-detection fails, specify the repo name: `/sync-link my-opencode-config` +If auto-detection fails, specify the repo name with `/sync-link my-opencode-config` +on v1 or `{"command":"link","repo":"my-opencode-config"}` on v2. After linking, restart opencode to apply the synced settings. diff --git a/docs/v2.md b/docs/v2.md index 9e0ce32..7561115 100644 --- a/docs/v2.md +++ b/docs/v2.md @@ -24,21 +24,20 @@ mcp `ctx.mcp.transform` (set/update per server) Unresolvable `{env:…}`: store agent `ctx.agent.transform` (update-only) Editor cannot create agents; unknown IDs warn once outside the transform, skip silently inside. model `ctx.model.transform` (update-only) Non-object shapes warn; unknown provider IDs warn via `ctx.model.provider.list()` best-effort; unknown model IDs skip silently (no bulk "has" API). provider `ctx.provider.transform` (update-only) Unknown IDs warn once outside via `ctx.provider.list()`, skip silently inside. -command Ignored with warn Sync commands are owned by this plugin and registered via `ctx.command.transform` from `src/command/*.md`. External `command` overrides are not applied. +command Ignored with warn V2 uses the `opencode_sync` tool. Slash commands and external `command` overrides are not registered. everything else `console.warn` with key name + docs pointer No fake global merge. File-level behavior (`syncRepoToLocal`/`syncLocalToRepo`, `stripOverrides` in `src/sync/apply.ts`, `src/sync/config.ts`) is runtime-independent and unchanged. -V2 command + tool limits (documented, not bugs) -- `CommandDefinition` only carries `name/description/execute` — unlike v1 - `config.command` there is no `template/agent/model/subtask`. The md template - is executed directly: run service → post via `ctx.session.synthetic`. -- Slash commands only carry free text (`prompt.text`). Only a single bare - `owner/repo` (or URL) is parsed (`parseCommandRepoArg` in `src/v2.ts` takes - the first token, strips quotes/`$ARGUMENTS`); `init`/`link` extra flags and - `enable-secrets`/Turso options are not parseable from slash text — use the - `opencode_sync` tool for full args. +V2 command + tool limits +- V2 does not register `/sync-*` slash commands. Its `CommandDefinition` + callback has no direct result channel. Posting command output with + `session.synthetic` resumes the model as if the output were a new user prompt; + even `/sync-status` can then prompt a follow-up sync operation. Using + `resume: false` prevents that operation but leaves the output in the inbox + rather than the visible session transcript. Use the `opencode_sync` tool, + which returns the result as tool content. - `Tool.Result.content` accepts `string | Content[]`; we return a plain string to keep status output readable. @@ -61,8 +60,7 @@ Implementation map no-op toast — v2 has neither toast nor log sink), session-status facade returns `{data:{}}` (empty = idle → Turso idle-gating intentionally skipped, syncs immediately), AI via `generate.text`. Registers tool (JSON Schema, - `required:["command"]`), commands (parse bare repo arg, post via - `session.synthetic`), mcp/agent/model/provider transforms, + `required:["command"]`), mcp/agent/model/provider transforms, `event.subscribe` → `service.handleEvent`, timed startup sync with dispose cleanup (`clearTimeout` + `abort` + `service.dispose()` which stops the Turso sync loop/idle-flush timers). @@ -70,10 +68,10 @@ Implementation map default export `{ id, setup: setupV2, server }` (no spread so runtimes do not leak fields). Deps: `@opencode-ai/plugin ^1.18.29`, `@opencode/plugin ^2.0.0`. README documents minimum versions. -5. Tests — `src/v2.test.ts`: dual export shape, mock-ctx tool/command/mcp/event - registration, status round-trip, missing-env disables server, transform - replay purity (no warn on second replay), Turso immediate-sync (empty status - = idle), `parseCommandRepoArg` edge cases. Existing v1 tests unchanged. +5. Tests — `src/v2.test.ts`: dual export shape, mock-ctx tool/mcp/event + registration, status round-trip without slash commands or synthetic messages, + missing-env disables server, transform replay purity (no warn on second + replay), Turso immediate-sync (empty status = idle). Existing v1 tests unchanged. 6. Local verify — `bun install`, `bun run check`, `bun test`, `bun run build`; manual packed-tarball smoke on opencode v1 (`plugin`) and v2 (`plugins`). 7. CI — build assertion that `dist` exposes both `setup` and `server`; unit @@ -84,6 +82,5 @@ Risks / known parity gaps - No toasts in v2 (console + `app.log` facade); no session-idle gating (empty status map = idle → immediate Turso sync). - AI messages fall back to static when no model is available. -- Slash commands support bare repo/backend token only; full options require the - `opencode_sync` tool. +- V2 sync operations require the `opencode_sync` tool; v1 keeps `/sync-*` commands. - Overrides require restart; no watcher/`reload()` calls. diff --git a/src/v2.test.ts b/src/v2.test.ts index b76496e..d9f9148 100644 --- a/src/v2.test.ts +++ b/src/v2.test.ts @@ -27,7 +27,7 @@ vi.mock('@opencode-ai/plugin', () => { import pluginDefault, { opencodeConfigSync, opencodeSyncedV2 } from './index.js'; import { resolveSyncLocations } from './sync/paths.js'; -import { parseCommandRepoArg, setupV2 } from './v2.js'; +import { setupV2 } from './v2.js'; const ENV_KEYS = ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME'] as const; @@ -56,7 +56,7 @@ interface MockCtx { modelUpdates: [string, string, Record][]; transformCallbacks: Record; subscribed: boolean; - synthetics: { sessionID: string; text: string }[]; + synthetics: { sessionID: string; text: string; resume?: boolean }[]; knownAgents: { id: string }[]; knownProviders: { provider: { id: string } }[]; ctx: unknown; @@ -193,7 +193,7 @@ function createMockCtx(): MockCtx { text: async () => ({ text: 'Sync opencode config' }), }, session: { - synthetic: async (input: { sessionID: string; text: string }) => { + synthetic: async (input: { sessionID: string; text: string; resume?: boolean }) => { mock.synthetics.push(input); return {}; }, @@ -275,7 +275,7 @@ describe('v2 dual export', () => { }); describe('v2 setup', () => { - it('registers the sync tool and owned commands, with a status round-trip', async () => { + it('exposes status through the tool without registering model-resuming slash commands', async () => { await withIsolatedHome(async () => { const mock = createMockCtx(); const cleanup = await setupV2(mock.ctx as never); @@ -284,12 +284,14 @@ describe('v2 setup', () => { expect(mock.toolAdds[0].name).toBe('opencode_sync'); expect(mock.toolAdds[0].input.required).toEqual(['command']); - expect(mock.commandAdds.length).toBeGreaterThan(0); - expect(mock.commandAdds.map((command) => command.name)).toContain('sync-status'); + expect(mock.commandAdds).toHaveLength(0); const result = await mock.toolAdds[0].execute({ command: 'status' }); expect(typeof result.content).toBe('string'); expect(result.content).toContain('opencode-synced is not configured'); + expect(result.content).toContain('opencode_sync with {"command":"init"}'); + expect(result.content).not.toContain('/sync-init'); + expect(mock.synthetics).toHaveLength(0); expect(mock.subscribed).toBe(true); } finally { cleanup(); @@ -377,23 +379,6 @@ describe('v2 setup', () => { }); }); - it('posts command results via session.synthetic', async () => { - await withIsolatedHome(async () => { - const mock = createMockCtx(); - const cleanup = await setupV2(mock.ctx as never); - try { - const statusCommand = mock.commandAdds.find((command) => command.name === 'sync-status'); - expect(statusCommand).toBeDefined(); - await statusCommand?.execute({ sessionID: 'session-1', prompt: { text: '' } }); - expect(mock.synthetics).toHaveLength(1); - expect(mock.synthetics[0].sessionID).toBe('session-1'); - expect(mock.synthetics[0].text).toContain('opencode-synced is not configured'); - } finally { - cleanup(); - } - }); - }); - it('cleanup is idempotent and stops background work', async () => { await withIsolatedHome(async () => { const mock = createMockCtx(); @@ -404,19 +389,3 @@ describe('v2 setup', () => { }); }); }); - -describe('parseCommandRepoArg', () => { - it('parses bare repo args and slash-prefixed invocations', () => { - expect(parseCommandRepoArg('owner/repo', 'sync-link')).toBe('owner/repo'); - expect(parseCommandRepoArg('/sync-link owner/repo', 'sync-link')).toBe('owner/repo'); - expect(parseCommandRepoArg('', 'sync-link')).toBeUndefined(); - expect(parseCommandRepoArg('/sync-link', 'sync-link')).toBeUndefined(); - }); - - it('handles quotes, $ARGUMENTS, and extra tokens (first token wins)', () => { - expect(parseCommandRepoArg('$ARGUMENTS owner/repo', 'sync-link')).toBe('owner/repo'); - expect(parseCommandRepoArg('/sync-link "owner/repo"', 'sync-link')).toBe('owner/repo'); - expect(parseCommandRepoArg('owner/repo extra words', 'sync-link')).toBe('owner/repo'); - expect(parseCommandRepoArg(' ', 'sync-link')).toBeUndefined(); - }); -}); diff --git a/src/v2.ts b/src/v2.ts index 9e73dc5..c206ca5 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -5,11 +5,8 @@ import type { PluginInput } from '@opencode-ai/plugin'; import { buildSyncToolInputSchema, executeSyncCommand, - loadCommands, - type ParsedCommand, SYNC_TOOL_COMMANDS, type SyncToolArgs, - type SyncToolCommand, } from './shared.js'; import { createNodeShell } from './shell-node.js'; import type { AiProvider } from './sync/ai.js'; @@ -22,9 +19,10 @@ import { resolveEnvPlaceholders, } from './sync/config.js'; import { resolveSyncLocations } from './sync/paths.js'; -import { createSyncService, type SyncService } from './sync/service.js'; +import { createSyncService } from './sync/service.js'; const PLUGIN_ID = 'opencode-synced'; +const SYNC_COMMAND_NAMES = new Set(SYNC_TOOL_COMMANDS); const OVERRIDES_DOC_POINTER = 'opencode-synced: ignoring unsupported override key (v2 applies only mcp/agent/model/provider via domain transforms; see https://opencode.ai/v2/docs/build/plugins/migrate-v1)'; @@ -41,6 +39,13 @@ function v2Error(message: string): void { console.error(`[opencode-synced] ${message}`); } +/** Replace v1 slash-command hints in shared service output with the v2 tool. */ +function v2ToolGuidance(result: string): string { + return result.replace(/\/sync-([a-z-]+)/g, (reference, command: string) => + SYNC_COMMAND_NAMES.has(command) ? `opencode_sync with {"command":"${command}"}` : reference + ); +} + interface OverrideFailure { fieldPath: readonly string[]; message: string; @@ -393,91 +398,7 @@ function createV2AiProvider(ctx: V2Context): AiProvider { }; } -const SYNC_COMMAND_NAMES = new Set(SYNC_TOOL_COMMANDS as readonly string[]); - -function toolCommandForName(name: string): SyncToolCommand | null { - const suffix = name.startsWith('sync-') ? name.slice('sync-'.length) : name; - return SYNC_COMMAND_NAMES.has(suffix) ? (suffix as SyncToolCommand) : null; -} - -/** - * Parse a bare repo argument from a slash-command invocation - * (e.g. `/sync-link owner/repo`). - * - * V2 slash commands only carry free text (`prompt.text`), not structured tool - * args, so only a single bare `owner/repo` (or URL) is supported. Quoted - * multi-word input uses the first token; anything beyond `init`/`link` repo - * and `sessions-backend` backend is intentionally ignored (documented limit). - */ -export function parseCommandRepoArg( - text: string | undefined, - commandName: string -): string | undefined { - if (!text) return undefined; - let arg = text.trim(); - if (!arg) return undefined; - if (arg.startsWith('/')) { - const firstSpace = arg.indexOf(' '); - if (firstSpace === -1) return undefined; - arg = arg.slice(firstSpace + 1).trim(); - } else if (arg.startsWith(commandName)) { - arg = arg.slice(commandName.length).trim(); - } - if (arg.startsWith('$ARGUMENTS')) arg = arg.slice('$ARGUMENTS'.length).trim(); - if (!arg) return undefined; - // Strip surrounding quotes, then take the first whitespace-separated token. - arg = arg.replace(/^["']+|["']+$/g, '').trim(); - const firstToken = arg.split(/\s+/)[0]; - return firstToken || undefined; -} - -async function executeV2Command( - service: SyncService, - command: ParsedCommand, - invocation: { sessionID: string; prompt?: { text?: string }; text?: string } -): Promise { - const toolCommand = toolCommandForName(command.name); - if (!toolCommand) return `Unknown sync command: ${command.name}`; - - const rawText = invocation.prompt?.text ?? invocation.text; - const repo = parseCommandRepoArg(rawText, command.name); - - switch (toolCommand) { - case 'status': - return await service.status(); - case 'init': - return await service.init({ repo }); - case 'link': - return await service.link({ repo }); - case 'pull': - return await service.pull(); - case 'push': - return await service.push(); - case 'resolve': - return await service.resolve(); - case 'secrets-pull': - return await service.secretsPull(); - case 'secrets-push': - return await service.secretsPush(); - case 'secrets-status': - return await service.secretsStatus(); - case 'enable-secrets': - return await service.enableSecrets({}); - case 'sessions-backend': { - const backend = repo === 'git' || repo === 'turso' ? repo : undefined; - return await service.sessionsBackend({ backend }); - } - case 'sessions-setup-turso': - return await service.sessionsSetupTurso({}); - case 'sessions-migrate-turso': - return await service.sessionsMigrateTurso({}); - case 'sessions-cleanup-git': - return await service.sessionsCleanupGit(); - } -} - export async function setupV2(ctx: V2Context): Promise<() => void> { - const commands = await loadCommands(); const service = createSyncService({ client: createV2ClientFacade(), $: createNodeShell(), @@ -499,31 +420,11 @@ export async function setupV2(ctx: V2Context): Promise<() => void> { const result = await executeSyncCommand(service, input as unknown as SyncToolArgs); // Tool.Result.content accepts string | Content[]; plain string keeps // large status outputs readable without manual TextContent wrapping. - return { content: result }; + return { content: v2ToolGuidance(result) }; }, }); }); - // V2 `CommandDefinition` only carries name/description/execute (no - // template/agent/model/subtask like v1 `config.command`). The markdown - // template is therefore executed directly via `executeV2Command` + synthetic - // reply instead of being registered as a prompt template. - await ctx.command.transform((editor) => { - for (const command of commands) { - editor.add({ - name: command.name, - description: command.frontmatter.description, - execute: async ({ sessionID, prompt }) => { - const result = await executeV2Command(service, command, { - sessionID, - prompt: prompt as { text?: string }, - }); - await ctx.session.synthetic({ sessionID, text: result }); - }, - }); - } - }); - if (resolved) { await registerMcpTransform(ctx, resolved); await registerAgentTransform(ctx, resolved); From 5f4f15e547e6bc41f999b70917a3b3108f1ace0d Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 22 Sep 2026 19:27:22 -0700 Subject: [PATCH 15/15] fix: preserve valid v2 MCP override siblings --- docs/v2.md | 6 ++-- src/v2.test.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/v2.ts | 23 ++++++++++++-- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/docs/v2.md b/docs/v2.md index 7561115..8e0bb5e 100644 --- a/docs/v2.md +++ b/docs/v2.md @@ -22,7 +22,7 @@ Overrides mapping Override key V2 destination Notes mcp `ctx.mcp.transform` (set/update per server) Unresolvable `{env:…}`: store secret-free copy (`blankEnvPlaceholders` in `src/sync/config.ts`) with `disabled:true` + `console.error`. Mirrors v1 `disableMcpServerForResolutionFailure` intent without mutating caller state. agent `ctx.agent.transform` (update-only) Editor cannot create agents; unknown IDs warn once outside the transform, skip silently inside. -model `ctx.model.transform` (update-only) Non-object shapes warn; unknown provider IDs warn via `ctx.model.provider.list()` best-effort; unknown model IDs skip silently (no bulk "has" API). +model `ctx.model.transform` (update-only) Non-object shapes warn; unknown provider IDs warn via `ctx.provider.list()` best-effort; unknown model IDs skip silently (no bulk "has" API). provider `ctx.provider.transform` (update-only) Unknown IDs warn once outside via `ctx.provider.list()`, skip silently inside. command Ignored with warn V2 uses the `opencode_sync` tool. Slash commands and external `command` overrides are not registered. everything else `console.warn` with key name + docs pointer No fake global merge. @@ -74,8 +74,8 @@ Implementation map replay), Turso immediate-sync (empty status = idle). Existing v1 tests unchanged. 6. Local verify — `bun install`, `bun run check`, `bun test`, `bun run build`; manual packed-tarball smoke on opencode v1 (`plugin`) and v2 (`plugins`). -7. CI — build assertion that `dist` exposes both `setup` and `server`; unit - tests; matrix smoke (v1 + v2 installs vs packed plugin, fail on early exit). +7. CI — lint, unit tests, build, and Windows path tests. Packed v1/v2 host + installation is a manual verification step, not a CI matrix job. Risks / known parity gaps - Non-MCP runtime keys warn-only in v2 (no global merge possible). diff --git a/src/v2.test.ts b/src/v2.test.ts index d9f9148..b48e467 100644 --- a/src/v2.test.ts +++ b/src/v2.test.ts @@ -333,6 +333,88 @@ describe('v2 setup', () => { }); }); + it('keeps a valid mcp sibling when another server has an unresolvable placeholder', async () => { + await withIsolatedHome(async () => { + const locations = resolveSyncLocations(); + await fs.mkdir(locations.configRoot, { recursive: true }); + await fs.writeFile( + locations.overridesPath, + JSON.stringify({ + mcp: { + good: { + type: 'remote', + url: 'https://good.test/mcp', + headers: { Authorization: 'Bearer {env:V2_SET_PAT}' }, + }, + bad: { + type: 'remote', + url: 'https://bad.test/mcp', + headers: { Authorization: 'Bearer {env:V2_MISSING_PAT}' }, + }, + }, + }), + 'utf8' + ); + process.env.V2_SET_PAT = 'test-token'; + delete process.env.V2_MISSING_PAT; + + const originalError = console.error; + console.error = () => {}; + try { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + expect(mock.mcpSets).toHaveLength(2); + expect(Object.fromEntries(mock.mcpSets)).toMatchObject({ + good: { headers: { Authorization: 'Bearer test-token' } }, + bad: { disabled: true, headers: { Authorization: 'Bearer ' } }, + }); + expect(JSON.stringify(mock.mcpSets)).not.toContain('{env:'); + } finally { + cleanup(); + } + } finally { + console.error = originalError; + delete process.env.V2_SET_PAT; + } + }); + }); + + it('rejects an unsafe mcp server key without discarding safe siblings', async () => { + await withIsolatedHome(async () => { + const locations = resolveSyncLocations(); + await fs.mkdir(locations.configRoot, { recursive: true }); + await fs.writeFile( + locations.overridesPath, + '{"mcp":{"__proto__":{"type":"remote","url":"https://unsafe.test/mcp"},"good":{"type":"remote","url":"https://good.test/mcp"}}}', + 'utf8' + ); + + const errors: unknown[][] = []; + const originalError = console.error; + console.error = (...args: unknown[]) => { + errors.push(args); + }; + try { + const mock = createMockCtx(); + const cleanup = await setupV2(mock.ctx as never); + try { + expect(mock.mcpSets).toEqual([ + ['good', { type: 'remote', url: 'https://good.test/mcp' }], + ]); + expect( + errors.some((args) => JSON.stringify(args).includes('Unsafe local override')) + ).toBe(true); + expect(Object.prototype).not.toHaveProperty('type'); + } finally { + cleanup(); + } + } finally { + console.error = originalError; + } + }); + }); + it('warns once for unknown agent/provider overrides and keeps replays pure', async () => { await withIsolatedHome(async () => { const locations = resolveSyncLocations(); diff --git a/src/v2.ts b/src/v2.ts index c206ca5..df1c762 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -54,14 +54,15 @@ interface OverrideFailure { interface ResolvedOverrides { /** Raw document as loaded (used for secret-free fallback configs). */ raw: Record; - /** Per-top-level-key resolved values; keys with failures are omitted. */ + /** Resolved values; MCP servers are independent so one failure preserves siblings. */ values: Record; /** Field paths (e.g. ['overrides','mcp','github',...]) that failed {env:…} resolution. */ failures: OverrideFailure[]; } /** - * Load overrides once and resolve `{env:…}` per top-level key. + * Load overrides once and resolve `{env:…}` per top-level key, except MCP + * servers, which must fail independently. * Single read avoids TOCTOU drift between the resolved values and the raw * fallback used for disabled MCP servers. */ @@ -72,6 +73,24 @@ async function loadResolvedOverrides(): Promise { const values: Record = {}; const failures: OverrideFailure[] = []; for (const [key, value] of Object.entries(raw)) { + if (key === 'mcp' && isPlainObject(value)) { + const resolvedMcp: Record = {}; + for (const [name, config] of Object.entries(value)) { + if (name === '__proto__') { + v2Error('Unsafe local override field "overrides.mcp.__proto__" is not allowed.'); + continue; + } + try { + resolvedMcp[name] = resolveEnvPlaceholders(config, process.env, ['overrides', key, name]); + } catch (error) { + if (!(error instanceof EnvPlaceholderResolutionError)) throw error; + failures.push({ fieldPath: error.fieldPath, message: error.message }); + v2Error(error.message); + } + } + values[key] = resolvedMcp; + continue; + } try { values[key] = resolveEnvPlaceholders(value, process.env, ['overrides', key]); } catch (error) {