diff --git a/CHANGELOG.md b/CHANGELOG.md index 105a7c2..31fbb08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to Gondolin are documented here. ## Unreleased +- Add `pi install npm:@earendil-works/gondolin` support: the package now ships a pi extension (`extensions/gondolin.ts`) via `pi.extensions`, configurable through JSON files (`~/.pi/agent/extensions/gondolin.json` global, `/.pi/gondolin.json` project). Config exposes allowedHosts, secrets, DNS, SSH egress, mapped TCP, WebSocket toggle, and VM resources; with no config the VM uses Gondolin defaults. + ## 0.12.0 - Add `VM.getHostPid()` to allow callers to collect host-side process metrics of the VM runner. #114 diff --git a/host/extensions/config.ts b/host/extensions/config.ts new file mode 100644 index 0000000..952c690 --- /dev/null +++ b/host/extensions/config.ts @@ -0,0 +1,189 @@ +import { existsSync, readFileSync } from "node:fs"; + +export interface DnsConfig { + mode?: "synthetic" | "trusted" | "open"; + trustedServers?: string[]; +} + +export interface SshConfig { + allowedHosts?: string[]; + agent?: boolean | string; + knownHostsFile?: string | string[]; +} + +export interface TcpConfig { + hosts?: Record; +} + +export interface GondolinConfig { + allowedHosts?: string[]; + allowedInternalHosts?: string[]; + secrets?: Record; + blockInternalRanges?: boolean; + replaceSecretsInQuery?: boolean; + allowWebSockets?: boolean; + memory?: string; + cpus?: number; + dns?: DnsConfig; + ssh?: SshConfig; + tcp?: TcpConfig; +} + +export interface LoadedConfig { + allowedHosts: string[]; + allowedInternalHosts: string[]; + secrets: Record; + blockInternalRanges?: boolean; + replaceSecretsInQuery?: boolean; + allowWebSockets?: boolean; + memory?: string; + cpus?: number; + dns?: DnsConfig; + ssh?: { + allowedHosts: string[]; + agent?: string; + knownHostsFile?: string | string[]; + }; + tcp?: TcpConfig; +} + +export function tryLoadConfig(configPath: string): GondolinConfig { + if (!existsSync(configPath)) return {}; + try { + const parsed = JSON.parse(readFileSync(configPath, "utf-8")); + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + console.warn(`[gondolin] ignoring config ${configPath}: expected a JSON object`); + return {}; + } + return parsed as GondolinConfig; + } catch (err) { + console.warn(`[gondolin] ignoring malformed config ${configPath}: ${err}`); + return {}; + } +} + +function filterStrings(arr: unknown[] | undefined): string[] { + return (arr ?? []).filter((h): h is string => typeof h === "string"); +} + +function mergeStringArrays(a: unknown[] | undefined, b: unknown[] | undefined): string[] { + return filterStrings([...(a ?? []), ...(b ?? [])]); +} + +function lastDefined(...values: (T | undefined)[]): T | undefined { + for (let i = values.length - 1; i >= 0; i--) { + if (values[i] !== undefined) return values[i]; + } + return undefined; +} + +function validateSecrets( + raw: Record, +): Record { + const secrets: Record = {}; + for (const [name, entry] of Object.entries(raw)) { + if ( + typeof entry === "object" && + entry !== null && + "hosts" in entry && + Array.isArray((entry as { hosts: unknown }).hosts) && + (entry as { hosts: unknown[] }).hosts.every( + (h) => typeof h === "string", + ) + ) { + secrets[name] = entry as { hosts: string[] }; + } else { + console.warn( + `[gondolin] ignoring malformed secret "${name}" in config (expected { hosts: string[] })`, + ); + } + } + return secrets; +} + +function resolveSshAgent(value: boolean | string | undefined): string | undefined { + if (value === true) return process.env.SSH_AUTH_SOCK; + if (typeof value === "string") return value; + return undefined; +} + +export function mergeConfigs( + global: GondolinConfig, + project: GondolinConfig, +): LoadedConfig { + const allowedHosts = mergeStringArrays(global.allowedHosts, project.allowedHosts); + const allowedInternalHosts = mergeStringArrays( + global.allowedInternalHosts, + project.allowedInternalHosts, + ); + + const mergedSecrets = { ...(global.secrets ?? {}), ...(project.secrets ?? {}) }; + if (global.secrets && project.secrets) { + for (const name of Object.keys(project.secrets)) { + if (name in global.secrets) { + console.warn( + `[gondolin] project config overrides global secret "${name}"`, + ); + } + } + } + const secrets = validateSecrets(mergedSecrets as Record); + + const result: LoadedConfig = { allowedHosts, allowedInternalHosts, secrets }; + + const blockInternalRanges = lastDefined(global.blockInternalRanges, project.blockInternalRanges); + if (blockInternalRanges !== undefined) result.blockInternalRanges = blockInternalRanges; + + const replaceSecretsInQuery = lastDefined( + global.replaceSecretsInQuery, + project.replaceSecretsInQuery, + ); + if (replaceSecretsInQuery !== undefined) result.replaceSecretsInQuery = replaceSecretsInQuery; + + const allowWebSockets = lastDefined(global.allowWebSockets, project.allowWebSockets); + if (allowWebSockets !== undefined) result.allowWebSockets = allowWebSockets; + + const memory = lastDefined(global.memory, project.memory); + if (memory !== undefined) result.memory = memory; + + const cpus = lastDefined(global.cpus, project.cpus); + if (cpus !== undefined) result.cpus = cpus; + + // DNS: project fields override global fields + if (global.dns || project.dns) { + result.dns = { + mode: lastDefined(global.dns?.mode, project.dns?.mode), + trustedServers: lastDefined(global.dns?.trustedServers, project.dns?.trustedServers), + }; + } + + // SSH: allowedHosts merge, scalars project-overrides-global + const sshHosts = mergeStringArrays(global.ssh?.allowedHosts, project.ssh?.allowedHosts); + if (sshHosts.length > 0 || global.ssh || project.ssh) { + const agent = resolveSshAgent(lastDefined(global.ssh?.agent, project.ssh?.agent)); + const knownHostsFile = lastDefined(global.ssh?.knownHostsFile, project.ssh?.knownHostsFile); + result.ssh = { + allowedHosts: sshHosts, + ...(agent !== undefined ? { agent } : {}), + ...(knownHostsFile !== undefined ? { knownHostsFile } : {}), + }; + } + + // TCP hosts: project overrides global per-key + const globalTcpHosts = global.tcp?.hosts ?? {}; + const projectTcpHosts = project.tcp?.hosts ?? {}; + if (Object.keys(globalTcpHosts).length > 0 || Object.keys(projectTcpHosts).length > 0) { + result.tcp = { hosts: { ...globalTcpHosts, ...projectTcpHosts } }; + } + + return result; +} + +export function loadConfig( + globalConfigPath: string, + projectConfigPath: string, +): LoadedConfig { + const global = tryLoadConfig(globalConfigPath); + const project = tryLoadConfig(projectConfigPath); + return mergeConfigs(global, project); +} diff --git a/host/extensions/gondolin.ts b/host/extensions/gondolin.ts new file mode 100644 index 0000000..809226f --- /dev/null +++ b/host/extensions/gondolin.ts @@ -0,0 +1,430 @@ +/** + * Pi + Gondolin Sandbox Extension + * + * Overrides pi's built-in `read`/`write`/`edit`/`bash` tools so they execute + * inside a Gondolin micro-VM instead of on the host. The directory you start + * `pi` in is mounted read-write at `/workspace` inside the VM. + * + * Installation (recommended): + * pi install npm:@earendil-works/gondolin + * + * Or load directly from a local checkout: + * pi -e /path/to/gondolin/host/extensions/gondolin.ts + * + * Configuration (optional, update-safe): + * ~/.pi/agent/extensions/gondolin.json — global defaults + * /.pi/gondolin.json — project overrides (merged with global) + * + * Both files share the same schema; array fields (allowedHosts, ssh.allowedHosts) + * are merged, scalar fields use project-overrides-global. Example: + * + * { + * "allowedHosts": ["api.anthropic.com"], + * "allowedInternalHosts": ["litellm.local"], + * "secrets": { + * "ANTHROPIC_API_KEY": { "hosts": ["api.anthropic.com"] } + * }, + * "blockInternalRanges": true, + * "replaceSecretsInQuery": false, + * "allowWebSockets": true, + * "memory": "2G", + * "cpus": 4, + * "dns": { "mode": "synthetic", "trustedServers": ["1.1.1.1"] }, + * "ssh": { + * "allowedHosts": ["github.com"], + * "agent": true, + * "knownHostsFile": "~/.ssh/known_hosts" + * }, + * "tcp": { "hosts": { "db.local": "127.0.0.1:5432" } } + * } + * + * Secret values are always read from the matching environment variable at + * startup — never store secret values in the config file. ssh.agent: true + * reads $SSH_AUTH_SOCK; a string value is used as a literal socket path. + * + * SSH and TCP egress automatically enable synthetic DNS with per-host mapping. + * + * With no config file the VM uses Gondolin's default: all outbound HTTP/TLS + * is allowed (no allowlist, no secret injection, no internal-range blocking). + * + * Notes: + * - The VM is started on `session_start` (and lazily if a tool is used before that) + * - User `!` commands are also executed inside the VM + * - Requires QEMU (see gondolin README "Quick Start") + */ + +import path from "node:path"; + +import type { + ExtensionAPI, + ExtensionContext, +} from "@earendil-works/pi-coding-agent"; +import { + type BashOperations, + createBashTool, + createEditTool, + createReadTool, + createWriteTool, + type EditOperations, + getAgentDir, + type ReadOperations, + type WriteOperations, +} from "@earendil-works/pi-coding-agent"; + +import { RealFSProvider, VM, createHttpHooks } from "@earendil-works/gondolin"; + +import { loadConfig } from "./config.ts"; + +const GUEST_WORKSPACE = "/workspace"; + +function shQuote(value: string): string { + return "'" + value.replace(/'/g, "'\\''") + "'"; +} + +function toGuestPath(localCwd: string, localPath: string): string { + const rel = path.relative(localCwd, localPath); + if (rel === "") return GUEST_WORKSPACE; + if (rel.startsWith("..") || path.isAbsolute(rel)) { + throw new Error(`path escapes workspace: ${localPath}`); + } + const posixRel = rel.split(path.sep).join(path.posix.sep); + return path.posix.join(GUEST_WORKSPACE, posixRel); +} + +function createGondolinReadOps(vm: VM, localCwd: string): ReadOperations { + return { + readFile: async (p) => { + const guestPath = toGuestPath(localCwd, p); + const r = await vm.exec(["/bin/cat", guestPath]); + if (!r.ok) { + throw new Error(`cat failed (${r.exitCode}): ${r.stderr}`); + } + return r.stdoutBuffer; + }, + access: async (p) => { + const guestPath = toGuestPath(localCwd, p); + const r = await vm.exec([ + "/bin/sh", + "-lc", + `test -r ${shQuote(guestPath)}`, + ]); + if (!r.ok) { + throw new Error(`not readable: ${p}`); + } + }, + detectImageMimeType: async (p) => { + const guestPath = toGuestPath(localCwd, p); + try { + const r = await vm.exec([ + "/bin/sh", + "-lc", + `file --mime-type -b ${shQuote(guestPath)}`, + ]); + if (!r.ok) return null; + const m = r.stdout.trim(); + return ["image/jpeg", "image/png", "image/gif", "image/webp"].includes( + m, + ) + ? m + : null; + } catch { + return null; + } + }, + }; +} + +function createGondolinWriteOps(vm: VM, localCwd: string): WriteOperations { + return { + writeFile: async (p, content) => { + const guestPath = toGuestPath(localCwd, p); + const dir = path.posix.dirname(guestPath); + + const b64 = Buffer.from(content, "utf8").toString("base64"); + const script = [ + `set -eu`, + `mkdir -p ${shQuote(dir)}`, + `echo ${shQuote(b64)} | base64 -d > ${shQuote(guestPath)}`, + ].join("\n"); + + const r = await vm.exec(["/bin/sh", "-lc", script]); + if (!r.ok) { + throw new Error(`write failed (${r.exitCode}): ${r.stderr}`); + } + }, + mkdir: async (dir) => { + const guestDir = toGuestPath(localCwd, dir); + const r = await vm.exec(["/bin/mkdir", "-p", guestDir]); + if (!r.ok) { + throw new Error(`mkdir failed (${r.exitCode}): ${r.stderr}`); + } + }, + }; +} + +function createGondolinEditOps(vm: VM, localCwd: string): EditOperations { + const r = createGondolinReadOps(vm, localCwd); + const w = createGondolinWriteOps(vm, localCwd); + return { readFile: r.readFile, access: r.access, writeFile: w.writeFile }; +} + +function sanitizeEnv( + env?: NodeJS.ProcessEnv, +): Record | undefined { + if (!env) return undefined; + const out: Record = {}; + for (const [k, v] of Object.entries(env)) { + if (typeof v === "string") out[k] = v; + } + return out; +} + +function createGondolinBashOps(vm: VM, localCwd: string): BashOperations { + return { + exec: async (command, cwd, { onData, signal, timeout, env }) => { + const guestCwd = toGuestPath(localCwd, cwd); + + const ac = new AbortController(); + const onAbort = () => ac.abort(); + signal?.addEventListener("abort", onAbort, { once: true }); + + let timedOut = false; + const timer = + timeout && timeout > 0 + ? setTimeout(() => { + timedOut = true; + ac.abort(); + }, timeout * 1000) + : undefined; + + try { + const proc = vm.exec(["/bin/bash", "-lc", command], { + cwd: guestCwd, + signal: ac.signal, + env: sanitizeEnv(env), + stdout: "pipe", + stderr: "pipe", + }); + + for await (const chunk of proc.output()) { + onData(chunk.data); + } + + const r = await proc; + return { exitCode: r.exitCode }; + } catch (err) { + if (signal?.aborted) throw new Error("aborted"); + if (timedOut) throw new Error(`timeout:${timeout}`); + throw err; + } finally { + if (timer) clearTimeout(timer); + signal?.removeEventListener("abort", onAbort); + } + }, + }; +} + +export default function (pi: ExtensionAPI) { + const localCwd = process.cwd(); + + const localRead = createReadTool(localCwd); + const localWrite = createWriteTool(localCwd); + const localEdit = createEditTool(localCwd); + const localBash = createBashTool(localCwd); + + let vm: VM | null = null; + let vmStarting: Promise | null = null; + let closed = false; + + async function ensureVm(ctx?: ExtensionContext) { + if (closed) throw new Error("Gondolin VM is shutting down"); + if (vm) return vm; + if (vmStarting) return vmStarting; + + vmStarting = (async () => { + ctx?.ui.setStatus( + "gondolin", + ctx.ui.theme.fg( + "accent", + `Gondolin: starting (mount ${GUEST_WORKSPACE})`, + ), + ); + + const config = loadConfig( + path.join(getAgentDir(), "extensions", "gondolin.json"), + path.join(localCwd, ".pi", "gondolin.json"), + ); + const secretEntries = Object.entries(config.secrets).filter( + ([name]) => { + if (process.env[name] !== undefined) return true; + console.warn( + `[gondolin] secret "${name}" configured but $${name} is not set — skipping`, + ); + return false; + }, + ); + const needHooks = + config.allowedHosts.length > 0 || + config.allowedInternalHosts.length > 0 || + secretEntries.length > 0 || + config.blockInternalRanges !== undefined || + config.replaceSecretsInQuery !== undefined; + const hooksResult = needHooks + ? createHttpHooks({ + ...(config.allowedHosts.length > 0 + ? { allowedHosts: config.allowedHosts } + : {}), + ...(config.allowedInternalHosts.length > 0 + ? { allowedInternalHosts: config.allowedInternalHosts } + : {}), + ...(config.blockInternalRanges !== undefined + ? { blockInternalRanges: config.blockInternalRanges } + : {}), + ...(config.replaceSecretsInQuery !== undefined + ? { replaceSecretsInQuery: config.replaceSecretsInQuery } + : {}), + secrets: Object.fromEntries( + secretEntries.map(([name, { hosts }]) => [ + name, + { hosts, value: process.env[name] ?? "" }, + ]), + ), + }) + : null; + + const needsSyntheticDns = config.ssh || config.tcp; + + const created = await VM.create({ + ...(hooksResult + ? { httpHooks: hooksResult.httpHooks, env: hooksResult.env } + : {}), + ...(config.allowWebSockets !== undefined + ? { allowWebSockets: config.allowWebSockets } + : {}), + ...(config.memory ? { memory: config.memory } : {}), + ...(config.cpus ? { cpus: config.cpus } : {}), + ...(config.dns || needsSyntheticDns + ? { + dns: { + ...config.dns, + ...(needsSyntheticDns && (!config.dns?.mode || config.dns.mode === "synthetic") + ? { mode: "synthetic" as const, syntheticHostMapping: "per-host" as const } + : {}), + }, + } + : {}), + ...(config.ssh + ? { + ssh: { + allowedHosts: config.ssh.allowedHosts, + ...(config.ssh.agent ? { agent: config.ssh.agent } : {}), + ...(config.ssh.knownHostsFile + ? { knownHostsFile: config.ssh.knownHostsFile } + : {}), + }, + } + : {}), + ...(config.tcp ? { tcp: config.tcp } : {}), + vfs: { + mounts: { + [GUEST_WORKSPACE]: new RealFSProvider(localCwd), + }, + }, + }); + + vm = created; + ctx?.ui.setStatus( + "gondolin", + ctx.ui.theme.fg( + "accent", + `Gondolin: running (${localCwd} -> ${GUEST_WORKSPACE})`, + ), + ); + ctx?.ui.notify( + `Gondolin VM ready. Host ${localCwd} mounted at ${GUEST_WORKSPACE}`, + "info", + ); + return created; + })(); + + return vmStarting; + } + + pi.on("session_start", async (_event, ctx) => { + await ensureVm(ctx); + }); + + pi.on("session_shutdown", async (_event, ctx) => { + closed = true; + if (!vm) return; + ctx.ui.setStatus( + "gondolin", + ctx.ui.theme.fg("muted", "Gondolin: stopping"), + ); + try { + await vm.close(); + } finally { + vm = null; + vmStarting = null; + } + }); + + pi.registerTool({ + ...localRead, + async execute(id, params, signal, onUpdate, ctx) { + const activeVm = await ensureVm(ctx); + const tool = createReadTool(localCwd, { + operations: createGondolinReadOps(activeVm, localCwd), + }); + return tool.execute(id, params, signal, onUpdate); + }, + }); + + pi.registerTool({ + ...localWrite, + async execute(id, params, signal, onUpdate, ctx) { + const activeVm = await ensureVm(ctx); + const tool = createWriteTool(localCwd, { + operations: createGondolinWriteOps(activeVm, localCwd), + }); + return tool.execute(id, params, signal, onUpdate); + }, + }); + + pi.registerTool({ + ...localEdit, + async execute(id, params, signal, onUpdate, ctx) { + const activeVm = await ensureVm(ctx); + const tool = createEditTool(localCwd, { + operations: createGondolinEditOps(activeVm, localCwd), + }); + return tool.execute(id, params, signal, onUpdate); + }, + }); + + pi.registerTool({ + ...localBash, + async execute(id, params, signal, onUpdate, ctx) { + const activeVm = await ensureVm(ctx); + const tool = createBashTool(localCwd, { + operations: createGondolinBashOps(activeVm, localCwd), + }); + return tool.execute(id, params, signal, onUpdate); + }, + }); + + pi.on("user_bash", async (_event, ctx) => { + if (closed) return; + const activeVm = await ensureVm(ctx); + return { operations: createGondolinBashOps(activeVm, localCwd) }; + }); + + pi.on("before_agent_start", async (event, ctx) => { + await ensureVm(ctx); + const modified = event.systemPrompt.replace( + `Current working directory: ${localCwd}`, + `Current working directory: ${GUEST_WORKSPACE} (Gondolin VM, mounted from host: ${localCwd})`, + ); + return { systemPrompt: modified }; + }); +} diff --git a/host/package.json b/host/package.json index 73a6b91..b2923f9 100644 --- a/host/package.json +++ b/host/package.json @@ -16,8 +16,15 @@ }, "./package.json": "./package.json" }, + "pi": { + "extensions": [ + "extensions/gondolin.ts" + ] + }, "files": [ - "dist/" + "dist/", + "examples/", + "extensions/" ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.build.json && node ./scripts/postbuild.mjs", @@ -58,6 +65,7 @@ "@earendil-works/gondolin-krun-runner-linux-x64": "0.12.0" }, "devDependencies": { + "@earendil-works/pi-coding-agent": "^0.75.5", "@types/node": "^22.10.7", "@types/node-forge": "^1.3.14", "@types/ssh2": "^1.15.5", diff --git a/host/test/extension-config.test.ts b/host/test/extension-config.test.ts new file mode 100644 index 0000000..a7d680c --- /dev/null +++ b/host/test/extension-config.test.ts @@ -0,0 +1,264 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import { + tryLoadConfig, + mergeConfigs, + loadConfig, +} from "../extensions/config.ts"; + +function makeTmpDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), "pi-config-test-")); +} + +function writeJson(dir: string, name: string, data: unknown): string { + const filePath = path.join(dir, name); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(data)); + return filePath; +} + +// --- tryLoadConfig --- + +test("tryLoadConfig returns empty for missing file", () => { + const result = tryLoadConfig("/nonexistent/path/gondolin.json"); + assert.deepEqual(result, {}); +}); + +test("tryLoadConfig loads valid config", () => { + const dir = makeTmpDir(); + const p = writeJson(dir, "gondolin.json", { + allowedHosts: ["example.net"], + secrets: { API_KEY: { hosts: ["example.net"] } }, + }); + const result = tryLoadConfig(p); + assert.deepEqual(result.allowedHosts, ["example.net"]); + assert.deepEqual(result.secrets, { API_KEY: { hosts: ["example.net"] } }); + fs.rmSync(dir, { recursive: true }); +}); + +test("tryLoadConfig returns empty for malformed JSON", () => { + const dir = makeTmpDir(); + const p = path.join(dir, "gondolin.json"); + fs.writeFileSync(p, "not valid json {{{"); + const result = tryLoadConfig(p); + assert.deepEqual(result, {}); + fs.rmSync(dir, { recursive: true }); +}); + +test("tryLoadConfig returns empty for null JSON", () => { + const dir = makeTmpDir(); + const p = path.join(dir, "gondolin.json"); + fs.writeFileSync(p, "null"); + const result = tryLoadConfig(p); + assert.deepEqual(result, {}); + fs.rmSync(dir, { recursive: true }); +}); + +// --- mergeConfigs: existing fields --- + +test("mergeConfigs with two empty configs", () => { + const result = mergeConfigs({}, {}); + assert.deepEqual(result.allowedHosts, []); + assert.deepEqual(result.allowedInternalHosts, []); + assert.deepEqual(result.secrets, {}); +}); + +test("mergeConfigs combines allowedHosts from both", () => { + const result = mergeConfigs( + { allowedHosts: ["a.example"] }, + { allowedHosts: ["b.example"] }, + ); + assert.deepEqual(result.allowedHosts, ["a.example", "b.example"]); +}); + +test("mergeConfigs project secret overrides global for same key", () => { + const result = mergeConfigs( + { secrets: { KEY: { hosts: ["global.example"] } } }, + { secrets: { KEY: { hosts: ["project.example"] } } }, + ); + assert.deepEqual(result.secrets, { KEY: { hosts: ["project.example"] } }); +}); + +test("mergeConfigs keeps distinct secrets from both", () => { + const result = mergeConfigs( + { secrets: { A: { hosts: ["a.example"] } } }, + { secrets: { B: { hosts: ["b.example"] } } }, + ); + assert.deepEqual(result.secrets, { + A: { hosts: ["a.example"] }, + B: { hosts: ["b.example"] }, + }); +}); + +test("mergeConfigs ignores malformed secret entries", () => { + const result = mergeConfigs( + { + secrets: { + GOOD: { hosts: ["a.example"] }, + BAD_STRING: "not an object" as unknown as { hosts: string[] }, + BAD_HOSTS: { hosts: [42] } as unknown as { hosts: string[] }, + }, + }, + {}, + ); + assert.deepEqual(result.secrets, { GOOD: { hosts: ["a.example"] } }); +}); + +// --- mergeConfigs: new declarative fields --- + +test("mergeConfigs combines allowedInternalHosts from both", () => { + const result = mergeConfigs( + { allowedInternalHosts: ["a.local"] }, + { allowedInternalHosts: ["b.local"] }, + ); + assert.deepEqual(result.allowedInternalHosts, ["a.local", "b.local"]); +}); + +test("mergeConfigs project scalar overrides global", () => { + const result = mergeConfigs( + { blockInternalRanges: true, memory: "1G", cpus: 2 }, + { blockInternalRanges: false, memory: "4G", cpus: 8 }, + ); + assert.equal(result.blockInternalRanges, false); + assert.equal(result.memory, "4G"); + assert.equal(result.cpus, 8); +}); + +test("mergeConfigs omits undefined scalars", () => { + const result = mergeConfigs({}, {}); + assert.equal(result.blockInternalRanges, undefined); + assert.equal(result.memory, undefined); + assert.equal(result.cpus, undefined); + assert.equal(result.allowWebSockets, undefined); + assert.equal(result.replaceSecretsInQuery, undefined); +}); + +test("mergeConfigs global scalar used when project omits", () => { + const result = mergeConfigs( + { allowWebSockets: false, replaceSecretsInQuery: true }, + {}, + ); + assert.equal(result.allowWebSockets, false); + assert.equal(result.replaceSecretsInQuery, true); +}); + +// --- mergeConfigs: DNS --- + +test("mergeConfigs dns project overrides global mode", () => { + const result = mergeConfigs( + { dns: { mode: "synthetic" } }, + { dns: { mode: "trusted", trustedServers: ["198.51.100.1"] } }, + ); + assert.equal(result.dns?.mode, "trusted"); + assert.deepEqual(result.dns?.trustedServers, ["198.51.100.1"]); +}); + +test("mergeConfigs dns omitted when neither sets it", () => { + const result = mergeConfigs({}, {}); + assert.equal(result.dns, undefined); +}); + +// --- mergeConfigs: SSH --- + +test("mergeConfigs ssh combines allowedHosts", () => { + const result = mergeConfigs( + { ssh: { allowedHosts: ["git.example"] } }, + { ssh: { allowedHosts: ["git2.example"] } }, + ); + assert.deepEqual(result.ssh?.allowedHosts, ["git.example", "git2.example"]); +}); + +test("mergeConfigs ssh agent true resolves to SSH_AUTH_SOCK", () => { + const original = process.env.SSH_AUTH_SOCK; + process.env.SSH_AUTH_SOCK = "/tmp/test-agent.sock"; + try { + const result = mergeConfigs( + { ssh: { allowedHosts: ["git.example"], agent: true } }, + {}, + ); + assert.equal(result.ssh?.agent, "/tmp/test-agent.sock"); + } finally { + if (original !== undefined) { + process.env.SSH_AUTH_SOCK = original; + } else { + delete process.env.SSH_AUTH_SOCK; + } + } +}); + +test("mergeConfigs ssh agent string used as literal path", () => { + const result = mergeConfigs( + { ssh: { allowedHosts: ["git.example"], agent: "/custom/agent.sock" } }, + {}, + ); + assert.equal(result.ssh?.agent, "/custom/agent.sock"); +}); + +test("mergeConfigs ssh omitted when neither sets it", () => { + const result = mergeConfigs({}, {}); + assert.equal(result.ssh, undefined); +}); + +// --- mergeConfigs: TCP --- + +test("mergeConfigs tcp hosts merged, project overrides per key", () => { + const result = mergeConfigs( + { tcp: { hosts: { "a.local": "127.0.0.1:5432", "b.local": "127.0.0.1:6379" } } }, + { tcp: { hosts: { "a.local": "198.51.100.2:5432" } } }, + ); + assert.deepEqual(result.tcp, { + hosts: { "a.local": "198.51.100.2:5432", "b.local": "127.0.0.1:6379" }, + }); +}); + +test("mergeConfigs tcp omitted when neither sets it", () => { + const result = mergeConfigs({}, {}); + assert.equal(result.tcp, undefined); +}); + +// --- loadConfig (end-to-end with filesystem) --- + +test("loadConfig with no config files returns defaults", () => { + const dir = makeTmpDir(); + const result = loadConfig( + path.join(dir, "global.json"), + path.join(dir, "project.json"), + ); + assert.deepEqual(result.allowedHosts, []); + assert.deepEqual(result.secrets, {}); + fs.rmSync(dir, { recursive: true }); +}); + +test("loadConfig merges global and project files", () => { + const dir = makeTmpDir(); + const g = writeJson(dir, "global.json", { + allowedHosts: ["global.example"], + memory: "1G", + ssh: { allowedHosts: ["git.example"] }, + }); + const p = writeJson(dir, "project.json", { + allowedHosts: ["project.example"], + memory: "2G", + ssh: { allowedHosts: ["git2.example"], agent: true }, + }); + const original = process.env.SSH_AUTH_SOCK; + process.env.SSH_AUTH_SOCK = "/tmp/test.sock"; + try { + const result = loadConfig(g, p); + assert.deepEqual(result.allowedHosts, ["global.example", "project.example"]); + assert.equal(result.memory, "2G"); + assert.deepEqual(result.ssh?.allowedHosts, ["git.example", "git2.example"]); + assert.equal(result.ssh?.agent, "/tmp/test.sock"); + } finally { + if (original !== undefined) { + process.env.SSH_AUTH_SOCK = original; + } else { + delete process.env.SSH_AUTH_SOCK; + } + } + fs.rmSync(dir, { recursive: true }); +}); diff --git a/host/tsconfig.build.json b/host/tsconfig.build.json index b9b1d6f..0d6e3a2 100644 --- a/host/tsconfig.build.json +++ b/host/tsconfig.build.json @@ -8,5 +8,6 @@ "sourceMap": true, "allowImportingTsExtensions": false, "rewriteRelativeImportExtensions": true - } + }, + "exclude": ["test/**/*.ts", "examples/**/*.ts", "extensions/**/*.ts"] } diff --git a/host/tsconfig.json b/host/tsconfig.json index 29dd14d..8339e0f 100644 --- a/host/tsconfig.json +++ b/host/tsconfig.json @@ -13,6 +13,6 @@ "skipLibCheck": true, "stripInternal": true }, - "include": ["src/**/*.ts", "bin/**/*.ts"], + "include": ["src/**/*.ts", "bin/**/*.ts", "examples/**/*.ts"], "exclude": ["test/**/*.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc6cc64..25f676a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: specifier: 0.12.0 version: link:../packages/gondolin-krun-runner-linux-x64 devDependencies: + '@earendil-works/pi-coding-agent': + specifier: ^0.75.5 + version: 0.75.5(zod@4.4.3) '@types/node': specifier: ^22.10.7 version: 22.19.8 @@ -60,11 +63,681 @@ importers: packages: + /@anthropic-ai/sdk@0.91.1(zod@4.4.3): + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + dependencies: + json-schema-to-ts: 3.1.1 + zod: 4.4.3 + dev: true + + /@aws-crypto/crc32@5.2.0: + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.11 + tslib: 2.8.1 + dev: true + + /@aws-crypto/sha256-browser@5.2.0: + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + 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.973.11 + '@aws-sdk/util-locate-window': 3.965.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + dev: true + + /@aws-crypto/sha256-js@5.2.0: + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.11 + tslib: 2.8.1 + dev: true + + /@aws-crypto/supports-web-crypto@5.2.0: + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + dependencies: + tslib: 2.8.1 + dev: true + + /@aws-crypto/util@5.2.0: + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + dev: true + + /@aws-sdk/client-bedrock-runtime@3.1048.0: + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.18 + '@aws-sdk/credential-provider-node': 3.972.52 + '@aws-sdk/eventstream-handler-node': 3.972.20 + '@aws-sdk/middleware-eventstream': 3.972.16 + '@aws-sdk/middleware-websocket': 3.972.26 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/core@3.974.18: + resolution: {integrity: sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/types': 3.973.11 + '@aws-sdk/xml-builder': 3.972.28 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.6 + '@smithy/signature-v4': 5.4.6 + '@smithy/types': 4.14.3 + bowser: 2.14.1 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-env@3.972.44: + resolution: {integrity: sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-http@3.972.46: + resolution: {integrity: sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.7 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-ini@3.972.50: + resolution: {integrity: sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/credential-provider-env': 3.972.44 + '@aws-sdk/credential-provider-http': 3.972.46 + '@aws-sdk/credential-provider-login': 3.972.49 + '@aws-sdk/credential-provider-process': 3.972.44 + '@aws-sdk/credential-provider-sso': 3.972.49 + '@aws-sdk/credential-provider-web-identity': 3.972.49 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/credential-provider-imds': 4.3.8 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-login@3.972.49: + resolution: {integrity: sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-node@3.972.52: + resolution: {integrity: sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.972.44 + '@aws-sdk/credential-provider-http': 3.972.46 + '@aws-sdk/credential-provider-ini': 3.972.50 + '@aws-sdk/credential-provider-process': 3.972.44 + '@aws-sdk/credential-provider-sso': 3.972.49 + '@aws-sdk/credential-provider-web-identity': 3.972.49 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/credential-provider-imds': 4.3.8 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-process@3.972.44: + resolution: {integrity: sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-sso@3.972.49: + resolution: {integrity: sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/token-providers': 3.1063.0 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/credential-provider-web-identity@3.972.49: + resolution: {integrity: sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/eventstream-handler-node@3.972.20: + resolution: {integrity: sha512-qr/S1iFCDIXlZwlZPaCqjKcHbJFr9scIFUhbh2+SrwPXZvRhyOUWjVDJpp8xoU4qrrMR0PqK1Yw5C2sSj7xAyw==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/middleware-eventstream@3.972.16: + resolution: {integrity: sha512-KR2Gdui/QLbkdG9FxW3vk/vIa8KiDP5vQBNERo7MmlPHjn23GXJ53Cq5P/ok7/ALbTUiYZ78DiBHoDcvzPWvgQ==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/middleware-websocket@3.972.26: + resolution: {integrity: sha512-foM3KvxGBHY9lRIm6C9JJJ5haodtXfJPPgJQcv5/c4A2pN4I7tlnOjh1o2d8Il1Y/j6GWOw3YeIYc2/VYjtGVQ==} + engines: {node: '>= 14.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/signature-v4': 5.4.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/nested-clients@3.997.17: + resolution: {integrity: sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.18 + '@aws-sdk/signature-v4-multi-region': 3.996.32 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.7 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/signature-v4-multi-region@3.996.32: + resolution: {integrity: sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/signature-v4': 5.4.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/token-providers@3.1048.0: + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/token-providers@3.1063.0: + resolution: {integrity: sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ==} + engines: {node: '>=20.0.0'} + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/types@3.973.11: + resolution: {integrity: sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg==} + engines: {node: '>=20.0.0'} + dependencies: + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@aws-sdk/util-locate-window@3.965.6: + resolution: {integrity: sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw==} + engines: {node: '>=20.0.0'} + dependencies: + tslib: 2.8.1 + dev: true + + /@aws-sdk/xml-builder@3.972.28: + resolution: {integrity: sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg==} + engines: {node: '>=20.0.0'} + dependencies: + '@smithy/types': 4.14.3 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + dev: true + + /@aws/lambda-invoke-store@0.2.4: + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + dev: true + + /@babel/runtime@7.29.7: + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + dev: true + /@cto.af/wtf8@0.0.5: resolution: {integrity: sha512-LfUFi+Vv4eDzj+XAtR89e3wwjXA/NZjUSwU5NhwbBrLecxPaBYFy3exCuc1j+D4UZeOVdqlsl8G7LmOt18V0tg==} engines: {node: '>=20'} dev: false + /@earendil-works/pi-agent-core@0.75.5(zod@4.4.3): + resolution: {integrity: sha512-LHygOgsW2pgXKb3IkXkOAeZPovHr9VF+EixgXVsDNuB4jmhEOXgshy/zksZ7slkUAx10OQ9W1Ed/2jsnhd1NqA==} + engines: {node: '>=22.19.0'} + dependencies: + '@earendil-works/pi-ai': 0.75.5(zod@4.4.3) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + dev: true + + /@earendil-works/pi-ai@0.75.5(zod@4.4.3): + resolution: {integrity: sha512-zf1F5kXk1pqZeFShXOqq9ibUk8QdtRoLCDPAjO+hj44e3EUs9/GFO2qnhTC5+JA2uwVCx+WCNe1PiCjlBYWm5w==} + engines: {node: '>=22.19.0'} + hasBin: true + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0 + '@mistralai/mistralai': 2.2.1 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(zod@4.4.3) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + dev: true + + /@earendil-works/pi-coding-agent@0.75.5(zod@4.4.3): + resolution: {integrity: sha512-O3CCQDYy28D4uwtP6zZkdEwzHN6X22v49Sb0+SZTC7x37V/YfmogrWPiaFoWeoc2hmdKhSATI7ZAK5bQbJG5NA==} + engines: {node: '>=22.19.0'} + hasBin: true + dependencies: + '@earendil-works/pi-agent-core': 0.75.5(zod@4.4.3) + '@earendil-works/pi-ai': 0.75.5(zod@4.4.3) + '@earendil-works/pi-tui': 0.75.5 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cross-spawn: 7.0.6 + diff: 8.0.4 + glob: 13.0.6 + highlight.js: 10.7.3 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + typebox: 1.1.38 + undici: 8.3.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.6 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + dev: true + + /@earendil-works/pi-tui@0.75.5: + resolution: {integrity: sha512-LkXUM1/49pvzzeI39Y5wjBMlgafcCf67HCLhB9Z7yuXHy4XgT+VqxWcZVW5hBdhQsHZd0znjJotfGH1BzxMfiA==} + engines: {node: '>=22.19.0'} + dependencies: + get-east-asian-width: 1.6.0 + marked: 15.0.12 + dev: true + + /@google/genai@1.52.0: + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + engines: {node: '>=20.0.0'} + requiresBuild: true + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + dependencies: + google-auth-library: 10.7.0 + p-retry: 4.6.2 + protobufjs: 7.6.2 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@mariozechner/clipboard-darwin-arm64@0.3.6: + resolution: {integrity: sha512-HjaisYCAbHi/1+N1yDAQHc8ZXGffufIUT5NSOSVR3f3AuMDusxTtnbK8tZ7JFDkShua1oNGZoNwQHsc8MPtE0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-darwin-universal@0.3.6: + resolution: {integrity: sha512-8BWtPjOtJOJoykml3w0fx0zRrfWP31mXrJwfoA7xzNprkZw1uolCNfgmjDiVBseoKjp16EGITz7bN+61qn8dWA==} + engines: {node: '>= 10'} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-darwin-x64@0.3.6: + resolution: {integrity: sha512-p9syiZD1kU4I+1ya7f7g+zD1GiUvR8fdlRlNmgsZNWlyjtc8rlV2EjTLd/35x1LsdBq020GVvtzp0ZmPgBI09Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-linux-arm64-gnu@0.3.6: + resolution: {integrity: sha512-5JFf5rGofrm+V29HNF+wLthXphHdQpMbKDUYJ5tML6/Z5DLlLOV/9Ak4kDPtYyZ+Dzf+kAusE0VsFg4+tfP1IA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-linux-arm64-musl@0.3.6: + resolution: {integrity: sha512-JlVjxxw0GbGC0djXYWRIqyteO3J1KZ/QG3udlEFaOD5TLOM1FnmXXAPDQBqr+aBVr720ef9K00dirYnJ0LDCtw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-linux-riscv64-gnu@0.3.6: + resolution: {integrity: sha512-4t8BUi5zZ+L77otFQVnVSlaTyAX4TVk9EqQm4syMrEQp96trFEHEwwNHcNEBGzYv5+K7mxay50TthYkz47OWzQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-linux-x64-gnu@0.3.6: + resolution: {integrity: sha512-trtPwcNLW37irwQCJLtCxLw757jjJZk3TSnY/MU9bhtWtA3K9b/eLW0e4RGhUXDoFRds9opNWWaUDuFLa8dm0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-linux-x64-musl@0.3.6: + resolution: {integrity: sha512-WfnzIvOCCWQiN0MmltCEo6cLceUDbYe+I7xyFZjaps5A+2Op/M2CY7Rey+C4ucQhrvmpoHmTSFgY9ODWk7snoA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-win32-arm64-msvc@0.3.6: + resolution: {integrity: sha512-+8+1aHYsBPUjmW3otmWlg+Hijt0iJvoBBs5e0mxFeUd4gDaKMB8Bn6x7c6KVtscg7E5j5NFXnwQqNSIAO4p8zQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard-win32-x64-msvc@0.3.6: + resolution: {integrity: sha512-S4xfPmERC8ZkiLHe3vekZCjdDwNEETCuvCgQK2kP6/TnvmUkq1y2Pk+DjM4t8uh9KMX9bH4zs5ePcKa8GTXmfg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@mariozechner/clipboard@0.3.6: + resolution: {integrity: sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg==} + engines: {node: '>= 10'} + requiresBuild: true + optionalDependencies: + '@mariozechner/clipboard-darwin-arm64': 0.3.6 + '@mariozechner/clipboard-darwin-universal': 0.3.6 + '@mariozechner/clipboard-darwin-x64': 0.3.6 + '@mariozechner/clipboard-linux-arm64-gnu': 0.3.6 + '@mariozechner/clipboard-linux-arm64-musl': 0.3.6 + '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.6 + '@mariozechner/clipboard-linux-x64-gnu': 0.3.6 + '@mariozechner/clipboard-linux-x64-musl': 0.3.6 + '@mariozechner/clipboard-win32-arm64-msvc': 0.3.6 + '@mariozechner/clipboard-win32-x64-msvc': 0.3.6 + dev: true + optional: true + + /@mistralai/mistralai@2.2.1: + resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} + dependencies: + ws: 8.21.0 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@nodable/entities@2.1.1: + resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==} + dev: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: true + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: true + + /@protobufjs/codegen@2.0.5: + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + dev: true + + /@protobufjs/eventemitter@1.1.1: + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + dev: true + + /@protobufjs/fetch@1.1.1: + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + dev: true + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: true + + /@protobufjs/inquire@1.1.2: + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + dev: true + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: true + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: true + + /@protobufjs/utf8@1.1.1: + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + dev: true + + /@silvia-odwyer/photon-node@0.3.4: + resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + dev: true + + /@smithy/core@3.24.6: + resolution: {integrity: sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==} + engines: {node: '>=18.0.0'} + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/credential-provider-imds@4.3.8: + resolution: {integrity: sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==} + engines: {node: '>=18.0.0'} + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/fetch-http-handler@5.4.6: + resolution: {integrity: sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==} + engines: {node: '>=18.0.0'} + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/is-array-buffer@2.2.0: + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.8.1 + dev: true + + /@smithy/node-http-handler@4.7.3: + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/node-http-handler@4.7.7: + resolution: {integrity: sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==} + engines: {node: '>=18.0.0'} + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/signature-v4@5.4.6: + resolution: {integrity: sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==} + engines: {node: '>=18.0.0'} + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + dev: true + + /@smithy/types@4.14.3: + resolution: {integrity: sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==} + engines: {node: '>=18.0.0'} + dependencies: + tslib: 2.8.1 + dev: true + + /@smithy/util-buffer-from@2.2.0: + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + dev: true + + /@smithy/util-utf8@2.3.0: + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + dev: true + /@types/node-forge@1.3.14: resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} dependencies: @@ -83,24 +756,61 @@ packages: undici-types: 6.21.0 dev: true + /@types/retry@0.12.0: + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + dev: true + /@types/ssh2@1.15.5: resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} dependencies: '@types/node': 18.19.130 dev: true + /agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + dev: true + /asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: safer-buffer: 2.1.2 dev: false + /balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + dev: true + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + /bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: tweetnacl: 0.14.5 dev: false + /bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + dev: true + + /bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + dev: true + + /brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + dependencies: + balanced-match: 4.0.4 + dev: true + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: true + /buildcheck@0.0.7: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} @@ -115,6 +825,11 @@ packages: '@cto.af/wtf8': 0.0.5 dev: false + /chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /cpu-features@0.0.10: resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} engines: {node: '>=10.0.0'} @@ -125,27 +840,381 @@ packages: dev: false optional: true + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + + /debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + dev: true + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true + + /fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + dev: true + + /fast-xml-parser@5.7.3: + resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} + hasBin: true + dependencies: + '@nodable/entities': 2.1.1 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + dev: true + + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + dev: true + + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + + /gaxios@7.1.5: + resolution: {integrity: sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==} + engines: {node: '>=18'} + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + dev: true + + /gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + dependencies: + gaxios: 7.1.5 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + dev: true + + /glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + dev: true + + /google-auth-library@10.7.0: + resolution: {integrity: sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==} + engines: {node: '>=18'} + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.5 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: true + + /hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + dependencies: + lru-cache: 11.5.1 + dev: true + + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + dev: true + + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + dependencies: + bignumber.js: 9.3.1 + dev: true + + /json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + dev: true + + /jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: true + + /jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + dev: true + + /long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + dev: true + + /lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + dev: true + + /marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + dev: true + + /minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + dependencies: + brace-expansion: 5.0.6 + dev: true + + /minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + /nan@2.25.0: resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} requiresBuild: true dev: false optional: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + dev: true + + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-forge@1.3.3: resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} engines: {node: '>= 6.13.0'} dev: false + /openai@6.26.0(zod@4.4.3): + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + dependencies: + zod: 4.4.3 + dev: true + + /p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + dev: true + + /partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + dev: true + + /path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + dev: true + /prettier@3.8.1: resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true dev: true + /proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + dev: true + + /protobufjs@7.6.2: + resolution: {integrity: sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 22.19.8 + long: 5.3.2 + dev: true + + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: true + + /retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + /ssh2@1.17.0: resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} engines: {node: '>=10.16.0'} @@ -158,10 +1227,26 @@ packages: nan: 2.25.0 dev: false + /strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + dev: true + + /ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + dev: true + + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + dev: true + /tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} dev: false + /typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} + dev: true + /typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -180,3 +1265,57 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} dev: false + + /undici@8.3.0: + resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==} + engines: {node: '>=22.19.0'} + dev: true + + /web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + 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 + dev: true + + /xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + dev: true + + /yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + dev: true + + /zod-to-json-schema@3.25.2(zod@4.4.3): + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + dependencies: + zod: 4.4.3 + dev: true + + /zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + dev: true