diff --git a/.opencode/plugin/envsitter-guard.ts b/.opencode/plugin/envsitter-guard.ts index 9aaf3b3..98c8d6b 100644 --- a/.opencode/plugin/envsitter-guard.ts +++ b/.opencode/plugin/envsitter-guard.ts @@ -1,4 +1,4 @@ -import EnvSitterGuard from "../../index"; +import EnvSitterGuard from "../../index.js"; export default EnvSitterGuard; -export { EnvSitterGuard } from "../../index"; +export { EnvSitterGuard } from "../../index.js"; diff --git a/index.ts b/index.ts index de27be9..7b06a72 100644 --- a/index.ts +++ b/index.ts @@ -1,916 +1,15 @@ -import type { Plugin } from "@opencode-ai/plugin"; -import { tool } from "@opencode-ai/plugin/tool"; -import { - EnvSitter, - addEnvFileKey, - annotateEnvFile, - copyEnvFileKeys, - deleteEnvFileKeys, - formatEnvFile, - setEnvFileKey, - unsetEnvFileKey, - validateEnvFile, -} from "envsitter"; -import path from "node:path"; - -function normalizePath(input: string): string { - return input.replace(/\\/g, "/"); -} - -function isDotEnvExamplePath(input: string): boolean { - return /(^|\/)\.env\.example$/.test(normalizePath(input)); -} - -function isSensitiveDotEnvPath(input: string): boolean { - const normalized = normalizePath(input); - if (isDotEnvExamplePath(normalized)) return false; - return /(^|\/)\.env($|\.)/.test(normalized); -} - -function isDotEnvishPath(input: string): boolean { - const normalized = normalizePath(input); - return isDotEnvExamplePath(normalized) || isSensitiveDotEnvPath(normalized); -} - -function isEnvSitterPepperPath(input: string): boolean { - const normalized = normalizePath(input); - return /(^|\/)\.envsitter\/pepper$/.test(normalized); -} - -function stripAtPrefix(input: string): string { - return input.trim().replace(/^@+/, ""); -} - -function parseUserRegExp(input: string): RegExp { - const trimmed = input.trim(); - if (!trimmed.startsWith("/")) return new RegExp(trimmed); - - let lastSlashIndex = -1; - for (let i = trimmed.length - 1; i >= 1; i -= 1) { - if (trimmed[i] !== "/") continue; - - let backslashCount = 0; - for (let j = i - 1; j >= 0 && trimmed[j] === "\\"; j -= 1) { - backslashCount += 1; - } - - const isEscaped = backslashCount % 2 === 1; - if (!isEscaped) { - lastSlashIndex = i; - break; - } - } - - if (lastSlashIndex === -1) { - throw new Error("Invalid regex literal; expected a closing `/`."); - } - - const body = trimmed.slice(1, lastSlashIndex); - const flags = trimmed.slice(lastSlashIndex + 1); - if (!/^[a-z]*$/.test(flags)) { - throw new Error("Invalid regex literal flags; expected only letters (e.g. `/abc/i`)."); - } - - return new RegExp(body, flags); -} - -function resolveCandidate(params: { candidate?: string; candidateEnvVar?: string }): string { - if (typeof params.candidate === "string" && params.candidate.length > 0) return params.candidate; - - if (typeof params.candidateEnvVar === "string" && params.candidateEnvVar.length > 0) { - const value = process.env[params.candidateEnvVar]; - if (typeof value === "string" && value.length > 0) return value; - throw new Error(`Env var \`${params.candidateEnvVar}\` was not set.`); - } - - throw new Error("Candidate is required for this operation. Provide `candidate` or `candidateEnvVar`."); -} - -function getFilePathFromArgs(args: unknown): string | undefined { - if (!args || typeof args !== "object") return; - const record = args as Record; - - const candidates: Array = [record.filePath, record.path, record.file_path]; - - const found = candidates.find((value) => typeof value === "string") as string | undefined; - return found ? stripAtPrefix(found) : undefined; -} - -function resolveDotEnvPath(params: { - worktree: string; - directory: string; - filePath: string; -}): { absolutePath: string; displayPath: string } { - const normalized = normalizePath(params.filePath); - - if (isEnvSitterPepperPath(normalized)) { - throw new Error("Access to `.envsitter/pepper` is blocked."); - } - - if (!isDotEnvishPath(normalized)) { - throw new Error("Only `.env`-style paths are allowed (e.g. `.env`, `.env.local`, `.env.example`)."); - } - - const absolutePath = path.resolve(params.directory, normalized); - const relativeToWorktree = path.relative(params.worktree, absolutePath); - if (relativeToWorktree.startsWith("..") || path.isAbsolute(relativeToWorktree)) { - throw new Error("EnvSitter tools only operate on files inside the current project."); - } - - return { absolutePath, displayPath: relativeToWorktree }; -} - -export const EnvSitterGuard: Plugin = async ({ directory, worktree }) => { - const matchOps = [ - "exists", - "is_empty", - "is_equal", - "partial_match_prefix", - "partial_match_suffix", - "partial_match_regex", - "is_number", - "is_boolean", - "is_string", - ] as const; - - const scanDetections = ["jwt", "url", "base64"] as const; - - return { - tool: { - envsitter_keys: tool({ - description: "List keys in a .env file (never returns values).", - args: { - filePath: tool.schema.string().optional(), - filterRegex: tool.schema.string().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const es = EnvSitter.fromDotenvFile(resolved.absolutePath); - const keys = await es.listKeys( - args.filterRegex - ? { - filter: parseUserRegExp(args.filterRegex), - } - : undefined, - ); - - return JSON.stringify({ file: resolved.displayPath, keys }, null, 2); - }, - }), - envsitter_fingerprint: tool({ - description: "Compute a deterministic fingerprint for a single key (never returns the value).", - args: { - filePath: tool.schema.string().optional(), - key: tool.schema.string(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const es = EnvSitter.fromDotenvFile(resolved.absolutePath); - const result = await es.fingerprintKey(args.key); - - return JSON.stringify({ file: resolved.displayPath, key: args.key, result }, null, 2); - }, - }), - envsitter_match: tool({ - description: - "Match key values without printing them. Supports existence/shape checks and outside-in candidate matching.", - args: { - filePath: tool.schema.string().optional(), - op: tool.schema.enum(matchOps).optional(), - key: tool.schema.string().optional(), - keys: tool.schema.array(tool.schema.string()).optional(), - allKeys: tool.schema.boolean().optional(), - candidate: tool.schema.string().optional(), - candidateEnvVar: tool.schema.string().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const op = args.op ?? "is_equal"; - const es = EnvSitter.fromDotenvFile(resolved.absolutePath); - - let isEqualCandidate: string | undefined; - - const matcher = (() => { - if (op === "exists") return { op } as const; - if (op === "is_empty") return { op } as const; - if (op === "is_number") return { op } as const; - if (op === "is_boolean") return { op } as const; - if (op === "is_string") return { op } as const; - - const candidate = resolveCandidate({ - candidate: args.candidate, - candidateEnvVar: args.candidateEnvVar, - }); - - if (op === "is_equal") { - isEqualCandidate = candidate; - return { op, candidate } as const; - } - - if (op === "partial_match_prefix") { - return { op, prefix: candidate } as const; - } - - if (op === "partial_match_suffix") { - return { op, suffix: candidate } as const; - } - - if (op === "partial_match_regex") { - return { op, regex: parseUserRegExp(candidate) } as const; - } - - throw new Error(`Unsupported op: ${op}`); - })(); - - const key = args.key; - const keys = args.keys; - const allKeys = args.allKeys === true; - - const selectorCount = - Number(typeof key === "string") + Number(Array.isArray(keys) && keys.length > 0) + Number(allKeys); - if (selectorCount !== 1) { - throw new Error("Provide exactly one of: `key`, `keys`, or `allKeys: true`. "); - } - - if (typeof key === "string") { - const match = - matcher.op === "is_equal" && typeof isEqualCandidate === "string" - ? await es.matchCandidate(key, isEqualCandidate) - : await es.matchKey(key, matcher); - return JSON.stringify({ file: resolved.displayPath, key, op: matcher.op, match }, null, 2); - } - - if (Array.isArray(keys) && keys.length > 0) { - const matches = - matcher.op === "is_equal" && typeof isEqualCandidate === "string" - ? await es.matchCandidateBulk(keys, isEqualCandidate) - : await es.matchKeyBulk(keys, matcher); - return JSON.stringify({ file: resolved.displayPath, op: matcher.op, matches }, null, 2); - } - - const matches = - matcher.op === "is_equal" && typeof isEqualCandidate === "string" - ? await es.matchCandidateAll(isEqualCandidate) - : await es.matchKeyAll(matcher); - return JSON.stringify({ file: resolved.displayPath, op: matcher.op, matches }, null, 2); - }, - }), - envsitter_match_by_key: tool({ - description: "Bulk match candidates-by-key without printing values (returns booleans only).", - args: { - filePath: tool.schema.string().optional(), - candidatesByKey: tool.schema.record(tool.schema.string(), tool.schema.string()).optional(), - candidatesByKeyJson: tool.schema.string().optional(), - candidatesByKeyEnvVar: tool.schema.string().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const fromRecord = args.candidatesByKey; - const fromJson = args.candidatesByKeyJson; - const fromEnvVar = args.candidatesByKeyEnvVar; - - const selectorCount = Number(!!fromRecord) + Number(!!fromJson) + Number(!!fromEnvVar); - if (selectorCount !== 1) { - throw new Error( - "Provide exactly one of: `candidatesByKey`, `candidatesByKeyJson`, or `candidatesByKeyEnvVar`.", - ); - } - - let candidatesByKey: Record; - - if (fromRecord) { - candidatesByKey = fromRecord; - } else { - const json = - typeof fromJson === "string" - ? fromJson - : (() => { - const envVarName = fromEnvVar as string; - const value = process.env[envVarName]; - if (typeof value !== "string") throw new Error(`Env var \`${envVarName}\` was not set.`); - return value; - })(); - - let parsed: unknown; - try { - parsed = JSON.parse(json); - } catch { - throw new Error("Invalid candidates JSON; expected an object mapping key -> candidate string."); - } - - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - throw new Error("Invalid candidates JSON; expected an object mapping key -> candidate string."); - } - - const record = parsed as Record; - const normalized: Record = {}; - for (const [key, value] of Object.entries(record)) { - if (typeof value !== "string") { - throw new Error("Invalid candidates JSON; expected every value to be a string."); - } - normalized[key] = value; - } - candidatesByKey = normalized; - } - - const es = EnvSitter.fromDotenvFile(resolved.absolutePath); - const matches = await es.matchCandidatesByKey(candidatesByKey); - - return JSON.stringify({ file: resolved.displayPath, matches }, null, 2); - }, - }), - envsitter_scan: tool({ - description: "Scan value shapes (jwt/url/base64) without printing values.", - args: { - filePath: tool.schema.string().optional(), - detect: tool.schema.array(tool.schema.enum(scanDetections)).optional(), - keysFilterRegex: tool.schema.string().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const es = EnvSitter.fromDotenvFile(resolved.absolutePath); - const findings = await es.scan({ - detect: args.detect, - keysFilter: args.keysFilterRegex ? parseUserRegExp(args.keysFilterRegex) : undefined, - }); - - return JSON.stringify({ file: resolved.displayPath, findings }, null, 2); - }, - }), - envsitter_validate: tool({ - description: "Validate dotenv syntax (never returns values).", - args: { - filePath: tool.schema.string().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await validateEnvFile(resolved.absolutePath); - return JSON.stringify({ file: resolved.displayPath, ok: result.ok, issues: result.issues }, null, 2); - }, - }), - envsitter_copy: tool({ - description: - "Copy keys between dotenv files safely (no values in output). Dry-run unless `write: true`.", - args: { - from: tool.schema.string(), - to: tool.schema.string(), - keys: tool.schema.array(tool.schema.string()).optional(), - includeRegex: tool.schema.string().optional(), - excludeRegex: tool.schema.string().optional(), - rename: tool.schema.string().optional(), - onConflict: tool.schema.enum(["error", "skip", "overwrite"] as const).optional(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolvedFrom = resolveDotEnvPath({ - worktree, - directory, - filePath: args.from, - }); - - const resolvedTo = resolveDotEnvPath({ - worktree, - directory, - filePath: args.to, - }); - - if (typeof args.rename === "string" && args.rename.trim().length === 0) { - throw new Error("Invalid `rename`; expected a non-empty string like `A=B,C=D`. "); - } - - const result = await copyEnvFileKeys({ - from: resolvedFrom.absolutePath, - to: resolvedTo.absolutePath, - keys: Array.isArray(args.keys) && args.keys.length > 0 ? args.keys : undefined, - include: args.includeRegex ? parseUserRegExp(args.includeRegex) : undefined, - exclude: args.excludeRegex ? parseUserRegExp(args.excludeRegex) : undefined, - rename: args.rename, - onConflict: args.onConflict, - write: args.write === true, - }); - - return JSON.stringify( - { - from: resolvedFrom.displayPath, - to: resolvedTo.displayPath, - onConflict: result.onConflict, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_format: tool({ - description: "Format/reorder a dotenv file (no values in output). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - mode: tool.schema.enum(["sections", "global"] as const).optional(), - sort: tool.schema.enum(["alpha", "none"] as const).optional(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await formatEnvFile({ - file: resolved.absolutePath, - mode: args.mode, - sort: args.sort, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - mode: result.mode, - sort: result.sort, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - }, - null, - 2, - ); - }, - }), - envsitter_reorder: tool({ - description: "Alias for envsitter_format.", - args: { - filePath: tool.schema.string().optional(), - mode: tool.schema.enum(["sections", "global"] as const).optional(), - sort: tool.schema.enum(["alpha", "none"] as const).optional(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await formatEnvFile({ - file: resolved.absolutePath, - mode: args.mode, - sort: args.sort, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - mode: result.mode, - sort: result.sort, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - }, - null, - 2, - ); - }, - }), - envsitter_annotate: tool({ - description: "Annotate a dotenv key with a comment (no values in output). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - key: tool.schema.string(), - comment: tool.schema.string(), - line: tool.schema.number().int().optional(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - if (args.comment.trim().length === 0) { - throw new Error("Comment must be a non-empty string."); - } - - const result = await annotateEnvFile({ - file: resolved.absolutePath, - key: args.key, - comment: args.comment, - line: args.line, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - key: result.key, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_add: tool({ - description: - "Add a new key to a dotenv file (fails if key already exists). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - key: tool.schema.string(), - value: tool.schema.string(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await addEnvFileKey({ - file: resolved.absolutePath, - key: args.key, - value: args.value, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - key: result.key, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_set: tool({ - description: - "Set a key's value in a dotenv file (creates if missing, updates if exists). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - key: tool.schema.string(), - value: tool.schema.string(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await setEnvFileKey({ - file: resolved.absolutePath, - key: args.key, - value: args.value, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - key: result.key, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_unset: tool({ - description: - "Unset a key's value in a dotenv file (sets to empty string, keeps the key). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - key: tool.schema.string(), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await unsetEnvFileKey({ - file: resolved.absolutePath, - key: args.key, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - key: result.key, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_delete: tool({ - description: - "Delete key(s) from a dotenv file entirely (removes the line). Dry-run unless `write: true`.", - args: { - filePath: tool.schema.string().optional(), - keys: tool.schema.array(tool.schema.string()), - write: tool.schema.boolean().optional(), - }, - async execute(args) { - const resolved = resolveDotEnvPath({ - worktree, - directory, - filePath: args.filePath ?? ".env", - }); - - const result = await deleteEnvFileKeys({ - file: resolved.absolutePath, - keys: args.keys, - write: args.write === true, - }); - - return JSON.stringify( - { - file: resolved.displayPath, - keys: result.keys, - willWrite: result.willWrite, - wrote: result.wrote, - hasChanges: result.hasChanges, - issues: result.issues, - plan: result.plan, - }, - null, - 2, - ); - }, - }), - envsitter_help: tool({ - description: - "Get comprehensive help on all EnvSitter tools. Call this to understand how to safely work with .env files without exposing secrets.", - args: { - topic: tool.schema - .enum([ - "overview", - "reading", - "matching", - "mutations", - "file_ops", - "all", - ] as const) - .optional(), - }, - async execute(args) { - const topic = args.topic ?? "all"; - - const overview = ` -## EnvSitter Tools Overview - -EnvSitter provides safe .env file operations that NEVER expose secret values. -All tools return keys, booleans, line numbers, and operation plans only. - -### Why Use EnvSitter? -- Direct reading of .env files is BLOCKED to prevent secret leaks -- These tools let you inspect, validate, and modify .env files safely -- File modifications are dry-run by default; use \`write: true\` to apply - -### Tool Categories -- **Reading**: envsitter_keys, envsitter_fingerprint, envsitter_scan -- **Matching**: envsitter_match, envsitter_match_by_key -- **Mutations**: envsitter_add, envsitter_set, envsitter_unset, envsitter_delete -- **File Ops**: envsitter_validate, envsitter_copy, envsitter_format, envsitter_annotate -`; - - const reading = ` -## Reading Tools (never return values) - -### envsitter_keys -List all keys in a .env file. -\`\`\`json -{ "filePath": ".env", "filterRegex": "/^API_/" } -\`\`\` -Returns: \`{ file, keys: string[] }\` - -### envsitter_fingerprint -Get a deterministic fingerprint of a key's value (for comparison/auditing). -\`\`\`json -{ "filePath": ".env", "key": "DATABASE_URL" } -\`\`\` -Returns: \`{ file, key, result: { algorithm, fingerprint, length } }\` - -### envsitter_scan -Detect value shapes (JWT, URL, base64) without revealing values. -\`\`\`json -{ "filePath": ".env", "detect": ["jwt", "url", "base64"], "keysFilterRegex": "/TOKEN/" } -\`\`\` -Returns: \`{ file, findings: [{ key, detections }] }\` -`; - - const matching = ` -## Matching Tools (return booleans only) - -### envsitter_match -Check if a key's value matches criteria without seeing the value. - -**Operations** (op parameter): -- \`exists\`: key is present -- \`is_empty\`: value is empty string -- \`is_equal\`: matches candidate exactly (provide \`candidate\` or \`candidateEnvVar\`) -- \`partial_match_prefix\`: value starts with candidate -- \`partial_match_suffix\`: value ends with candidate -- \`partial_match_regex\`: value matches regex pattern -- \`is_number\`: value is numeric -- \`is_boolean\`: value is true/false -- \`is_string\`: value is neither number nor boolean - -**Selectors** (provide exactly one): -- \`key\`: single key -- \`keys\`: array of keys -- \`allKeys: true\`: all keys in file - -\`\`\`json -{ "filePath": ".env", "key": "NODE_ENV", "op": "is_equal", "candidate": "production" } -{ "filePath": ".env", "keys": ["API_KEY", "SECRET"], "op": "exists" } -{ "filePath": ".env", "allKeys": true, "op": "is_empty" } -\`\`\` - -### envsitter_match_by_key -Bulk match different candidates against different keys. -\`\`\`json -{ "filePath": ".env", "candidatesByKey": { "API_KEY": "sk-xxx", "DB_PASS": "secret123" } } -\`\`\` -Returns: \`{ file, matches: [{ key, match: boolean }] }\` -`; - - const mutations = ` -## Mutation Tools (modify .env files safely) - -All mutation tools are DRY-RUN by default. Set \`write: true\` to apply changes. -Output includes operation plan with line numbers, never values. - -### envsitter_add -Add a NEW key (fails if key already exists). -\`\`\`json -{ "filePath": ".env", "key": "NEW_KEY", "value": "some-value", "write": true } -\`\`\` -Returns: \`{ file, key, hasChanges, plan: { action: "added"|"key_exists" } }\` - -### envsitter_set -Create or update a key (upsert behavior). -\`\`\`json -{ "filePath": ".env", "key": "API_KEY", "value": "new-value", "write": true } -\`\`\` -Returns: \`{ file, key, hasChanges, plan: { action: "added"|"updated"|"no_change" } }\` - -### envsitter_unset -Set a key to empty string (keeps the key line). -\`\`\`json -{ "filePath": ".env", "key": "OLD_KEY", "write": true } -\`\`\` -Returns: \`{ file, key, hasChanges, plan: { action: "unset"|"not_found" } }\` - -### envsitter_delete -Remove key(s) entirely from the file. -\`\`\`json -{ "filePath": ".env", "keys": ["OLD_KEY", "UNUSED_KEY"], "write": true } -\`\`\` -Returns: \`{ file, keys, hasChanges, plan: [{ key, action: "deleted"|"not_found" }] }\` -`; - - const fileOps = ` -## File Operation Tools - -### envsitter_validate -Check .env file syntax for errors. -\`\`\`json -{ "filePath": ".env" } -\`\`\` -Returns: \`{ file, ok: boolean, issues: [{ line, column, message }] }\` - -### envsitter_copy -Copy keys between .env files. Dry-run unless \`write: true\`. -\`\`\`json -{ - "from": ".env.production", - "to": ".env.staging", - "keys": ["API_URL", "REDIS_URL"], - "onConflict": "overwrite", - "write": true -} -\`\`\` -Options: \`includeRegex\`, \`excludeRegex\`, \`rename\` (e.g., "OLD=NEW,A=B") -Returns: \`{ from, to, hasChanges, plan: [{ fromKey, toKey, action }] }\` - -### envsitter_format / envsitter_reorder -Format/reorder a .env file. Dry-run unless \`write: true\`. -\`\`\`json -{ "filePath": ".env", "mode": "sections", "sort": "alpha", "write": true } -\`\`\` -Modes: \`sections\` (preserve section groupings), \`global\` (treat as flat list) -Sort: \`alpha\` (alphabetical), \`none\` (preserve order within sections) - -### envsitter_annotate -Add a comment above a key. Dry-run unless \`write: true\`. -\`\`\`json -{ "filePath": ".env", "key": "DATABASE_URL", "comment": "Production DB only", "write": true } -\`\`\` -Returns: \`{ file, key, hasChanges, plan: { action: "inserted"|"updated"|"not_found" } }\` -`; - - const sections: Record = { - overview, - reading, - matching, - mutations, - file_ops: fileOps, - }; - - if (topic === "all") { - return [overview, reading, matching, mutations, fileOps].join("\n---\n"); - } - - return sections[topic] ?? overview; - }, - }), - }, - "tool.execute.before": async (input, output) => { - const filePath = getFilePathFromArgs(output.args); - if (!filePath) return; - - if (!isSensitiveDotEnvPath(filePath) && !isEnvSitterPepperPath(filePath)) return; - - if (input.tool === "read") { - throw new Error( - "Reading `.env*` is blocked to prevent secret leaks. " + - "Use EnvSitter tools instead (never prints values). " + - "Call envsitter_help for comprehensive usage guide." - ); - } - - if (input.tool === "edit" || input.tool === "write" || input.tool === "patch" || input.tool === "multiedit") { - throw new Error( - "Editing `.env*` and `.envsitter/pepper` via standard tools is blocked. " + - "Use EnvSitter mutation tools: envsitter_add, envsitter_set, envsitter_unset, envsitter_delete. " + - "Call envsitter_help for comprehensive usage guide." - ); - } - }, - }; +import type { Plugin as V1Plugin } from "@opencode-ai/plugin"; +import type { Plugin as V2 } from "@opencode/plugin"; + +import { serverV1 } from "./lib/v1.js"; +import { setupV2 } from "./lib/v2.js"; + +// One default export serves both OpenCode majors: V2 reads `id` + `setup`, V1 (>= 1.18.29) calls `server`. +// Keep every export of this module plugin-shaped: V1 rejects modules that export anything else. +export const EnvSitterGuard: V2.Plugin & { readonly server: V1Plugin } = { + id: "envsitter-guard", + setup: setupV2, + server: serverV1, }; export default EnvSitterGuard; diff --git a/lib/help.ts b/lib/help.ts new file mode 100644 index 0000000..8637aa4 --- /dev/null +++ b/lib/help.ts @@ -0,0 +1,173 @@ +export const HELP_TOPICS = ["overview", "reading", "matching", "mutations", "file_ops", "all"] as const; + +export type HelpTopic = (typeof HELP_TOPICS)[number]; + +const overview = ` +## EnvSitter Tools Overview + +EnvSitter provides safe .env file operations that NEVER expose secret values. +All tools return keys, booleans, line numbers, and operation plans only. + +### Why Use EnvSitter? +- Direct access to sensitive env files (\`.env\`, \`.env.*\`, \`*.env\`) is BLOCKED to prevent secret leaks: + reads, edits/writes/patches, shell commands that name them, and grep matches inside them +- Secret-looking values from those files are replaced with \`[envsitter: KEY redacted]\` in other tools' output +- Example/template files (\`.env.example\`, \`.env.sample\`, \`.env.template\`, \`.env.dist\`, \`.env.default(s)\`) stay readable +- These tools let you inspect, validate, and modify env files safely +- File modifications are dry-run by default; use \`write: true\` to apply + +### Tool Categories +- **Reading**: envsitter_keys, envsitter_fingerprint, envsitter_scan +- **Matching**: envsitter_match, envsitter_match_by_key +- **Mutations**: envsitter_add, envsitter_set, envsitter_unset, envsitter_delete +- **File Ops**: envsitter_validate, envsitter_copy, envsitter_format, envsitter_annotate +`; + +const reading = ` +## Reading Tools (never return values) + +### envsitter_keys +List all keys in a .env file. +\`\`\`json +{ "filePath": ".env", "filterRegex": "/^API_/" } +\`\`\` +Returns: \`{ file, keys: string[] }\` + +### envsitter_fingerprint +Get a deterministic fingerprint of a key's value (for comparison/auditing). +\`\`\`json +{ "filePath": ".env", "key": "DATABASE_URL" } +\`\`\` +Returns: \`{ file, key, result: { algorithm, fingerprint, length } }\` + +### envsitter_scan +Detect value shapes (JWT, URL, base64) without revealing values. +\`\`\`json +{ "filePath": ".env", "detect": ["jwt", "url", "base64"], "keysFilterRegex": "/TOKEN/" } +\`\`\` +Returns: \`{ file, findings: [{ key, detections }] }\` +`; + +const matching = ` +## Matching Tools (return booleans only) + +### envsitter_match +Check if a key's value matches criteria without seeing the value. + +**Operations** (op parameter): +- \`exists\`: key is present +- \`is_empty\`: value is empty string +- \`is_equal\`: matches candidate exactly (provide \`candidate\` or \`candidateEnvVar\`) +- \`partial_match_prefix\`: value starts with candidate +- \`partial_match_suffix\`: value ends with candidate +- \`partial_match_regex\`: value matches regex pattern +- \`is_number\`: value is numeric +- \`is_boolean\`: value is true/false +- \`is_string\`: value is neither number nor boolean + +**Selectors** (provide exactly one): +- \`key\`: single key +- \`keys\`: array of keys +- \`allKeys: true\`: all keys in file + +\`\`\`json +{ "filePath": ".env", "key": "NODE_ENV", "op": "is_equal", "candidate": "production" } +{ "filePath": ".env", "keys": ["API_KEY", "SECRET"], "op": "exists" } +{ "filePath": ".env", "allKeys": true, "op": "is_empty" } +\`\`\` + +### envsitter_match_by_key +Bulk match different candidates against different keys. +\`\`\`json +{ "filePath": ".env", "candidatesByKey": { "API_KEY": "sk-xxx", "DB_PASS": "secret123" } } +\`\`\` +Returns: \`{ file, matches: [{ key, match: boolean }] }\` +`; + +const mutations = ` +## Mutation Tools (modify .env files safely) + +All mutation tools are DRY-RUN by default. Set \`write: true\` to apply changes. +Output includes operation plan with line numbers, never values. + +### envsitter_add +Add a NEW key (fails if key already exists). +\`\`\`json +{ "filePath": ".env", "key": "NEW_KEY", "value": "some-value", "write": true } +\`\`\` +Returns: \`{ file, key, hasChanges, plan: { action: "added"|"key_exists" } }\` + +### envsitter_set +Create or update a key (upsert behavior). +\`\`\`json +{ "filePath": ".env", "key": "API_KEY", "value": "new-value", "write": true } +\`\`\` +Returns: \`{ file, key, hasChanges, plan: { action: "added"|"updated"|"no_change" } }\` + +### envsitter_unset +Set a key to empty string (keeps the key line). +\`\`\`json +{ "filePath": ".env", "key": "OLD_KEY", "write": true } +\`\`\` +Returns: \`{ file, key, hasChanges, plan: { action: "unset"|"not_found" } }\` + +### envsitter_delete +Remove key(s) entirely from the file. +\`\`\`json +{ "filePath": ".env", "keys": ["OLD_KEY", "UNUSED_KEY"], "write": true } +\`\`\` +Returns: \`{ file, keys, hasChanges, plan: [{ key, action: "deleted"|"not_found" }] }\` +`; + +const fileOps = ` +## File Operation Tools + +### envsitter_validate +Check .env file syntax for errors. +\`\`\`json +{ "filePath": ".env" } +\`\`\` +Returns: \`{ file, ok: boolean, issues: [{ line, column, message }] }\` + +### envsitter_copy +Copy keys between .env files. Dry-run unless \`write: true\`. +\`\`\`json +{ + "from": ".env.production", + "to": ".env.staging", + "keys": ["API_URL", "REDIS_URL"], + "onConflict": "overwrite", + "write": true +} +\`\`\` +Options: \`includeRegex\`, \`excludeRegex\`, \`rename\` (e.g., "OLD=NEW,A=B") +Returns: \`{ from, to, hasChanges, plan: [{ fromKey, toKey, action }] }\` + +### envsitter_format / envsitter_reorder +Format/reorder a .env file. Dry-run unless \`write: true\`. +\`\`\`json +{ "filePath": ".env", "mode": "sections", "sort": "alpha", "write": true } +\`\`\` +Modes: \`sections\` (preserve section groupings), \`global\` (treat as flat list) +Sort: \`alpha\` (alphabetical), \`none\` (preserve order within sections) + +### envsitter_annotate +Add a comment above a key. Dry-run unless \`write: true\`. +\`\`\`json +{ "filePath": ".env", "key": "DATABASE_URL", "comment": "Production DB only", "write": true } +\`\`\` +Returns: \`{ file, key, hasChanges, plan: { action: "inserted"|"updated"|"not_found" } }\` +`; + +const sections: Record, string> = { + overview, + reading, + matching, + mutations, + file_ops: fileOps, +}; + +export function helpText(topic: HelpTopic): string { + if (topic === "all") return [overview, reading, matching, mutations, fileOps].join("\n---\n"); + return sections[topic]; +} diff --git a/lib/policy.ts b/lib/policy.ts new file mode 100644 index 0000000..35e21b1 --- /dev/null +++ b/lib/policy.ts @@ -0,0 +1,271 @@ +import { isEnvSitterPepperFile, isSensitiveEnvFile } from "envsitter"; + +export type BlockedAccess = { + readonly kind: "read" | "edit" | "shell" | "access"; + readonly tool: string; + readonly paths: readonly string[]; +}; + +const PATH_ARG_KEYS = ["filePath", "file_path", "filepath", "path", "paths", "filePaths"] as const; +const READ_TOOLS = new Set(["read", "grep", "glob", "list", "view"]); +const EDIT_TOOLS = new Set(["edit", "write", "patch", "apply_patch", "multiedit"]); + +const PATCH_FILE_HEADER = /^\*\*\*\s+(?:Add File|Update File|Delete File|Move to):\s*(.+?)\s*$/gm; +const UNIFIED_DIFF_HEADER = /^(?:---|\+\+\+)\s+(?:[ab]\/)?(.+?)\s*$/gm; + +const SHELL_SEGMENT_OPERATORS = new Set([";", "&", "|", "\n", "(", ")", "`"]); +const SHELL_WORD_BREAKS = new Set(["<", ">"]); +const SHELL_SUBSTITUTION = /\$\(([^()]*)\)|`([^`]*)`/g; +const SHELL_PREFIX_COMMANDS = new Set(["sudo", "env", "command", "exec", "time", "nohup", "nice"]); +const PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]); + +export function normalizePath(input: string): string { + return input.replace(/\\/g, "/"); +} + +export function stripAtPrefix(input: string): string { + return input.trim().replace(/^@+/, ""); +} + +export function isProtectedPath(input: string): boolean { + const candidate = normalizePath(stripAtPrefix(input)); + return isSensitiveEnvFile(candidate) || isEnvSitterPepperFile(candidate); +} + +function asRecord(value: unknown): Record | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; + return value as Record; +} + +function collectPathArgs(args: Record): string[] { + const paths: string[] = []; + for (const key of PATH_ARG_KEYS) { + const value = args[key]; + if (typeof value === "string") paths.push(value); + if (Array.isArray(value)) paths.push(...value.filter((item): item is string => typeof item === "string")); + } + return paths; +} + +export function extractPatchPaths(patchText: string): string[] { + const paths: string[] = []; + for (const match of patchText.matchAll(PATCH_FILE_HEADER)) paths.push(match[1] ?? ""); + for (const match of patchText.matchAll(UNIFIED_DIFF_HEADER)) paths.push(match[1] ?? ""); + return paths.filter((p) => p.length > 0 && p !== "/dev/null"); +} + +function splitShellSegments(command: string): string[][] { + const segments: string[][] = []; + let words: string[] = []; + let word = ""; + let inWord = false; + let quote: "'" | '"' | undefined; + + const endWord = () => { + if (inWord) words.push(word); + word = ""; + inWord = false; + }; + const endSegment = () => { + endWord(); + if (words.length > 0) segments.push(words); + words = []; + }; + + for (let i = 0; i < command.length; i += 1) { + const char = command[i] ?? ""; + + if (quote) { + if (char === quote) { + quote = undefined; + } else if (char === "\\" && quote === '"' && i + 1 < command.length) { + word += command[i + 1]; + i += 1; + } else { + word += char; + } + continue; + } + + if (char === "'" || char === '"') { + quote = char; + inWord = true; + } else if (char === "\\" && i + 1 < command.length) { + word += command[i + 1]; + inWord = true; + i += 1; + } else if (SHELL_SEGMENT_OPERATORS.has(char)) { + endSegment(); + } else if (SHELL_WORD_BREAKS.has(char) || /\s/.test(char)) { + endWord(); + } else { + word += char; + inWord = true; + } + } + + endSegment(); + return segments; +} + +function isEnvSitterPackage(word: string | undefined): boolean { + return word === "envsitter" || (word?.startsWith("envsitter@") ?? false); +} + +function isEnvSitterInvocation(words: readonly string[]): boolean { + let index = 0; + while (index < words.length) { + const word = words[index] ?? ""; + if (!/^[A-Za-z_][A-Za-z0-9_]*=/.test(word) && !SHELL_PREFIX_COMMANDS.has(word)) break; + index += 1; + } + + const program = (words[index] ?? "").split("/").pop() ?? ""; + if (program === "envsitter") return true; + + const rest = words.slice(index + 1).filter((word) => !word.startsWith("-")); + if (PACKAGE_RUNNERS.has(program)) return isEnvSitterPackage(rest[0]); + if (program === "pnpm" || program === "yarn") return rest[0] === "dlx" && isEnvSitterPackage(rest[1]); + return false; +} + +function isProtectedGlob(word: string): boolean { + return /[*?[]/.test(word) && word.toLowerCase().includes(".env"); +} + +/** + * Best-effort scan for `.env*` / pepper paths named in a shell command. It is a guardrail against + * accidental reads (`cat .env`, `source .env`, `echo x >> .env`), not a sandbox: variables, + * eval, and generic commands such as `grep -r` without a path can still reach file contents. + * Segments that invoke the envsitter CLI are allowed because it never prints values. + */ +export function findProtectedPathsInCommand(command: string): string[] { + const found = new Set(); + const sources = [command, ...[...command.matchAll(SHELL_SUBSTITUTION)].map((m) => m[1] ?? m[2] ?? "")]; + + for (const source of sources) { + for (const words of splitShellSegments(source)) { + if (isEnvSitterInvocation(words)) continue; + for (const word of words) { + for (const candidate of [word, ...word.split("=").slice(1)]) { + if (candidate && (isProtectedPath(candidate) || isProtectedGlob(candidate))) found.add(candidate); + } + } + } + } + + return [...found]; +} + +function kindForTool(tool: string): BlockedAccess["kind"] { + if (READ_TOOLS.has(tool)) return "read"; + if (EDIT_TOOLS.has(tool)) return "edit"; + return "access"; +} + +export function findBlockedAccess(tool: string, args: unknown): BlockedAccess | undefined { + if (tool.startsWith("envsitter_")) return undefined; + + const record = asRecord(args); + if (!record) return undefined; + + const paths = collectPathArgs(record); + if (typeof record.patchText === "string") paths.push(...extractPatchPaths(record.patchText)); + + const protectedPaths = paths.filter(isProtectedPath); + if (protectedPaths.length > 0) return { kind: kindForTool(tool), tool, paths: protectedPaths }; + + if (typeof record.command === "string") { + const shellPaths = findProtectedPathsInCommand(record.command); + if (shellPaths.length > 0) return { kind: "shell", tool, paths: shellPaths }; + } + + return undefined; +} + +export function blockedAccessMessage(blocked: BlockedAccess): string { + const targets = blocked.paths.map((p) => `\`${p}\``).join(", "); + const help = "Call envsitter_help for a usage guide."; + + if (blocked.kind === "read") { + return `Reading \`.env*\` is blocked to prevent secret leaks (${targets}). Use EnvSitter tools instead (never prints values). ${help}`; + } + if (blocked.kind === "edit") { + return `Editing \`.env*\` and \`.envsitter/pepper\` via standard tools is blocked (${targets}). Use EnvSitter mutation tools: envsitter_add, envsitter_set, envsitter_unset, envsitter_delete. ${help}`; + } + if (blocked.kind === "shell") { + return `Shell commands that reference \`.env*\` files or \`.envsitter/pepper\` are blocked (${targets}). Use EnvSitter tools or the envsitter CLI, which never print values. ${help}`; + } + return `Access to \`.env*\` files and \`.envsitter/pepper\` through \`${blocked.tool}\` is blocked (${targets}). Use EnvSitter tools instead. ${help}`; +} + +export function permissionDenial(action: string, resources: readonly string[]): string | undefined { + if (action === "read" || action === "edit") { + const paths = resources.filter(isProtectedPath); + if (paths.length > 0) return blockedAccessMessage({ kind: action, tool: action, paths }); + } + + if (action === "shell") { + const paths = resources.flatMap(findProtectedPathsInCommand); + if (paths.length > 0) return blockedAccessMessage({ kind: "shell", tool: action, paths }); + } + + return undefined; +} + +function hiddenMatchesNote(hidden: number): string { + return `(${hidden} match${hidden === 1 ? "" : "es"} in protected .env files hidden by envsitter-guard. Use EnvSitter tools to inspect them.)`; +} + +// OpenCode grep output (V1 and V2): "Found N matches", then per file a ":" header, " Line N: text" rows, and a blank line. +export function redactGrepOutput(output: string): { output: string; hidden: number } { + const kept: string[] = []; + let hidden = 0; + let skipping = false; + + for (const line of output.split("\n")) { + const header = /^(\S.*):$/.exec(line); + if (header && !line.startsWith("Found ")) { + skipping = isProtectedPath(header[1] ?? ""); + if (skipping) continue; + } else if (skipping && line.startsWith(" Line ")) { + hidden += 1; + continue; + } else if (skipping && line === "") { + skipping = false; + continue; + } + kept.push(line); + } + + if (hidden === 0) return { output, hidden }; + + const recounted = kept.join("\n").replace(/^Found (\d+) matches/, (_match, total: string) => `Found ${Math.max(0, Number(total) - hidden)} matches`); + return { output: `${recounted.trimEnd()}\n\n${hiddenMatchesNote(hidden)}`, hidden }; +} + +type GrepMatch = { readonly entry?: { readonly path?: unknown } }; + +export function redactGrepResult(result: Result): Result { + const content = result.content; + let redactedContent: unknown = content; + + if (typeof content === "string") { + redactedContent = redactGrepOutput(content).output; + } else if (Array.isArray(content)) { + redactedContent = content.map((part: unknown) => { + const record = asRecord(part); + if (record?.type !== "text" || typeof record.text !== "string") return part; + return { ...record, text: redactGrepOutput(record.text).output }; + }); + } + + const output = Array.isArray(result.output) + ? (result.output as GrepMatch[]).filter((match) => { + const matchPath = match.entry?.path; + return typeof matchPath !== "string" || !isProtectedPath(matchPath); + }) + : result.output; + + return { ...result, content: redactedContent, output }; +} diff --git a/lib/scrub.ts b/lib/scrub.ts new file mode 100644 index 0000000..a96b10b --- /dev/null +++ b/lib/scrub.ts @@ -0,0 +1,137 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +import { EnvSitter, isSensitiveEnvFile, type EnvSitterRedactor } from "envsitter"; + +const SKIP_DIRS = new Set([".git", "node_modules", "dist", "build", "out", "coverage", "target", "vendor", ".next", ".nuxt", ".turbo", ".cache", ".venv", "venv"]); +const MAX_DEPTH = 3; +const MAX_ENV_FILES = 50; + +export type Scrub = (text: string) => string; + +type CachedRedactor = { + readonly mtimeMs: number; + readonly size: number; + readonly redactor: EnvSitterRedactor; +}; + +function errorCode(error: unknown): string | undefined { + return error instanceof Error && "code" in error ? String((error as { code?: unknown }).code) : undefined; +} + +export async function findSensitiveEnvFiles(root: string): Promise { + const found: string[] = []; + let level = [root]; + + for (let depth = 0; depth <= MAX_DEPTH && level.length > 0; depth++) { + const next: string[] = []; + for (const dir of level) { + let entries; + try { + entries = await fs.readdir(dir, { withFileTypes: true }); + } catch (error) { + const code = errorCode(error); + if (code === "ENOENT" || code === "EACCES" || code === "ENOTDIR") continue; + throw error; + } + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) next.push(fullPath); + } else if ((entry.isFile() || entry.isSymbolicLink()) && isSensitiveEnvFile(fullPath)) { + found.push(fullPath); + if (found.length >= MAX_ENV_FILES) return found.sort(); + } + } + } + level = next; + } + + return found.sort(); +} + +async function redactorFor(file: string, cache: Map): Promise { + let info; + try { + info = await fs.stat(file); + } catch (error) { + if (errorCode(error) !== "ENOENT") throw error; + cache.delete(file); + return undefined; + } + if (!info.isFile()) return undefined; + + const cached = cache.get(file); + if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) return cached.redactor; + + const redactor = await EnvSitter.fromDotenvFile(file).redactor(); + cache.set(file, { mtimeMs: info.mtimeMs, size: info.size, redactor }); + return redactor; +} + +// Redactors are cached per env file and rebuilt when the file's mtime or size changes. +export function createScrubLoader(root: string): () => Promise { + const cache = new Map(); + + return async () => { + const redactors: EnvSitterRedactor[] = []; + for (const file of await findSensitiveEnvFiles(root)) { + const redactor = await redactorFor(file, cache); + if (redactor && redactor.keys.length > 0) redactors.push(redactor); + } + return (text) => redactors.reduce((current, redactor) => redactor.redact(current).text, text); + }; +} + +function asRecord(value: unknown): Record | undefined { + return typeof value === "object" && value !== null && !Array.isArray(value) ? (value as Record) : undefined; +} + +function scrubContent(content: unknown, scrub: Scrub): unknown { + if (typeof content === "string") return scrub(content); + if (!Array.isArray(content)) return content; + + let changed = false; + const parts = content.map((part: unknown) => { + const record = asRecord(part); + if (record?.type !== "text" || typeof record.text !== "string") return part; + const text = scrub(record.text); + if (text === record.text) return part; + changed = true; + return { ...record, text }; + }); + return changed ? parts : content; +} + +function scrubStrings(value: unknown, scrub: Scrub): unknown { + if (typeof value === "string") return scrub(value); + + if (Array.isArray(value)) { + let changed = false; + const items = value.map((item: unknown) => { + const scrubbed = scrubStrings(item, scrub); + if (scrubbed !== item) changed = true; + return scrubbed; + }); + return changed ? items : value; + } + + const record = asRecord(value); + if (!record || (Object.getPrototypeOf(record) !== Object.prototype && Object.getPrototypeOf(record) !== null)) return value; + + let changed = false; + const entries = Object.entries(record).map(([key, item]) => { + const scrubbed = scrubStrings(item, scrub); + if (scrubbed !== item) changed = true; + return [key, scrubbed] as const; + }); + return changed ? Object.fromEntries(entries) : value; +} + +export function scrubToolResult(result: Result, scrub: Scrub): Result { + const content = scrubContent(result.content, scrub); + const output = scrubStrings(result.output, scrub); + if (content === result.content && output === result.output) return result; + return { ...result, content, output }; +} diff --git a/lib/tools.ts b/lib/tools.ts new file mode 100644 index 0000000..abe417d --- /dev/null +++ b/lib/tools.ts @@ -0,0 +1,508 @@ +import path from "node:path"; + +import { tool } from "@opencode-ai/plugin/tool"; +import { + EnvSitter, + addEnvFileKey, + annotateEnvFile, + copyEnvFileKeys, + deleteEnvFileKeys, + formatEnvFile, + isEnvFile, + isEnvSitterPepperFile, + setEnvFileKey, + unsetEnvFileKey, + validateEnvFile, + type EnvSitterMatcher, +} from "envsitter"; +import type { z } from "zod"; + +import { HELP_TOPICS, helpText } from "./help.js"; +import { normalizePath, stripAtPrefix } from "./policy.js"; + +const schema = tool.schema; + +const MATCH_OPS = [ + "exists", + "is_empty", + "is_equal", + "partial_match_prefix", + "partial_match_suffix", + "partial_match_regex", + "is_number", + "is_boolean", + "is_string", +] as const; + +const SCAN_DETECTIONS = ["jwt", "url", "base64"] as const; + +export type ToolEnvironment = { + readonly directory: string; + readonly root: string; +}; + +export type GuardTool = { + readonly name: string; + readonly description: string; + readonly args: z.ZodRawShape; + readonly inputSchema: Record; + readonly run: (input: unknown, env: ToolEnvironment) => Promise; +}; + +function defineTool(definition: { + name: string; + description: string; + args: Shape; + run: (args: z.infer>, env: ToolEnvironment) => Promise; +}): GuardTool { + const parser = schema.object(definition.args); + const { $schema: _dialect, ...inputSchema } = schema.toJSONSchema(parser); + + return { + name: definition.name, + description: definition.description, + args: definition.args, + inputSchema, + async run(input, env) { + const output = await definition.run(parser.parse(input ?? {}), env); + return typeof output === "string" ? output : JSON.stringify(output, null, 2); + }, + }; +} + +function resolveEnvFilePath(env: ToolEnvironment, filePath: string | undefined): { absolutePath: string; displayPath: string } { + const normalized = normalizePath(stripAtPrefix(filePath ?? ".env")); + + if (isEnvSitterPepperFile(normalized)) { + throw new Error("Access to `.envsitter/pepper` is blocked."); + } + + if (!isEnvFile(normalized)) { + throw new Error("Only dotenv files are allowed (e.g. `.env`, `.env.local`, `api.env`, `.env.example`)."); + } + + const absolutePath = path.resolve(env.directory, normalized); + const relativeToRoot = path.relative(env.root, absolutePath); + if (relativeToRoot.split(path.sep)[0] === ".." || path.isAbsolute(relativeToRoot)) { + throw new Error("EnvSitter tools only operate on files inside the current project."); + } + + return { absolutePath, displayPath: relativeToRoot }; +} + +function parseUserRegExp(input: string): RegExp { + const trimmed = input.trim(); + if (!trimmed.startsWith("/")) return new RegExp(trimmed); + + let lastSlashIndex = -1; + for (let i = trimmed.length - 1; i >= 1; i -= 1) { + if (trimmed[i] !== "/") continue; + + let backslashCount = 0; + for (let j = i - 1; j >= 0 && trimmed[j] === "\\"; j -= 1) { + backslashCount += 1; + } + + if (backslashCount % 2 === 0) { + lastSlashIndex = i; + break; + } + } + + if (lastSlashIndex === -1) { + throw new Error("Invalid regex literal; expected a closing `/`."); + } + + const body = trimmed.slice(1, lastSlashIndex); + const flags = trimmed.slice(lastSlashIndex + 1); + if (!/^[a-z]*$/.test(flags)) { + throw new Error("Invalid regex literal flags; expected only letters (e.g. `/abc/i`)."); + } + + return new RegExp(body, flags); +} + +function resolveCandidate(params: { candidate?: string | undefined; candidateEnvVar?: string | undefined }): string { + if (typeof params.candidate === "string" && params.candidate.length > 0) return params.candidate; + + if (typeof params.candidateEnvVar === "string" && params.candidateEnvVar.length > 0) { + const value = process.env[params.candidateEnvVar]; + if (typeof value === "string" && value.length > 0) return value; + throw new Error(`Env var \`${params.candidateEnvVar}\` was not set.`); + } + + throw new Error("Candidate is required for this operation. Provide `candidate` or `candidateEnvVar`."); +} + +function buildMatcher(op: (typeof MATCH_OPS)[number], candidateSource: { candidate?: string | undefined; candidateEnvVar?: string | undefined }): EnvSitterMatcher { + if (op === "exists" || op === "is_empty" || op === "is_number" || op === "is_boolean" || op === "is_string") { + return { op }; + } + + const candidate = resolveCandidate(candidateSource); + if (op === "is_equal") return { op, candidate }; + if (op === "partial_match_prefix") return { op, prefix: candidate }; + if (op === "partial_match_suffix") return { op, suffix: candidate }; + return { op, regex: parseUserRegExp(candidate) }; +} + +function parseCandidatesJson(json: string): Record { + let parsed: unknown; + try { + parsed = JSON.parse(json); + } catch { + throw new Error("Invalid candidates JSON; expected an object mapping key -> candidate string."); + } + + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("Invalid candidates JSON; expected an object mapping key -> candidate string."); + } + + const candidates: Record = {}; + for (const [key, value] of Object.entries(parsed as Record)) { + if (typeof value !== "string") { + throw new Error("Invalid candidates JSON; expected every value to be a string."); + } + candidates[key] = value; + } + return candidates; +} + +const formatArgs = { + filePath: schema.string().optional(), + mode: schema.enum(["sections", "global"] as const).optional(), + sort: schema.enum(["alpha", "none"] as const).optional(), + write: schema.boolean().optional(), +}; + +async function runFormat(args: z.infer>, env: ToolEnvironment) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await formatEnvFile({ + file: resolved.absolutePath, + mode: args.mode, + sort: args.sort, + write: args.write === true, + }); + + return { + file: resolved.displayPath, + mode: result.mode, + sort: result.sort, + willWrite: result.willWrite, + wrote: result.wrote, + hasChanges: result.hasChanges, + issues: result.issues, + }; +} + +export const guardTools: readonly GuardTool[] = [ + defineTool({ + name: "envsitter_keys", + description: "List keys in a .env file (never returns values).", + args: { + filePath: schema.string().optional(), + filterRegex: schema.string().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const es = EnvSitter.fromDotenvFile(resolved.absolutePath); + const keys = await es.listKeys(args.filterRegex ? { filter: parseUserRegExp(args.filterRegex) } : {}); + return { file: resolved.displayPath, keys }; + }, + }), + defineTool({ + name: "envsitter_fingerprint", + description: "Compute a deterministic fingerprint for a single key (never returns the value).", + args: { + filePath: schema.string().optional(), + key: schema.string(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const es = EnvSitter.fromDotenvFile(resolved.absolutePath); + const result = await es.fingerprintKey(args.key); + return { file: resolved.displayPath, key: args.key, result }; + }, + }), + defineTool({ + name: "envsitter_match", + description: + "Match key values without printing them. Supports existence/shape checks and outside-in candidate matching.", + args: { + filePath: schema.string().optional(), + op: schema.enum(MATCH_OPS).optional(), + key: schema.string().optional(), + keys: schema.array(schema.string()).optional(), + allKeys: schema.boolean().optional(), + candidate: schema.string().optional(), + candidateEnvVar: schema.string().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const matcher = buildMatcher(args.op ?? "is_equal", args); + + const hasKey = typeof args.key === "string"; + const hasKeys = Array.isArray(args.keys) && args.keys.length > 0; + const allKeys = args.allKeys === true; + if (Number(hasKey) + Number(hasKeys) + Number(allKeys) !== 1) { + throw new Error("Provide exactly one of: `key`, `keys`, or `allKeys: true`."); + } + + const es = EnvSitter.fromDotenvFile(resolved.absolutePath); + if (typeof args.key === "string") { + const match = await es.matchKey(args.key, matcher); + return { file: resolved.displayPath, key: args.key, op: matcher.op, match }; + } + + const matches = hasKeys && args.keys ? await es.matchKeyBulk(args.keys, matcher) : await es.matchKeyAll(matcher); + return { file: resolved.displayPath, op: matcher.op, matches }; + }, + }), + defineTool({ + name: "envsitter_match_by_key", + description: "Bulk match candidates-by-key without printing values (returns booleans only).", + args: { + filePath: schema.string().optional(), + candidatesByKey: schema.record(schema.string(), schema.string()).optional(), + candidatesByKeyJson: schema.string().optional(), + candidatesByKeyEnvVar: schema.string().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + + const selectorCount = + Number(!!args.candidatesByKey) + Number(!!args.candidatesByKeyJson) + Number(!!args.candidatesByKeyEnvVar); + if (selectorCount !== 1) { + throw new Error( + "Provide exactly one of: `candidatesByKey`, `candidatesByKeyJson`, or `candidatesByKeyEnvVar`.", + ); + } + + let candidatesByKey: Record; + if (args.candidatesByKey) { + candidatesByKey = args.candidatesByKey; + } else if (args.candidatesByKeyJson) { + candidatesByKey = parseCandidatesJson(args.candidatesByKeyJson); + } else { + const envVarName = args.candidatesByKeyEnvVar ?? ""; + const value = process.env[envVarName]; + if (typeof value !== "string") throw new Error(`Env var \`${envVarName}\` was not set.`); + candidatesByKey = parseCandidatesJson(value); + } + + const es = EnvSitter.fromDotenvFile(resolved.absolutePath); + const matches = await es.matchCandidatesByKey(candidatesByKey); + return { file: resolved.displayPath, matches }; + }, + }), + defineTool({ + name: "envsitter_scan", + description: "Scan value shapes (jwt/url/base64) without printing values.", + args: { + filePath: schema.string().optional(), + detect: schema.array(schema.enum(SCAN_DETECTIONS)).optional(), + keysFilterRegex: schema.string().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const es = EnvSitter.fromDotenvFile(resolved.absolutePath); + const findings = await es.scan({ + ...(args.detect ? { detect: args.detect } : {}), + ...(args.keysFilterRegex ? { keysFilter: parseUserRegExp(args.keysFilterRegex) } : {}), + }); + return { file: resolved.displayPath, findings }; + }, + }), + defineTool({ + name: "envsitter_validate", + description: "Validate dotenv syntax (never returns values).", + args: { + filePath: schema.string().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await validateEnvFile(resolved.absolutePath); + return { file: resolved.displayPath, ok: result.ok, issues: result.issues }; + }, + }), + defineTool({ + name: "envsitter_copy", + description: "Copy keys between dotenv files safely (no values in output). Dry-run unless `write: true`.", + args: { + from: schema.string(), + to: schema.string(), + keys: schema.array(schema.string()).optional(), + includeRegex: schema.string().optional(), + excludeRegex: schema.string().optional(), + rename: schema.string().optional(), + onConflict: schema.enum(["error", "skip", "overwrite"] as const).optional(), + write: schema.boolean().optional(), + }, + async run(args, env) { + const from = resolveEnvFilePath(env, args.from); + const to = resolveEnvFilePath(env, args.to); + + if (typeof args.rename === "string" && args.rename.trim().length === 0) { + throw new Error("Invalid `rename`; expected a non-empty string like `A=B,C=D`."); + } + + const result = await copyEnvFileKeys({ + from: from.absolutePath, + to: to.absolutePath, + ...(args.keys && args.keys.length > 0 ? { keys: args.keys } : {}), + ...(args.includeRegex ? { include: parseUserRegExp(args.includeRegex) } : {}), + ...(args.excludeRegex ? { exclude: parseUserRegExp(args.excludeRegex) } : {}), + ...(args.rename ? { rename: args.rename } : {}), + ...(args.onConflict ? { onConflict: args.onConflict } : {}), + write: args.write === true, + }); + + return { + from: from.displayPath, + to: to.displayPath, + onConflict: result.onConflict, + willWrite: result.willWrite, + wrote: result.wrote, + hasChanges: result.hasChanges, + issues: result.issues, + plan: result.plan, + }; + }, + }), + defineTool({ + name: "envsitter_format", + description: "Format/reorder a dotenv file (no values in output). Dry-run unless `write: true`.", + args: formatArgs, + run: runFormat, + }), + defineTool({ + name: "envsitter_reorder", + description: "Alias for envsitter_format.", + args: formatArgs, + run: runFormat, + }), + defineTool({ + name: "envsitter_annotate", + description: "Annotate a dotenv key with a comment (no values in output). Dry-run unless `write: true`.", + args: { + filePath: schema.string().optional(), + key: schema.string(), + comment: schema.string(), + line: schema.number().int().optional(), + write: schema.boolean().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + if (args.comment.trim().length === 0) { + throw new Error("Comment must be a non-empty string."); + } + + const result = await annotateEnvFile({ + file: resolved.absolutePath, + key: args.key, + comment: args.comment, + ...(args.line !== undefined ? { line: args.line } : {}), + write: args.write === true, + }); + + return { + file: resolved.displayPath, + key: result.key, + willWrite: result.willWrite, + wrote: result.wrote, + hasChanges: result.hasChanges, + issues: result.issues, + plan: result.plan, + }; + }, + }), + defineTool({ + name: "envsitter_add", + description: "Add a new key to a dotenv file (fails if key already exists). Dry-run unless `write: true`.", + args: { + filePath: schema.string().optional(), + key: schema.string(), + value: schema.string(), + write: schema.boolean().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await addEnvFileKey({ + file: resolved.absolutePath, + key: args.key, + value: args.value, + write: args.write === true, + }); + return { ...result, file: resolved.displayPath }; + }, + }), + defineTool({ + name: "envsitter_set", + description: + "Set a key's value in a dotenv file (creates if missing, updates if exists). Dry-run unless `write: true`.", + args: { + filePath: schema.string().optional(), + key: schema.string(), + value: schema.string(), + write: schema.boolean().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await setEnvFileKey({ + file: resolved.absolutePath, + key: args.key, + value: args.value, + write: args.write === true, + }); + return { ...result, file: resolved.displayPath }; + }, + }), + defineTool({ + name: "envsitter_unset", + description: + "Unset a key's value in a dotenv file (sets to empty string, keeps the key). Dry-run unless `write: true`.", + args: { + filePath: schema.string().optional(), + key: schema.string(), + write: schema.boolean().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await unsetEnvFileKey({ + file: resolved.absolutePath, + key: args.key, + write: args.write === true, + }); + return { ...result, file: resolved.displayPath }; + }, + }), + defineTool({ + name: "envsitter_delete", + description: "Delete key(s) from a dotenv file entirely (removes the line). Dry-run unless `write: true`.", + args: { + filePath: schema.string().optional(), + keys: schema.array(schema.string()), + write: schema.boolean().optional(), + }, + async run(args, env) { + const resolved = resolveEnvFilePath(env, args.filePath); + const result = await deleteEnvFileKeys({ + file: resolved.absolutePath, + keys: args.keys, + write: args.write === true, + }); + return { ...result, file: resolved.displayPath }; + }, + }), + defineTool({ + name: "envsitter_help", + description: + "Get comprehensive help on all EnvSitter tools. Call this to understand how to safely work with .env files without exposing secrets.", + args: { + topic: schema.enum(HELP_TOPICS).optional(), + }, + async run(args) { + return helpText(args.topic ?? "all"); + }, + }), +]; diff --git a/lib/v1.ts b/lib/v1.ts new file mode 100644 index 0000000..a9f2b96 --- /dev/null +++ b/lib/v1.ts @@ -0,0 +1,33 @@ +import type { Hooks, Plugin } from "@opencode-ai/plugin"; +import { tool } from "@opencode-ai/plugin/tool"; + +import { blockedAccessMessage, findBlockedAccess, redactGrepOutput } from "./policy.js"; +import { createScrubLoader } from "./scrub.js"; +import { guardTools, type ToolEnvironment } from "./tools.js"; + +export const serverV1: Plugin = async ({ directory, worktree }) => { + const env: ToolEnvironment = { directory, root: worktree }; + const loadScrub = createScrubLoader(worktree); + + const tools: NonNullable = {}; + for (const guardTool of guardTools) { + tools[guardTool.name] = tool({ + description: guardTool.description, + args: guardTool.args, + execute: (args) => guardTool.run(args, env), + }); + } + + return { + tool: tools, + "tool.execute.before": async (input, output) => { + const blocked = findBlockedAccess(input.tool, output.args); + if (blocked) throw new Error(blockedAccessMessage(blocked)); + }, + "tool.execute.after": async (input, output) => { + if (input.tool.startsWith("envsitter_") || typeof output.output !== "string") return; + const text = input.tool === "grep" ? redactGrepOutput(output.output).output : output.output; + output.output = (await loadScrub())(text); + }, + }; +}; diff --git a/lib/v2.ts b/lib/v2.ts new file mode 100644 index 0000000..d6d8354 --- /dev/null +++ b/lib/v2.ts @@ -0,0 +1,44 @@ +import type { Plugin } from "@opencode/plugin"; +import type { JsonSchema } from "effect"; + +import { blockedAccessMessage, findBlockedAccess, permissionDenial, redactGrepResult } from "./policy.js"; +import { createScrubLoader, scrubToolResult } from "./scrub.js"; +import { guardTools, type ToolEnvironment } from "./tools.js"; + +export async function setupV2(ctx: Plugin.Context): Promise { + const env: ToolEnvironment = { + directory: ctx.location.directory, + root: ctx.location.project.directory, + }; + const loadScrub = createScrubLoader(env.root); + + await ctx.tool.transform((editor) => { + for (const guardTool of guardTools) { + editor.add({ + name: guardTool.name, + description: guardTool.description, + input: guardTool.inputSchema as JsonSchema.JsonSchema, + options: { codemode: false }, + execute: async (input) => ({ content: await guardTool.run(input, env) }), + }); + } + }); + + await ctx.tool.hook("execute.before", (event) => { + const blocked = findBlockedAccess(event.tool, event.input); + if (blocked) throw new Error(blockedAccessMessage(blocked)); + }); + + await ctx.tool.hook("execute.after", async (event) => { + if (event.status !== "completed" || event.tool.startsWith("envsitter_")) return; + const result = event.tool === "grep" ? redactGrepResult(event.result) : event.result; + event.result = scrubToolResult(result, await loadScrub()); + }); + + await ctx.permission.hook("evaluate", (event) => { + const message = permissionDenial(event.action, event.resources); + if (!message) return; + event.effect = "deny"; + event.message = message; + }); +} diff --git a/package-lock.json b/package-lock.json index 97c69be..3767f0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,47 +1,2227 @@ { "name": "envsitter-guard", - "version": "0.0.3", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "envsitter-guard", - "version": "0.0.3", + "version": "0.1.0", "license": "MIT", "dependencies": { - "@opencode-ai/plugin": "^1.1.14", + "@opencode-ai/plugin": "^1.19.3", "envsitter": "^0.0.4" }, "devDependencies": { - "@types/node": "*", + "@opencode/plugin": "^2.1.0", + "@types/node": "^22.20.5", "husky": "^9.1.7", - "typescript": "*" + "typescript": "^6.0.3" + }, + "peerDependencies": { + "@opencode/plugin": ">=2.0.32" + }, + "peerDependenciesMeta": { + "@opencode/plugin": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1057.0.tgz", + "integrity": "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.978.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.4.tgz", + "integrity": "sha512-JvGp6y9sPX8FSs2K/EmFMZcUfdkHxqGE45+oxwGC75OIh50j2e4cbx7zcR3BwNs2DSGqJdBja5h6Vy89iIDmRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@aws-sdk/xml-builder": "^3.972.41", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.36.0", + "@smithy/signature-v4": "^5.7.4", + "@smithy/types": "^4.19.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.74.tgz", + "integrity": "sha512-IYfhaLlWr5Y1gyhm87aChm4SkxNAiRtNuKyUyNUqQ9igyfdlp2nPeycL0Cdi88oovBqVoHim1b9NaiEDZ3DfCA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.75", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.75.tgz", + "integrity": "sha512-nc8Epg98PcYoKNx0VFlwtmZSCP+tuJ1FLWqpslET/gp4h/WYuULSJqtxppM6ywKKDdzNJPvLD7ceRfmxm3Qdxw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.77.tgz", + "integrity": "sha512-KkYFdIQwuw9IOHfEzK78slxmVWuhcm1w6YgE3V3WVxAeRRj9q5J9jlwzpY/oszWxd4SD96FTFkDdRGBdMFF2Mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.20.tgz", + "integrity": "sha512-OSl2R/0zLkoeKEIy6oP2tu/bsSdY+DGgro2eOffMD2CcP8POv7rS14FY5Jzc5bjnqB1G9wv/aM1akHC2mdlhZw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/credential-provider-env": "^3.972.75", + "@aws-sdk/credential-provider-http": "^3.972.77", + "@aws-sdk/credential-provider-login": "^3.972.82", + "@aws-sdk/credential-provider-process": "^3.972.75", + "@aws-sdk/credential-provider-sso": "^3.973.19", + "@aws-sdk/credential-provider-web-identity": "^3.972.81", + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.82.tgz", + "integrity": "sha512-8jyDPtf1WPxeQ/5xlNpyxAD6P7YKwkYeNk8B2vFEDwkPWDvb7c3tQJG5/jbNWbF2yTMIH9tDTueBkdfovYIAPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.87", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.87.tgz", + "integrity": "sha512-TKj2C0x4IMPSSOAlmeryX8fCPvvPS2pFJz2ikV9IGqyh6Ehsg4leMalCTVfV2rvMTrlGAe7X7nK0HZ3P0KZR9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.75", + "@aws-sdk/credential-provider-http": "^3.972.77", + "@aws-sdk/credential-provider-ini": "^3.973.20", + "@aws-sdk/credential-provider-process": "^3.972.75", + "@aws-sdk/credential-provider-sso": "^3.973.19", + "@aws-sdk/credential-provider-web-identity": "^3.972.81", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.75", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.75.tgz", + "integrity": "sha512-ZNm8T/HJNhpS8MikDVAsym8QyUSUuOUGiCkmI3TTya7G6xhOgfRLd2iK91HhQQyvRM36eo6Xm845hHVyfToA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.19.tgz", + "integrity": "sha512-qHGDAGesyWtRw3BapyFkYSUzVNrC1CWPS6+S2lf/MLMEkWB2gR3Bl6imVHy1nhImXKS+gcuUDzbu39BnsHJc1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/token-providers": "3.1153.0", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.81", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.81.tgz", + "integrity": "sha512-nHkgkMIC6NHgqfI6UAQq5cCS4bUkNxkT+r9fXCq/X4IKfX/DHGZ8UM3T4b78OXpWK5OlzEzN4kxwyax0W/MUCA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1057.0.tgz", + "integrity": "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.1057.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", + "@aws-sdk/credential-provider-env": "^3.972.41", + "@aws-sdk/credential-provider-http": "^3.972.43", + "@aws-sdk/credential-provider-ini": "^3.972.46", + "@aws-sdk/credential-provider-login": "^3.972.45", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/credential-provider-process": "^3.972.41", + "@aws-sdk/credential-provider-sso": "^3.972.45", + "@aws-sdk/credential-provider-web-identity": "^3.972.45", + "@aws-sdk/nested-clients": "^3.997.13", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/credential-provider-imds": "^4.3.6", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.49", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.49.tgz", + "integrity": "sha512-zjqIqzTYLRKhT7TPJDUreK9vG9PjhmnfLCaTaqArkRT7j+Ogl0tuXB7MpSd8rlRfRT+xTOeyNFWaOfyNcf51hg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/signature-v4-multi-region": "^3.996.48", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.48.tgz", + "integrity": "sha512-cFDXfEbXaGwbnpvR/DyqfxfjxpjXVRVthQLgkwvcfDS5u3+8G9WSj1X1Ag/pTRg/QOeOno1Tq3ycEdrKsCUcOA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@smithy/signature-v4": "^5.7.4", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1153.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1153.0.tgz", + "integrity": "sha512-6bKJ2OfLboxhzeJ3hBDeB8L6emCYf9DfdrjBAharLrY42J1RV/6jA6G8jvfNghzID8Yb40BdSY7jQMtWx9Ot0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.4", + "@aws-sdk/nested-clients": "^3.997.49", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.36.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.6.tgz", + "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.10.tgz", + "integrity": "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", + "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@npmcli/agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", + "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.4.0.tgz", + "integrity": "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/config": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-10.8.1.tgz", + "integrity": "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@opencode-ai/plugin": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.14.tgz", - "integrity": "sha512-tfF4bEjeF7Gm0W0ViQUhzy77AaZfRxQ/kcPa7/Bc/YM9HddzjEqz0wOJ6ePG8UdUYc0dkKSJOJVhapUbAn/tOw==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.19.3.tgz", + "integrity": "sha512-FBN2OREfg9xiIvrxrxjZt6CCg6kn8WzKdwsD/6+a10R/M4hrdRTounUgg0LUR4cOXcI/XMKPeIWdfdaXBdmZEw==", "license": "MIT", "dependencies": { - "@opencode-ai/sdk": "1.1.14", + "@ai-sdk/provider": "3.0.8", + "@opencode-ai/sdk": "1.19.3", + "effect": "4.0.0-beta.83", "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.4.5", + "@opentui/keymap": ">=0.4.5", + "@opentui/solid": ">=0.4.5" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/keymap": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } } }, "node_modules/@opencode-ai/sdk": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.14.tgz", - "integrity": "sha512-PJFu2QPxnOk0VZzlPm+IxhD1wSA41PJyCG6gkxAMI767gfAO96A0ukJJN7VK/gO6MbxLF5oTFaxBX5rAGcBRVw==", - "license": "MIT" + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.19.3.tgz", + "integrity": "sha512-G63bZD2r6plqgjFdenWVj3HBFWmLQSVuG9CXMQuTjakzA5aL6wFLmI23Biz5ESE4lQQ08UTHcY2N4NOShJFb2w==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } }, - "node_modules/@types/node": { - "version": "25.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz", - "integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", + "node_modules/@opencode/ai": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.1.0.tgz", + "integrity": "sha512-4pEMrwKlq18OV4ccNkmPbl7tEUhLMql0QNyM0d3zqNKCiRn+LaWCerDaFZ9fQkAcLxNkRuBegPqvZhKl4uIgHQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@aws-sdk/credential-providers": "3.1057.0", + "@opencode/schema": "2.1.0", + "@smithy/eventstream-codec": "4.2.14", + "@smithy/util-utf8": "4.2.2", + "aws4fetch": "1.0.20", + "effect": "4.0.1", + "google-auth-library": "10.5.0" + } + }, + "node_modules/@opencode/ai/node_modules/effect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.1.tgz", + "integrity": "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opencode/plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.1.0.tgz", + "integrity": "sha512-lnBdxgjcKNZgk+sOdOb1RseaOKqV5+1EaLScaK3wYqfnPPk51KvfoXLRT8sQGVvGxgEmRQfKGP5tTxZ6WhGWeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode/ai": "2.1.0", + "@opencode/client": "2.1.0", + "@opencode/protocol": "2.1.0", + "@opencode/schema": "2.1.0", + "@opencode/util": "2.1.0", + "effect": "4.0.1" + }, + "peerDependencies": { + "@opencode/theme": "2.1.0", + "@opentui/core": ">=0.6.6", + "@opentui/solid": ">=0.6.6", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "@opencode/theme": { + "optional": true + }, + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/plugin/node_modules/@opencode/client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.1.0.tgz", + "integrity": "sha512-2rT2cog3QFMErvWaUvV3M5I9SvBF8p8MWC5PHh+Rno2Z828SiVQRlmXO0BwbxRou3xmAgKk5dHk0573BS2VBTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opencode/protocol": "2.1.0", + "@opencode/schema": "2.1.0" + }, + "peerDependencies": { + "effect": "4.0.1", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "effect": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/plugin/node_modules/effect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.1.tgz", + "integrity": "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opencode/protocol": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.1.0.tgz", + "integrity": "sha512-Zo1TDw5Yx41Bi7+I0Djs0b92IcPP7Jg/k+lL6FLm+m46TT27wIG7n8xPV490LTo4sXTtknYddnU4aLpwqaIa3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opencode/schema": "2.1.0", + "effect": "4.0.1" + } + }, + "node_modules/@opencode/protocol/node_modules/effect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.1.tgz", + "integrity": "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opencode/schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.1.0.tgz", + "integrity": "sha512-Cv1bP3lEDXbRvmlw7pwDhMNFSAOy+Zo8zBs/t0lP1iWZE6tqMhRb1lsvaX6DPI8iwzF3QW2b9dKcixPjlqwAhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.1.0", + "effect": "4.0.1" + } + }, + "node_modules/@opencode/schema/node_modules/effect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.1.tgz", + "integrity": "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opencode/util": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.1.0.tgz", + "integrity": "sha512-LttLlMxN+FpodiNK2avxm6BZ1U/u3TwB2fU36g2QIN1g/A7pQ4RfsGSBAZBQ0CKkC60Bi6WADrZUJc7a40wQqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@effect/opentelemetry": "4.0.1", + "@effect/platform-node": "4.0.1", + "@effect/platform-node-shared": "4.0.1", + "@npmcli/arborist": "9.4.0", + "@npmcli/config": "10.8.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/context-async-hooks": "2.8.0", + "@opentelemetry/exporter-trace-otlp-http": "0.219.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-logs": "0.219.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0", + "@opentelemetry/sdk-trace-node": "2.8.0", + "cross-spawn": "7.0.6", + "effect": "4.0.1", + "glob": "13.0.5", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "npm-package-arg": "13.0.2", + "open": "11.0.4", + "pacote": "21.5.1" + } + }, + "node_modules/@opencode/util/node_modules/@effect/opentelemetry": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@effect/opentelemetry/-/opentelemetry-4.0.1.tgz", + "integrity": "sha512-MZ7M1a1dxMqTqeNwIGSECjO7RrReEgeEwHo8BnbOqP+8IfVpdiJi/iPCmkFVWlAxanMl4qhsq69e0Hkul9t+bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <2.0.0", + "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/resources": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", + "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", + "effect": "^4.0.1" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/api-logs": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-logs": { + "optional": true + }, + "@opentelemetry/sdk-metrics": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/sdk-trace-node": { + "optional": true + }, + "@opentelemetry/sdk-trace-web": { + "optional": true + } + } + }, + "node_modules/@opencode/util/node_modules/@effect/platform-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@effect/platform-node/-/platform-node-4.0.1.tgz", + "integrity": "sha512-FT0Fu5LO0/XP9zBpj0HqrtJNLluK4StNKqf6TSulUsjp8FPBsDVXGJiDuyoN5J2yVMT7tgvhxSwaDcObWVRIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@effect/platform-node-shared": "^4.0.1", + "undici": "^8.11.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.1", + "redis": ">=5.0.0 <7.0.0" + } + }, + "node_modules/@opencode/util/node_modules/@effect/platform-node-shared": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@effect/platform-node-shared/-/platform-node-shared-4.0.1.tgz", + "integrity": "sha512-VanaejudEhKJ4mpmqnEzdncVUhN6sGSO9ZDND54mRxAlZxj+4dYAWjDTYAy9o4tJo9aALeP2U8cVcOFzEdSeLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ws": "^8.18.1", + "ws": "^8.22.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.1" + } + }, + "node_modules/@opencode/util/node_modules/effect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.1.tgz", + "integrity": "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opencode/util/node_modules/undici": { + "version": "8.12.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.3.tgz", + "integrity": "sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz", + "integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz", + "integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.219.0.tgz", + "integrity": "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-exporter-base": "0.219.0", + "@opentelemetry/otlp-transformer": "0.219.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz", + "integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-transformer": "0.219.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz", + "integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-logs": "0.219.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz", + "integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.8.0.tgz", + "integrity": "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.8.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/bloom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.3.0.tgz", + "integrity": "sha512-NQ5poYpZr0jv6zazN1cy6JY5kIipKFZ3R9GTL4kOUTX3a9YAfY29U8bsE4lWhVQEM+G4V0BXVC57mubIdDijeA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/client": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.3.0.tgz", + "integrity": "sha512-fHrjwGCBvAANGi+TuRuwKi83sIOzgHr8eh+Uf48w3fSX7Hhrz174NrWqfJhAC2vnN0d/tfaZpdr6+o6JC3QHwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.3.0.tgz", + "integrity": "sha512-yt6vRfPBXtW/3qm53cF81TSQpLMjWdW4/2E9WQ2GgbvsKkuCKCFQIcMiKlil1ZMnezK+IQuS795xKU5psbaegw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/search": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.3.0.tgz", + "integrity": "sha512-RPXAZmKjZZG7Tm7m1w9keFdXzbL91PiSHQBfQRe9xwW40jNsTRbG0FwhIz4QlHPU4gKDAsCy8IQHj4yZl4H9rw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/time-series": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.3.0.tgz", + "integrity": "sha512-+fJOB8mN1z5WRakCJNh5lVdz1KqvHGSOistsKeitD24QsCtPwyRQXWC0iq6T/i/ubLE/YcguZqPm8F598MKk4A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz", + "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.2.tgz", + "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz", + "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.0.tgz", + "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.5.2.tgz", + "integrity": "sha512-nxu3SgmAw9JXT2CtkU0m/XNLWpP9MsaBx1zAGAypCbYj15tIFlmcYwpF+Oh18le83d+IM9PT7ENdXnE4C+d5mA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.5.2.tgz", + "integrity": "sha512-iq+cW3mAb7vfcxEEpYi3zXKpDtbrIFyanWjQl4zBq4seWD4OSxXDWSfespZxenX6aEaighn+NR3u1nU1DSvs3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws4fetch": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bin-links": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.2.tgz", + "integrity": "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cmd-shim": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/effect": { + "version": "4.0.0-beta.83", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.83.tgz", + "integrity": "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "fast-check": "^4.8.0", + "find-my-way-ts": "^0.1.6", + "ini": "^7.0.0", + "kubernetes-types": "^1.30.0", + "msgpackr": "^2.0.1", + "multipasta": "^0.2.7", + "toml": "^4.1.1", + "uuid": "^14.0.0", + "yaml": "^2.9.0" + } + }, + "node_modules/effect/node_modules/ini": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-7.0.0.tgz", + "integrity": "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==", + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/envsitter": { @@ -53,6 +2233,305 @@ "envsitter": "dist/cli.js" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.10.2.tgz", + "integrity": "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/find-my-way-ts": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", + "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.4.tgz", + "integrity": "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/glob": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.5.tgz", + "integrity": "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.2.0.tgz", + "integrity": "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -69,10 +2548,1417 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ip-address": { + "version": "10.7.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kubernetes-types": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", + "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", + "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.1.2.tgz", + "integrity": "sha512-JHAdwubWNEadHQsFAib6uXOzKFxY8QOWHu9LCCksOINMC+TjgaKS2Tdrw7dW5SY0LLz8YK2/YqLSi2vigQt05w==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.4" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/multipasta": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.8.tgz", + "integrity": "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "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" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/open": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.1.1.tgz", + "integrity": "sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.5.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", + "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/parse-conflict-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pure-rand": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz", + "integrity": "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/read-cmd-shim": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/redis": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-6.3.0.tgz", + "integrity": "sha512-XFQbPie1lGpKeUZ8ySYY43yLnQy/iIQtHNvIr/1XdaABpb96rUSdm3Mfl+98FEVbv9ZJIzINMno5s0Nh8E5LEw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@redis/bloom": "6.3.0", + "@redis/client": "6.3.0", + "@redis/json": "6.3.0", + "@redis/search": "6.3.0", + "@redis/time-series": "6.3.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz", + "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-4.3.0.tgz", + "integrity": "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -83,13 +3969,264 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz", + "integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "dev": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", + "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/zod": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", diff --git a/package.json b/package.json index 1beab06..46ae321 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "envsitter-guard", - "version": "0.0.4", - "description": "OpenCode plugin that prevents agents/tools from reading or editing sensitive .env* files, while still allowing safe inspection via EnvSitter.", + "version": "0.1.0", + "description": "OpenCode plugin (OpenCode 1 and 2) that prevents agents/tools from reading or editing sensitive .env files, while still allowing safe inspection via EnvSitter.", "license": "MIT", + "homepage": "https://boxpositron.github.io/envsitter/guard/", "keywords": [ "opencode", "opencode-plugin", @@ -40,17 +41,27 @@ "typecheck": "tsc -p tsconfig.json", "build:test": "tsc -p tsconfig.test.json", "test": "npm run build:test && node --test dist-test/test/*.test.js", + "test:e2e": "npm run build && node scripts/e2e-opencode.mjs", "prepack": "npm run build", "prepublishOnly": "npm run typecheck && npm test && npm run build" }, "dependencies": { - "@opencode-ai/plugin": "^1.1.14", - "envsitter": "^0.0.4" + "@opencode-ai/plugin": "^1.19.3", + "envsitter": "^0.1.0" + }, + "peerDependencies": { + "@opencode/plugin": ">=2.0.32" + }, + "peerDependenciesMeta": { + "@opencode/plugin": { + "optional": true + } }, "devDependencies": { - "@types/node": "*", + "@opencode/plugin": "^2.1.0", + "@types/node": "^22.20.5", "husky": "^9.1.7", - "typescript": "*" + "typescript": "^6.0.3" }, "publishConfig": { "access": "public" diff --git a/scripts/e2e-opencode.mjs b/scripts/e2e-opencode.mjs new file mode 100644 index 0000000..194165a --- /dev/null +++ b/scripts/e2e-opencode.mjs @@ -0,0 +1,295 @@ +#!/usr/bin/env node +// Cross-version end-to-end check: loads the built plugin (dist/) into the real OpenCode 1 and OpenCode 2 CLIs, +// drives them with a scripted OpenAI-compatible model, and asserts what the model receives back from each tool. +// Everything runs in a temporary HOME with fake secrets, so real config, credentials, and databases are never touched. +// +// Usage: npm run build && node scripts/e2e-opencode.mjs +// Env: OPENCODE_V1_BIN (default "opencode"), OPENCODE_V2_BIN (default "opencode2"), KEEP_E2E=1 to keep the temp dir, +// E2E_REQUIRE_ALL=1 to fail (instead of skip) when a CLI is missing, as CI does. + +import { spawn, spawnSync } from "node:child_process"; +import fs from "node:fs"; +import http from "node:http"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const pluginEntry = path.join(repoRoot, "dist", "index.js"); +const FAKE_SECRET = "sk-fake-guard-e2e-123"; +const RUN_TIMEOUT_MS = 240_000; + +if (!fs.existsSync(pluginEntry)) { + console.error("dist/index.js is missing. Run `npm run build` first."); + process.exit(2); +} + +const versions = [ + { + name: "OpenCode 1", + major: 1, + bin: process.env.OPENCODE_V1_BIN ?? "opencode", + args: (model, prompt) => ["run", "--model", model, prompt], + }, + { + name: "OpenCode 2", + major: 2, + bin: process.env.OPENCODE_V2_BIN ?? "opencode2", + args: (model, prompt) => ["run", "--standalone", "--auto", "--model", model, prompt], + }, +]; + +const textOf = (content) => + typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => part?.text ?? "").join("") : ""; +const toolNames = (tools) => tools.map((tool) => tool.function?.name).filter(Boolean); +const toolProps = (tools, name) => tools.find((tool) => tool.function?.name === name)?.function?.parameters?.properties ?? {}; +const pathArg = (tools, name, value) => ("filePath" in toolProps(tools, name) ? { filePath: value } : { path: value }); +const firstTool = (tools, names) => names.find((name) => toolNames(tools).includes(name)); + +function scenarios(project) { + const envFile = path.join(project, ".env"); + const envBefore = fs.readFileSync(envFile, "utf8"); + const noLeak = (result) => !result.includes(FAKE_SECRET); + + return [ + { + name: "envsitter_keys lists keys without values", + plan: () => ["envsitter_keys", { filePath: ".env" }], + pass: (result) => result.includes("API_TOKEN") && result.includes("DB_PASSWORD") && noLeak(result), + }, + { + name: "read .env is blocked", + plan: (tools) => ["read", pathArg(tools, "read", envFile)], + pass: (result) => result.includes("Reading `.env*` is blocked") && noLeak(result), + }, + { + name: "read .env.example is allowed", + plan: (tools) => ["read", pathArg(tools, "read", path.join(project, ".env.example"))], + pass: (result) => result.includes("replace-me"), + }, + { + name: "write .env is blocked", + plan: (tools) => ["write", { ...pathArg(tools, "write", envFile), content: "API_TOKEN=overwritten\n" }], + pass: (result) => result.includes("Editing `.env*`") && fs.readFileSync(envFile, "utf8") === envBefore, + }, + { + name: "shell `cat .env` is blocked", + plan: (tools) => { + const name = firstTool(tools, ["bash", "shell"]); + if (!name) return undefined; + const args = { command: "cat .env" }; + if ("description" in toolProps(tools, name)) args.description = "Print env file"; + return [name, args]; + }, + pass: (result) => result.includes("Shell commands that reference") && noLeak(result), + }, + { + name: "shell output reaching .env values without naming the file is scrubbed", + plan: (tools) => { + const name = firstTool(tools, ["bash", "shell"]); + if (!name) return undefined; + const args = { command: "cat logs/app.log" }; + if ("description" in toolProps(tools, name)) args.description = "Print app log"; + return [name, args]; + }, + pass: (result) => result.includes("Bearer [envsitter: API_TOKEN redacted]") && noLeak(result), + }, + { + name: "read of a non-env file containing a secret is scrubbed", + plan: (tools) => ["read", pathArg(tools, "read", path.join(project, "logs", "app.log"))], + pass: (result) => result.includes("Bearer [envsitter: API_TOKEN redacted]") && noLeak(result), + }, + { + name: "grep hides matches inside .env", + plan: () => ["grep", { pattern: "API_TOKEN" }], + pass: (result) => result.includes("app.ts") && noLeak(result), + }, + { + name: "patch adding .env.local is blocked", + model: "mock/gpt-5-scripted", + plan: (tools) => { + const name = firstTool(tools, ["apply_patch", "patch"]); + return name && [name, { patchText: "*** Begin Patch\n*** Add File: .env.local\n+X=1\n*** End Patch" }]; + }, + pass: (result) => result.includes("Editing `.env*`") && !fs.existsSync(path.join(project, ".env.local")), + }, + ]; +} + +function startModel(state) { + const send = (res, chunks) => { + res.writeHead(200, { "content-type": "text/event-stream" }); + for (const chunk of chunks) res.write(`data: ${typeof chunk === "string" ? chunk : JSON.stringify(chunk)}\n\n`); + res.end(); + }; + const base = { id: "chatcmpl-e2e", object: "chat.completion.chunk", created: 0, model: "scripted" }; + const finish = (reason) => ({ ...base, choices: [{ index: 0, delta: {}, finish_reason: reason }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } }); + const text = (res, content) => + send(res, [{ ...base, choices: [{ index: 0, delta: { role: "assistant", content }, finish_reason: null }] }, finish("stop"), "[DONE]"]); + const toolCall = (res, name, args) => + send(res, [ + { + ...base, + choices: [ + { + index: 0, + delta: { role: "assistant", tool_calls: [{ index: 0, id: "call_e2e_1", type: "function", function: { name, arguments: JSON.stringify(args) } }] }, + finish_reason: null, + }, + ], + }, + finish("tool_calls"), + "[DONE]", + ]); + + const server = http.createServer((req, res) => { + let raw = ""; + req.on("data", (chunk) => (raw += chunk)); + req.on("end", () => { + if (!req.url?.endsWith("/chat/completions")) { + res.writeHead(404).end(); + return; + } + const body = JSON.parse(raw || "{}"); + const messages = body.messages ?? []; + const tools = body.tools ?? []; + const marker = messages.map((m) => (m.role === "user" ? /E2E-SCENARIO:(\d+)/.exec(textOf(m.content))?.[1] : undefined)).find(Boolean); + const scenario = marker === undefined ? undefined : state.scenarios[Number(marker)]; + if (!scenario || tools.length === 0) return text(res, "Guard e2e"); + + state.toolNames = toolNames(tools); + const last = messages.at(-1); + if (last?.role === "tool") { + state.result = textOf(last.content); + return text(res, "DONE"); + } + const planned = scenario.plan(tools); + if (!planned) { + state.result = "<>"; + return text(res, "UNAVAILABLE"); + } + state.call = planned; + return toolCall(res, planned[0], planned[1]); + }); + }); + + return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve(server))); +} + +function createProject(root, baseURL) { + const project = path.join(root, "project"); + fs.mkdirSync(path.join(project, "src"), { recursive: true }); + fs.mkdirSync(path.join(project, ".opencode", "plugin"), { recursive: true }); + fs.writeFileSync(path.join(project, ".env"), `API_TOKEN=${FAKE_SECRET}\nDB_PASSWORD=fake-db-pass-456\n`); + fs.writeFileSync(path.join(project, ".env.example"), "API_TOKEN=replace-me\n"); + fs.writeFileSync(path.join(project, "src", "app.ts"), "export const token = process.env.API_TOKEN;\n"); + fs.mkdirSync(path.join(project, "logs"), { recursive: true }); + fs.writeFileSync(path.join(project, "logs", "app.log"), `request ok\nauth header: Bearer ${FAKE_SECRET}\n`); + fs.writeFileSync( + path.join(project, ".opencode", "plugin", "envsitter-guard.ts"), + `export { default, EnvSitterGuard } from ${JSON.stringify(pluginEntry)};\n`, + ); + fs.writeFileSync( + path.join(project, "opencode.json"), + JSON.stringify( + { + $schema: "https://opencode.ai/config.json", + model: "mock/scripted", + small_model: "mock/scripted", + provider: { + mock: { + npm: "@ai-sdk/openai-compatible", + name: "Mock", + options: { baseURL, apiKey: "e2e" }, + models: { scripted: { name: "Scripted", tool_call: true }, "gpt-5-scripted": { name: "GPT 5 Scripted", tool_call: true } }, + }, + }, + permission: { edit: "allow", bash: "allow", webfetch: "allow" }, + }, + null, + 2, + ), + ); + spawnSync("git", ["init", "-q"], { cwd: project }); + return project; +} + +function isolatedEnv(home, project) { + return { + ...process.env, + HOME: home, + PWD: project, + XDG_CONFIG_HOME: path.join(home, ".config"), + XDG_DATA_HOME: path.join(home, ".local", "share"), + XDG_CACHE_HOME: path.join(home, ".cache"), + XDG_STATE_HOME: path.join(home, ".local", "state"), + OPENCODE_DISABLE_MODELS_FETCH: "true", + ENVSITTER_PEPPER: "e2e-pepper", + }; +} + +function run(bin, args, options) { + return new Promise((resolve) => { + const child = spawn(bin, args, { ...options, stdio: ["ignore", "pipe", "pipe"] }); + let stderr = ""; + child.stderr.on("data", (chunk) => (stderr += chunk)); + child.stdout.resume(); + const timer = setTimeout(() => child.kill("SIGKILL"), RUN_TIMEOUT_MS); + child.on("close", (code) => { + clearTimeout(timer); + resolve({ code, stderr }); + }); + child.on("error", (error) => { + clearTimeout(timer); + resolve({ code: -1, stderr: String(error) }); + }); + }); +} + +const tempRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "envsitter-guard-e2e-"))); +const state = { scenarios: [] }; +const server = await startModel(state); +const baseURL = `http://127.0.0.1:${server.address().port}/v1`; +let failures = 0; + +for (const version of versions) { + const probe = spawnSync(version.bin, ["--version"], { encoding: "utf8" }); + const reported = `${probe.stdout ?? ""}`.trim(); + const major = Number(/(\d+)\.\d+\.\d+/.exec(reported)?.[1]); + if (probe.status !== 0 || major !== version.major) { + const required = process.env.E2E_REQUIRE_ALL === "1"; + if (required) failures += 1; + console.log(`\n${version.name}: ${required ? "FAIL, missing" : "skipped"} (\`${version.bin} --version\` reported ${JSON.stringify(reported || probe.error?.message || "nothing")})`); + continue; + } + + const versionRoot = path.join(tempRoot, `v${version.major}`); + const project = createProject(versionRoot, baseURL); + const env = isolatedEnv(path.join(versionRoot, "home"), project); + state.scenarios = scenarios(project); + console.log(`\n${version.name} (${reported})`); + + for (const [index, scenario] of state.scenarios.entries()) { + state.result = undefined; + state.call = undefined; + const outcome = await run(version.bin, version.args(scenario.model ?? "mock/scripted", `E2E-SCENARIO:${index}`), { cwd: project, env }); + const result = state.result ?? "<>"; + const ok = outcome.code === 0 && result !== "<>" && scenario.pass(result); + if (!ok) failures += 1; + const via = state.call ? ` via ${state.call[0]}` : ""; + console.log(` ${ok ? "PASS" : "FAIL"} ${scenario.name}${via}`); + if (!ok) console.log(` exit=${outcome.code} result=${JSON.stringify(result.slice(0, 300))} stderr=${JSON.stringify(outcome.stderr.slice(-300))}`); + } + + const visible = (state.toolNames ?? []).filter((name) => name.startsWith("envsitter_")).length; + const toolsOk = visible === 15; + if (!toolsOk) failures += 1; + console.log(` ${toolsOk ? "PASS" : "FAIL"} model sees all 15 envsitter_* tools directly (${visible})`); +} + +server.close(); +if (process.env.KEEP_E2E === "1") console.log(`\nKept ${tempRoot}`); +else fs.rmSync(tempRoot, { recursive: true, force: true }); + +console.log(failures === 0 ? "\nAll checks passed." : `\n${failures} check(s) failed.`); +process.exitCode = failures === 0 ? 0 : 1; diff --git a/test/envsitter-guard.hook.test.ts b/test/envsitter-guard.hook.test.ts index 0621aec..0905d14 100644 --- a/test/envsitter-guard.hook.test.ts +++ b/test/envsitter-guard.hook.test.ts @@ -9,6 +9,7 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin"; import EnvSitterGuard from "../index.js"; type ToolExecuteBeforeHook = NonNullable; +type ToolExecuteAfterHook = NonNullable; async function createTmpDir(): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "envsitter-guard-")); @@ -37,7 +38,7 @@ function createClientSpy(): { }; } -async function getBeforeHook(params: { directory: string; worktree: string }) { +async function getHooks(params: { directory: string; worktree: string }) { const { client, calls } = createClientSpy(); const pluginInput: PluginInput = { @@ -45,72 +46,142 @@ async function getBeforeHook(params: { directory: string; worktree: string }) { project: {} as unknown as PluginInput["project"], directory: params.directory, worktree: params.worktree, + experimental_workspace: { register() {} }, serverUrl: new URL("http://localhost"), $: (() => { throw new Error("not used in tests"); }) as unknown as PluginInput["$"], }; - const hooks = (await EnvSitterGuard(pluginInput)) as { + const hooks = (await EnvSitterGuard.server(pluginInput)) as { "tool.execute.before": ToolExecuteBeforeHook; + "tool.execute.after": ToolExecuteAfterHook; }; - return { hook: hooks["tool.execute.before"], calls }; + return { before: hooks["tool.execute.before"], after: hooks["tool.execute.after"], calls }; +} + +async function getBeforeHook() { + const worktree = await createTmpDir(); + return getHooks({ directory: worktree, worktree }); +} + +function call(tool: string) { + return { tool, sessionID: "s", callID: "c" }; } test("blocks reading .env", async () => { - const worktree = await createTmpDir(); - const { hook } = await getBeforeHook({ directory: worktree, worktree }); + const { before } = await getBeforeHook(); await assert.rejects( - () => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: ".env" } }), + () => before(call("read"), { args: { filePath: ".env" } }), (err: unknown) => err instanceof Error && err.message.includes("Reading `.env*` is blocked"), ); }); -test("allows reading .env.example", async () => { - const worktree = await createTmpDir(); - const { hook } = await getBeforeHook({ directory: worktree, worktree }); +test("allows reading example and template env files", async () => { + const { before } = await getBeforeHook(); - await hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: ".env.example" } }); + for (const filePath of [".env.example", ".env.sample", ".env.template", "api.env.example"]) { + await before(call("read"), { args: { filePath } }); + } +}); + +test("blocks reading named env files such as api.env and config.env.local", async () => { + const { before } = await getBeforeHook(); + + for (const filePath of ["api.env", "config/config.env.local", "/abs/project/.env.production"]) { + await assert.rejects( + () => before(call("read"), { args: { filePath } }), + (err: unknown) => err instanceof Error && err.message.includes("Reading `.env*` is blocked"), + filePath, + ); + } }); test("blocks editing .env", async () => { - const worktree = await createTmpDir(); - const { hook } = await getBeforeHook({ directory: worktree, worktree }); + const { before } = await getBeforeHook(); await assert.rejects( - () => hook({ tool: "edit", sessionID: "s", callID: "c" }, { args: { filePath: ".env" } }), + () => before(call("edit"), { args: { filePath: ".env" } }), (err: unknown) => err instanceof Error && err.message.includes("Editing `.env*"), ); }); -test("blocks .envsitter/pepper", async () => { - const worktree = await createTmpDir(); - const { hook } = await getBeforeHook({ directory: worktree, worktree }); +test("blocks apply_patch when the patch touches .env", async () => { + const { before } = await getBeforeHook(); + const patchText = ["*** Begin Patch", "*** Update File: src/app.ts", "@@", "*** Add File: .env.local", "+TOKEN=x", "*** End Patch"].join("\n"); await assert.rejects( - () => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: ".envsitter/pepper" } }), + () => before(call("apply_patch"), { args: { patchText } }), + (err: unknown) => err instanceof Error && err.message.includes("`.env.local`"), + ); + + await before(call("apply_patch"), { args: { patchText: "*** Begin Patch\n*** Update File: src/app.ts\n*** End Patch" } }); +}); + +test("blocks shell commands that name .env files but allows the envsitter CLI", async () => { + const { before } = await getBeforeHook(); + + await assert.rejects( + () => before(call("bash"), { args: { command: "cat .env | head -5" } }), + (err: unknown) => err instanceof Error && err.message.includes("Shell commands that reference"), + ); + + await before(call("bash"), { args: { command: "npx envsitter keys --file .env --json" } }); + await before(call("bash"), { args: { command: 'git commit -m "document .env usage"' } }); +}); + +test("blocks .envsitter/pepper", async () => { + const { before } = await getBeforeHook(); + + await assert.rejects( + () => before(call("read"), { args: { filePath: ".envsitter/pepper" } }), (err: unknown) => err instanceof Error && err.message.includes("blocked"), ); }); test("strips @ prefix in filePath", async () => { - const worktree = await createTmpDir(); - const { hook } = await getBeforeHook({ directory: worktree, worktree }); + const { before } = await getBeforeHook(); await assert.rejects( - () => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: "@.env" } }), + () => before(call("read"), { args: { filePath: "@.env" } }), (err: unknown) => err instanceof Error && err.message.includes("Reading `.env*` is blocked"), ); }); test("blocking is silent (no toasts)", async () => { - const worktree = await createTmpDir(); - const { hook, calls } = await getBeforeHook({ directory: worktree, worktree }); + const { before, calls } = await getBeforeHook(); - await assert.rejects(() => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: ".env" } })); - await assert.rejects(() => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { filePath: ".env" } })); + await assert.rejects(() => before(call("read"), { args: { filePath: ".env" } })); + await assert.rejects(() => before(call("read"), { args: { filePath: ".env" } })); assert.equal(calls.showToast, 0, "should not show toasts, only throw errors"); }); + +test("hides grep matches from protected env files", async () => { + const { after } = await getBeforeHook(); + const output = { + title: "TOKEN", + metadata: {}, + output: ["Found 3 matches", "/repo/.env:", " Line 1: TOKEN=super-secret", "", "/repo/src/app.ts:", " Line 4: process.env.TOKEN", " Line 9: TOKEN"].join("\n"), + }; + + await after({ ...call("grep"), args: { pattern: "TOKEN" } }, output); + + assert.ok(!output.output.includes("super-secret")); + assert.ok(output.output.startsWith("Found 2 matches")); + assert.ok(output.output.includes("/repo/src/app.ts:")); + assert.ok(output.output.includes("1 match in protected .env files hidden")); +}); + +test("scrubs secret values from any tool output, such as shell commands that never name .env", async () => { + const worktree = await createTmpDir(); + await fs.writeFile(path.join(worktree, ".env"), "API_TOKEN=tok_live_9f8a7b6c5d4e\nNODE_ENV=development\n"); + const { after } = await getHooks({ directory: worktree, worktree }); + + const output = { title: "cat logs/app.log", metadata: {}, output: "env=development\ncalling api with tok_live_9f8a7b6c5d4e\n" }; + await after({ ...call("bash"), args: { command: "cat logs/app.log" } }, output); + + assert.equal(output.output, "env=development\ncalling api with [envsitter: API_TOKEN redacted]\n"); +}); diff --git a/test/envsitter-guard.tools.test.ts b/test/envsitter-guard.tools.test.ts index 161186a..d79ab59 100644 --- a/test/envsitter-guard.tools.test.ts +++ b/test/envsitter-guard.tools.test.ts @@ -125,13 +125,14 @@ async function getTools(params: { directory: string; worktree: string }): Promis project: {} as unknown as PluginInput["project"], directory: params.directory, worktree: params.worktree, + experimental_workspace: { register() {} }, serverUrl: new URL("http://localhost"), $: (() => { throw new Error("not used in tests"); }) as unknown as PluginInput["$"], }; - const hooks = (await EnvSitterGuard(pluginInput)) as unknown as { + const hooks = (await EnvSitterGuard.server(pluginInput)) as unknown as { tool: ToolApi; }; @@ -305,6 +306,24 @@ test("tool execution rejects paths outside worktree", async () => { ); }); +test("envsitter tools accept named env files such as api.env", async () => { + await withPepper(async () => { + const worktree = await createTmpDir(); + await fs.writeFile(path.join(worktree, "api.env"), "API_TOKEN=secret-token\n"); + + const tools = await getTools({ directory: worktree, worktree }); + const out = await tools.envsitter_keys.execute({ filePath: "api.env" }); + + assert.ok(!out.includes("secret-token")); + assert.deepEqual((JSON.parse(out) as { keys: string[] }).keys, ["API_TOKEN"]); + + await assert.rejects( + () => tools.envsitter_keys.execute({ filePath: "src/index.ts" }), + (err: unknown) => err instanceof Error && err.message.includes("Only dotenv files are allowed"), + ); + }); +}); + test("tool execution blocks .envsitter/pepper", async () => { const worktree = await createTmpDir(); const tools = await getTools({ directory: worktree, worktree }); diff --git a/test/envsitter-guard.v2.test.ts b/test/envsitter-guard.v2.test.ts new file mode 100644 index 0000000..f60c5d1 --- /dev/null +++ b/test/envsitter-guard.v2.test.ts @@ -0,0 +1,204 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import type { Plugin } from "@opencode/plugin"; + +import EnvSitterGuard from "../index.js"; + +type RegisteredTool = { + name: string; + description: string; + input: Record; + options?: { codemode?: boolean }; + execute: (input: unknown, context: unknown) => Promise<{ content?: unknown }>; +}; + +type HookCallback = (event: Record) => unknown; + +const registration = { dispose: async () => {} }; + +async function setupV2(directory: string, root = directory) { + const tools = new Map(); + const toolHooks = new Map(); + const permissionHooks = new Map(); + + const ctx = { + location: { directory, project: { id: "project", directory: root, canonical: root } }, + options: {}, + tool: { + async transform(callback: (editor: { add(tool: RegisteredTool): void }) => void) { + callback({ add: (tool) => tools.set(tool.name, tool) }); + return registration; + }, + async hook(name: string, callback: HookCallback) { + toolHooks.set(name, callback); + return registration; + }, + }, + permission: { + async hook(name: string, callback: HookCallback) { + permissionHooks.set(name, callback); + return registration; + }, + }, + } as unknown as Plugin.Context; + + await EnvSitterGuard.setup(ctx); + + const hook = (map: Map, name: string): HookCallback => { + const callback = map.get(name); + assert.ok(callback, `missing hook ${name}`); + return callback; + }; + + return { + tools, + before: hook(toolHooks, "execute.before"), + after: hook(toolHooks, "execute.after"), + evaluate: hook(permissionHooks, "evaluate"), + }; +} + +async function createProject(files: Record): Promise { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "envsitter-guard-v2-")); + for (const [name, contents] of Object.entries(files)) { + await fs.mkdir(path.dirname(path.join(dir, name)), { recursive: true }); + await fs.writeFile(path.join(dir, name), contents); + } + return dir; +} + +function toolEvent(tool: string, input: Record) { + return { tool, sessionID: "ses_1", agent: "build", messageID: "msg_1", id: "call_1", input }; +} + +test("V2 default export carries a stable id plus both entrypoints", () => { + assert.equal(EnvSitterGuard.id, "envsitter-guard"); + assert.equal(typeof EnvSitterGuard.setup, "function"); + assert.equal(typeof EnvSitterGuard.server, "function"); +}); + +test("V2 registers every envsitter tool directly (not behind Code Mode) with JSON Schema inputs", async () => { + const { tools } = await setupV2(await createProject({})); + + assert.equal(tools.size, 15); + for (const tool of tools.values()) { + assert.ok(tool.name.startsWith("envsitter_"), tool.name); + assert.equal(tool.options?.codemode, false, tool.name); + assert.equal(tool.input.type, "object", tool.name); + assert.equal("$schema" in tool.input, false, tool.name); + } + + const fingerprint = tools.get("envsitter_fingerprint"); + assert.deepEqual(fingerprint?.input.required, ["key"]); +}); + +test("V2 tools return structured content without values and validate input", async () => { + const dir = await createProject({ ".env": "API_TOKEN=very-secret\nPORT=3000\n" }); + const { tools } = await setupV2(dir); + + const keys = tools.get("envsitter_keys"); + assert.ok(keys); + const result = await keys.execute({ filePath: ".env" }, {}); + assert.equal(typeof result.content, "string"); + assert.ok(!String(result.content).includes("very-secret")); + assert.deepEqual((JSON.parse(String(result.content)) as { keys: string[] }).keys, ["API_TOKEN", "PORT"]); + + const fingerprint = tools.get("envsitter_fingerprint"); + assert.ok(fingerprint); + await assert.rejects(() => fingerprint.execute({ filePath: ".env" }, {})); +}); + +test("V2 tools resolve paths from the plugin location and stay inside the project", async () => { + const root = await createProject({ "apps/api/.env": "A=1\n" }); + const { tools } = await setupV2(path.join(root, "apps", "api"), root); + + const keys = tools.get("envsitter_keys"); + assert.ok(keys); + const result = await keys.execute({}, {}); + assert.equal((JSON.parse(String(result.content)) as { file: string }).file, path.join("apps", "api", ".env")); + + await assert.rejects( + () => keys.execute({ filePath: "../../../outside/.env" }, {}), + (err: unknown) => err instanceof Error && err.message.includes("inside the current project"), + ); +}); + +test("V2 execute.before blocks built-in tools on protected files, including nested Code Mode calls", async () => { + const { before } = await setupV2(await createProject({})); + + assert.throws(() => before(toolEvent("read", { path: ".env" })), /Reading `\.env\*` is blocked/); + assert.throws(() => before(toolEvent("write", { path: "api.env", content: "X=1" })), /Editing `\.env\*`/); + assert.throws( + () => before(toolEvent("patch", { patchText: "*** Begin Patch\n*** Delete File: .env.production\n*** End Patch" })), + /Editing `\.env\*`/, + ); + assert.throws(() => before(toolEvent("shell", { command: "source .env && env" })), /Shell commands that reference/); + assert.throws(() => before(toolEvent("grep", { pattern: "TOKEN", path: ".env.local" })), /Reading `\.env\*` is blocked/); + assert.throws(() => before(toolEvent("filesystem_read_file", { path: "/abs/.env" })), /through `filesystem_read_file`/); + + before(toolEvent("read", { path: ".env.example" })); + before(toolEvent("read", { path: "src/index.ts" })); + before(toolEvent("shell", { command: "npm test" })); + before(toolEvent("envsitter_keys", { filePath: ".env" })); +}); + +test("V2 execute.after hides grep matches from protected files in content and structured output", async () => { + const { after } = await setupV2(await createProject({})); + + const event = { + ...toolEvent("grep", { pattern: "TOKEN" }), + status: "completed", + result: { + content: ["Found 2 matches", "/repo/.env:", " Line 1: TOKEN=super-secret", "", "/repo/src/app.ts:", " Line 3: TOKEN"].join("\n"), + output: [ + { entry: { path: ".env" }, line: 1, text: "TOKEN=super-secret" }, + { entry: { path: "src/app.ts" }, line: 3, text: "TOKEN" }, + ], + }, + }; + + await after(event); + + const result = event.result as { content: string; output: Array<{ entry: { path: string } }> }; + assert.ok(!result.content.includes("super-secret")); + assert.ok(result.content.startsWith("Found 1 matches")); + assert.deepEqual( + result.output.map((match) => match.entry.path), + ["src/app.ts"], + ); +}); + +test("V2 execute.after scrubs secret values from shell results that never name .env", async () => { + const { after } = await setupV2(await createProject({ ".env": "API_TOKEN=tok_live_9f8a7b6c5d4e\n" })); + + const event = { + ...toolEvent("shell", { command: "grep -r api ." }), + status: "completed", + result: { content: [{ type: "text", text: "./logs/app.log: api key tok_live_9f8a7b6c5d4e" }] }, + }; + await after(event); + + const result = event.result as { content: Array<{ text: string }> }; + assert.equal(result.content[0]?.text, "./logs/app.log: api key [envsitter: API_TOKEN redacted]"); +}); + +test("V2 permission evaluate denies protected read, edit, and shell resources only", async () => { + const { evaluate } = await setupV2(await createProject({})); + + const deniedRead = { sessionID: "ses_1", action: "read", resources: ["/repo/.env"], effect: "allow" as string, message: undefined as string | undefined }; + await evaluate(deniedRead); + assert.equal(deniedRead.effect, "deny"); + assert.match(deniedRead.message ?? "", /Reading `\.env\*` is blocked/); + + const deniedShell = { sessionID: "ses_1", action: "shell", resources: ["cat .env.local"], effect: "ask" as string }; + await evaluate(deniedShell); + assert.equal(deniedShell.effect, "deny"); + + const allowed = { sessionID: "ses_1", action: "edit", resources: ["/repo/src/app.ts", "/repo/.env.example"], effect: "allow" as string }; + await evaluate(allowed); + assert.equal(allowed.effect, "allow"); +}); diff --git a/test/policy.test.ts b/test/policy.test.ts new file mode 100644 index 0000000..79d5de8 --- /dev/null +++ b/test/policy.test.ts @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { extractPatchPaths, findBlockedAccess, findProtectedPathsInCommand, redactGrepOutput } from "../lib/policy.js"; + +test("shell scan flags commands that name protected files", () => { + const blocked = [ + "cat .env", + "source .env", + ". .env.local", + "echo TOKEN=x >> .env", + "cat<.env", + "node --env-file=.env app.js", + 'echo "$(cat .env)"', + "echo `cat api.env`", + "cat .env*", + "cat .envsitter/pepper", + "ls && cat config/app.env", + "envsitter keys --file .env; cat .env", + ]; + + for (const command of blocked) { + assert.ok(findProtectedPathsInCommand(command).length > 0, command); + } +}); + +test("shell scan allows envsitter CLI calls, example files, and prose mentions", () => { + const allowed = [ + "npx envsitter keys --file .env", + "npx -y envsitter@latest match --file .env --key A --op exists", + "envsitter set --file .env.local --key A --value-stdin --write", + "pnpm dlx envsitter validate --file .env", + 'git commit -m "document .env usage"', + "cat .env.example", + "cp .env.example .env.sample", + "ls -la", + "npm test", + ]; + + for (const command of allowed) { + assert.deepEqual(findProtectedPathsInCommand(command), [], command); + } +}); + +test("patch path extraction covers patch envelopes and unified diffs", () => { + const envelope = ["*** Begin Patch", "*** Update File: src/a.ts", "*** Move to: src/b.ts", "*** Add File: .env", "*** Delete File: old.env", "*** End Patch"].join("\n"); + assert.deepEqual(extractPatchPaths(envelope), ["src/a.ts", "src/b.ts", ".env", "old.env"]); + + const unified = ["--- a/.env.local", "+++ b/.env.local", "@@ -1 +1 @@", "-A=1", "+A=2"].join("\n"); + assert.deepEqual(extractPatchPaths(unified), [".env.local", ".env.local"]); + assert.deepEqual(extractPatchPaths("--- /dev/null\n+++ b/src/new.ts"), ["src/new.ts"]); +}); + +test("findBlockedAccess reads path arguments from V1 and V2 tool shapes", () => { + assert.equal(findBlockedAccess("read", { filePath: ".env" })?.kind, "read"); + assert.equal(findBlockedAccess("read", { path: ".env" })?.kind, "read"); + assert.equal(findBlockedAccess("multi_read", { paths: ["a.ts", ".env.production"] })?.kind, "access"); + assert.equal(findBlockedAccess("envsitter_keys", { filePath: ".env" }), undefined); + assert.equal(findBlockedAccess("read", "not-an-object"), undefined); +}); + +test("grep redaction is a no-op when nothing is protected", () => { + const output = "Found 1 matches\n/repo/src/a.ts:\n Line 1: TOKEN"; + assert.deepEqual(redactGrepOutput(output), { output, hidden: 0 }); +}); diff --git a/test/scrub.test.ts b/test/scrub.test.ts new file mode 100644 index 0000000..7924568 --- /dev/null +++ b/test/scrub.test.ts @@ -0,0 +1,72 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import { createScrubLoader, findSensitiveEnvFiles, scrubToolResult } from "../lib/scrub.js"; + +const TOKEN = "tok_live_9f8a7b6c5d4e"; +const DB_PASSWORD = "s3cretPassw0rd"; + +async function createProject(files: Record): Promise { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "envsitter-guard-scrub-")); + for (const [name, contents] of Object.entries(files)) { + await fs.mkdir(path.dirname(path.join(dir, name)), { recursive: true }); + await fs.writeFile(path.join(dir, name), contents); + } + return dir; +} + +test("finds sensitive env files in the project, skipping examples and dependency folders", async () => { + const root = await createProject({ + ".env": `API_TOKEN=${TOKEN}\n`, + ".env.example": "API_TOKEN=replace-me\n", + "apps/web/.env.local": `DATABASE_URL=postgres://app:${DB_PASSWORD}@db:5432/app\n`, + "node_modules/pkg/.env": "X=1\n", + "a/b/c/d/.env": "TOO_DEEP=1\n", + }); + + const files = (await findSensitiveEnvFiles(root)).map((file) => path.relative(root, file)); + assert.deepEqual(files, [".env", path.join("apps", "web", ".env.local")]); +}); + +test("scrub replaces secret values from every env file and leaves other text alone", async () => { + const root = await createProject({ + ".env": `API_TOKEN=${TOKEN}\nNODE_ENV=development\n`, + "apps/web/.env.local": `DATABASE_URL=postgres://app:${DB_PASSWORD}@db:5432/app\n`, + }); + + const scrub = await createScrubLoader(root)(); + const output = scrub(`Bearer ${TOKEN}\npassword=${DB_PASSWORD}\nNODE_ENV=development`); + assert.equal(output, "Bearer [envsitter: API_TOKEN redacted]\npassword=[envsitter: DATABASE_URL redacted]\nNODE_ENV=development"); +}); + +test("scrub picks up env file changes", async () => { + const root = await createProject({ ".env": `API_TOKEN=${TOKEN}\n` }); + const loadScrub = createScrubLoader(root); + assert.ok(!(await loadScrub())(TOKEN).includes(TOKEN)); + + const rotated = "tok_live_0a1b2c3d4e5f6a7b8c"; + await fs.writeFile(path.join(root, ".env"), `API_TOKEN=${rotated}\n`); + const scrub = await loadScrub(); + assert.equal(scrub(rotated), "[envsitter: API_TOKEN redacted]"); + assert.equal(scrub(TOKEN), TOKEN); +}); + +test("scrubToolResult scrubs text content and structured output, and keeps untouched results identical", () => { + const scrub = (text: string) => text.replaceAll(TOKEN, "[x]"); + + const result = { + content: [{ type: "text", text: `token ${TOKEN}` }, { type: "image", data: TOKEN }], + output: { stdout: `ok ${TOKEN}`, exitCode: 0, lines: [TOKEN, "fine"] }, + metadata: { title: "t" }, + }; + const scrubbed = scrubToolResult(result, scrub); + assert.deepEqual(scrubbed.content, [{ type: "text", text: "token [x]" }, { type: "image", data: TOKEN }]); + assert.deepEqual(scrubbed.output, { stdout: "ok [x]", exitCode: 0, lines: ["[x]", "fine"] }); + assert.equal(scrubbed.metadata, result.metadata); + + const clean = { content: "nothing secret", output: { exitCode: 0 } }; + assert.equal(scrubToolResult(clean, scrub), clean); +}); diff --git a/tsconfig.json b/tsconfig.json index 1ab9f4a..6a720f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,5 +8,5 @@ "types": ["node"], "skipLibCheck": true }, - "include": ["index.ts", ".opencode/plugin/**/*.ts"] + "include": ["index.ts", ".opencode/plugin/**/*.ts", "test/**/*.ts"] }