From b36b81087d809014475422808d6f20d23f5567f5 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:39:41 +0000 Subject: [PATCH 01/21] feat(local): add command to run a local Spotlight sidecar Adds 'sentry local', a long-running command that starts a minimal Hono HTTP server wire-compatible with the Spotlight sidecar protocol. The server uses @spotlightjs/spotlight/sdk's createSpotlightBuffer + pushToSpotlightBuffer helpers to ingest envelopes from any Sentry SDK running in the user's dev stack and tails them to the terminal. Endpoints exposed: POST /stream - Spotlight ingest POST /api/{projectId}/envelope/ - Sentry SDK ingest path GET /stream - SSE feed for the Spotlight overlay GET /health - liveness check Why a thin in-tree server instead of spawning npx @spotlightjs/spotlight: the SDK helpers give us decompression + lazy parsing for free while keeping the surface focused on a CLI-friendly tail UX, and bundling through esbuild keeps the published binary self-contained per the no-runtime-dependencies rule. The command runs without auth (it's a local dev tool) and shuts down gracefully on SIGINT/SIGTERM, force-closing keep-alive connections so SSE subscribers don't block exit. --- bun.lock | 323 ++++++++++++--- docs/src/content/docs/contributing.md | 1 + docs/src/fragments/commands/local.md | 50 +++ package.json | 3 + plugins/sentry-cli/skills/sentry-cli/SKILL.md | 8 + .../skills/sentry-cli/references/local.md | 42 ++ src/app.ts | 2 + src/commands/local.ts | 369 ++++++++++++++++++ 8 files changed, 754 insertions(+), 44 deletions(-) create mode 100644 docs/src/fragments/commands/local.md create mode 100644 plugins/sentry-cli/skills/sentry-cli/references/local.md create mode 100644 src/commands/local.ts diff --git a/bun.lock b/bun.lock index ead0d2419..ff961af11 100644 --- a/bun.lock +++ b/bun.lock @@ -8,10 +8,12 @@ "@anthropic-ai/sdk": "^0.39.0", "@biomejs/biome": "2.3.8", "@clack/prompts": "^0.11.0", + "@hono/node-server": "^2.0.0", "@mastra/client-js": "^1.4.0", "@sentry/api": "^0.113.0", "@sentry/node-core": "10.50.0", "@sentry/sqlish": "^1.0.0", + "@spotlightjs/spotlight": "^4.11.3", "@stricli/auto-complete": "^1.2.4", "@stricli/core": "^1.2.4", "@types/bun": "latest", @@ -26,6 +28,7 @@ "consola": "^3.4.2", "esbuild": "^0.25.0", "fast-check": "^4.5.3", + "hono": "^4.12.15", "http-cache-semantics": "^4.2.0", "ignore": "^7.0.5", "marked": "^15", @@ -143,7 +146,11 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], + "@fastify/otel": ["@fastify/otel@0.18.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.28.0", "minimatch": "^10.2.4" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA=="], + + "@hono/mcp": ["@hono/mcp@0.2.5", "", { "dependencies": { "pkce-challenge": "^5.0.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.1", "hono": "*", "hono-rate-limiter": "^0.4.2", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-JsaJes7VlNvUrUQ9j2b9C13xjFLvyKQY515aWtsdJ9cwhBmWz5od2yUCbDu7cX38GeADmlLmpu4BKNNAV6G27w=="], + + "@hono/node-server": ["@hono/node-server@2.0.0", "", { "peerDependencies": { "hono": "^4" } }, "sha512-n3GfHwwCvHCkGmOwKfxUPOlbfzuO64Sbc5XC4NGPIXxkuOnJrdgExdRKmHfF924r914WRJPT397GdqLvdYTeyQ=="], "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], @@ -151,6 +158,12 @@ "@isaacs/ttlcache": ["@isaacs/ttlcache@2.1.4", "", {}, "sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@lukeed/csprng": ["@lukeed/csprng@1.1.0", "", {}, "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="], "@lukeed/uuid": ["@lukeed/uuid@2.0.1", "", { "dependencies": { "@lukeed/csprng": "^1.1.0" } }, "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w=="], @@ -165,20 +178,72 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + "@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="], + + "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.61.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q=="], + + "@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg=="], + + "@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.31.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g=="], + + "@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.33.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA=="], + + "@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.57.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow=="], + + "@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg=="], + + "@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.60.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w=="], + + "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg=="], + + "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ=="], + + "@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.23.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ=="], + + "@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.58.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw=="], + + "@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.62.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA=="], + + "@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.58.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q=="], + + "@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.67.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ=="], + + "@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.60.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg=="], + + "@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.60.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/mysql": "2.15.27" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ=="], + + "@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.60.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw=="], + + "@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.66.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", "@types/pg-pool": "2.0.7" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA=="], + + "@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ=="], + + "@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.33.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w=="], + + "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.38.3", "", {}, "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g=="], "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ=="], "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="], + "@peggyjs/from-mem": ["@peggyjs/from-mem@3.1.3", "", { "dependencies": { "semver": "7.7.4" } }, "sha512-LLlgtfXIaeYXoOYovOI0spLM8ZXaqkAlmcRRrLzHJzLMqkU6Sw0R4KMoCoHx1PjaP815pSCBlS+BN6aD8t1Jgg=="], + "@prisma/instrumentation": ["@prisma/instrumentation@7.6.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ=="], + "@sentry/api": ["@sentry/api@0.113.0", "", {}, "sha512-28W0Oykb/O+6kH8F+OEd8070N4z7ctawlyUtEvnNZNlaLviDC9Is1X/0JiK2Xb9y2ZNbkWf+/H1y5hXr0WTIOw=="], "@sentry/core": ["@sentry/core@10.50.0", "", {}, "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg=="], + "@sentry/node": ["@sentry/node@10.51.0", "", { "dependencies": { "@fastify/otel": "0.18.0", "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-amqplib": "0.61.0", "@opentelemetry/instrumentation-connect": "0.57.0", "@opentelemetry/instrumentation-dataloader": "0.31.0", "@opentelemetry/instrumentation-fs": "0.33.0", "@opentelemetry/instrumentation-generic-pool": "0.57.0", "@opentelemetry/instrumentation-graphql": "0.62.0", "@opentelemetry/instrumentation-hapi": "0.60.0", "@opentelemetry/instrumentation-http": "0.214.0", "@opentelemetry/instrumentation-ioredis": "0.62.0", "@opentelemetry/instrumentation-kafkajs": "0.23.0", "@opentelemetry/instrumentation-knex": "0.58.0", "@opentelemetry/instrumentation-koa": "0.62.0", "@opentelemetry/instrumentation-lru-memoizer": "0.58.0", "@opentelemetry/instrumentation-mongodb": "0.67.0", "@opentelemetry/instrumentation-mongoose": "0.60.0", "@opentelemetry/instrumentation-mysql": "0.60.0", "@opentelemetry/instrumentation-mysql2": "0.60.0", "@opentelemetry/instrumentation-pg": "0.66.0", "@opentelemetry/instrumentation-redis": "0.62.0", "@opentelemetry/instrumentation-tedious": "0.33.0", "@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/semantic-conventions": "^1.40.0", "@prisma/instrumentation": "7.6.0", "@sentry/core": "10.51.0", "@sentry/node-core": "10.51.0", "@sentry/opentelemetry": "10.51.0", "import-in-the-middle": "^3.0.0" } }, "sha512-2yZLRZwS1dKG8/4eOTpGSo/gO/EgmT9aPj6lAzUkRa7bZCTTdW4BraaHU0leX5T94909Qfhbr3W5AVTfDOCKiQ=="], + "@sentry/node-core": ["@sentry/node-core@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0", "@sentry/opentelemetry": "10.50.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base", "@opentelemetry/semantic-conventions"] }, "sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw=="], "@sentry/opentelemetry": ["@sentry/opentelemetry@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw=="], @@ -189,6 +254,8 @@ "@sindresorhus/transliterate": ["@sindresorhus/transliterate@1.6.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0" } }, "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ=="], + "@spotlightjs/spotlight": ["@spotlightjs/spotlight@4.11.3", "", { "dependencies": { "@hono/mcp": "^0.2.2", "@hono/node-server": "^1.19.10", "@jridgewell/trace-mapping": "^0.3.25", "@modelcontextprotocol/sdk": "^1.26.0", "@sentry/core": "^10.31.0", "@sentry/node": "^10.31.0", "anser": "^2.3.3", "chalk": "^5.6.2", "eventsource": "^4.0.0", "fast-fuzzy": "^1.12.0", "hono": "^4.12.7", "launch-editor": "^2.9.1", "logfmt": "^1.4.0", "mcp-proxy": "^5.6.0", "semver": "^7.7.3", "uuidv7": "^1.0.2", "yaml": "^2.8.1", "zod": "^4" }, "bin": { "spotlight": "dist/run.js" } }, "sha512-0vSx3r1qkScyJtilISn2+vfMCq1M9LEGhKfAv5Wgd4soWEwhtSpU8obCd/dWrNdPdpyD5OwiUuynxqxvq0cVpg=="], + "@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="], "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="], @@ -221,10 +288,16 @@ "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + "@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="], + "@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + "@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], + + "@types/pg-pool": ["@types/pg-pool@2.0.7", "", { "dependencies": { "@types/pg": "*" } }, "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng=="], + "@types/picomatch": ["@types/picomatch@4.0.3", "", {}, "sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ=="], "@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="], @@ -239,9 +312,11 @@ "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -253,6 +328,8 @@ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "anser": ["anser@2.3.5", "", {}, "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -265,12 +342,16 @@ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "binpunch": ["binpunch@1.0.0", "", { "bin": { "binpunch": "dist/cli.js" } }, "sha512-ghxdoerLN3WN64kteDJuL4d9dy7gbvcqoADNRWBk6aQ5FrYH1EmPmREAdcdIdTNAA3uW3V38Env5OqH2lj+i+g=="], "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -299,13 +380,13 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], @@ -353,11 +434,11 @@ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + "eventsource": ["eventsource@4.1.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ=="], "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], @@ -367,11 +448,13 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-fuzzy": ["fast-fuzzy@1.12.0", "", { "dependencies": { "graphemesplit": "^2.4.1" } }, "sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], @@ -381,7 +464,9 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -397,6 +482,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graphemesplit": ["graphemesplit@2.6.0", "", { "dependencies": { "js-base64": "^3.6.0", "unicode-trie": "^2.0.0" } }, "sha512-rG9w2wAfkpg0DILa1pjnjNfucng3usON360shisqIMUBw/87pojcBSrHmeE4UwryAuBih7g8m1oilf5/u8EWdQ=="], + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -409,10 +496,12 @@ "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], - "hono": ["hono@4.12.3", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="], + "hono": ["hono@4.12.15", "", {}, "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg=="], "hono-openapi": ["hono-openapi@1.3.0", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig=="], + "hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="], + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -443,6 +532,8 @@ "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], + "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], @@ -459,15 +550,21 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + "launch-editor": ["launch-editor@2.13.2", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg=="], + + "logfmt": ["logfmt@1.4.0", "", { "dependencies": { "split": "0.2.x", "through": "2.3.x" }, "bin": { "logfmt": "bin/logfmt" } }, "sha512-p1Ow0C2dDJYaQBhRHt+HVMP6ELuBm4jYSYNHPMfz0J5wJ9qA6/7oBOlBZBfT1InqguTYcvJzNea5FItDxTcbyw=="], + "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "mcp-proxy": ["mcp-proxy@5.12.5", "", { "bin": { "mcp-proxy": "dist/bin/mcp-proxy.mjs" } }, "sha512-Vawdc8vi36fXxKCaDpluRvbGcmrUXJdvXcDhkh30HYsws8XqX2rWPBflZpavzeS+6SwijRFV7g+9ypQRJZlrEQ=="], + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], @@ -489,7 +586,7 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], @@ -513,6 +610,8 @@ "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], @@ -531,12 +630,26 @@ "peggy": ["peggy@5.1.0", "", { "dependencies": { "@peggyjs/from-mem": "3.1.3", "commander": "^14.0.3", "source-map-generator": "2.0.6" }, "bin": { "peggy": "bin/peggy.js" } }, "sha512-IEo5aYRZ2kXH4Qby06cjtL114PZnwLoTiA41vUmg2vPZgANn+c87m5BUurhuDr5/cu758ZlpgsAfBVx+hhO5+w=="], + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -559,6 +672,8 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -571,9 +686,9 @@ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], @@ -581,6 +696,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -593,6 +710,8 @@ "source-map-generator": ["source-map-generator@2.0.6", "", {}, "sha512-IlassDs1Ve8nV6uyQZXF9kdkJpVKnMte2JZQXu13M0A5zwc+vu6+LNHfmxsHBMDtoZE21RHiKI0/xvpecZRCNg=="], + "split": ["split@0.2.10", "", { "dependencies": { "through": "2" } }, "sha512-e0pKq+UUH2Xq/sXbYpZBZc3BawsfDZ7dgv+JtRTUPNcvF5CMR4Y9cvJqkMY0MoxWzTHvZuz1beg6pNEKlszPiQ=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -609,6 +728,10 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], @@ -627,6 +750,8 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], @@ -649,10 +774,14 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + "yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], "yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], @@ -667,6 +796,8 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@a2a-js/sdk/express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], + "@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], "@ai-sdk/provider-utils-v6/@ai-sdk/provider": ["@ai-sdk/provider@3.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-m9ka3ptkPQbaHHZHqDXDF9C9B5/Mav0KTdky1k2HZ3/nrW2t1AgObxIVPyGDWQNS9FXT/FS6PIoSjpcP/No8rQ=="], @@ -675,29 +806,109 @@ "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], - "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + "@fastify/otel/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="], + + "@fastify/otel/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@fastify/otel/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@mastra/core/hono": ["hono@4.12.3", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="], + + "@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], + + "@modelcontextprotocol/sdk/eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "@modelcontextprotocol/sdk/hono": ["hono@4.12.3", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="], "@modelcontextprotocol/sdk/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + + "@opentelemetry/instrumentation-amqplib/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-amqplib/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-connect/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-connect/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-fs/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-hapi/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-hapi/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/instrumentation-http/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-ioredis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-kafkajs/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-knex/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-koa/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-koa/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-mongodb/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-mongoose/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-mongoose/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-mysql/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-mysql2/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-pg/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + + "@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-redis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/instrumentation-tedious/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + "@peggyjs/from-mem/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="], - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@sentry/node/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@sentry/node/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "@sentry/node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], + + "@sentry/node/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@sentry/node/@sentry/core": ["@sentry/core@10.51.0", "", {}, "sha512-Y45V/YXvVLEXmOdkbD1oG1gkRWFi9guCEGg3PlIlIpRjAbZUrvLGgjRJIc1E7XpSzmOnWbs5BbUxMv4PDaPj2w=="], + + "@sentry/node/@sentry/node-core": ["@sentry/node-core@10.51.0", "", { "dependencies": { "@sentry/core": "10.51.0", "@sentry/opentelemetry": "10.51.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base", "@opentelemetry/semantic-conventions"] }, "sha512-VP9DMEzBEuauABrfDHYz/pRYa74M09uRJLz0ls3yel3sKhYHMyCB29ZxbKcciUhD4d33dwgi8DbaPZV2H/wnfQ=="], + + "@sentry/node/@sentry/opentelemetry": ["@sentry/opentelemetry@10.51.0", "", { "dependencies": { "@sentry/core": "10.51.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-Qc7AlCE4uhB+SvHLqah4RgR1WdY7wmmr/hx9g/prDP9R1ocshmUEMrZK9qjuwaklW7/fmkFCXI8ETxo5L1bHIA=="], + + "@spotlightjs/spotlight/@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + + "@spotlightjs/spotlight/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "express/body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], + "@spotlightjs/spotlight/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], - "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], @@ -705,7 +916,7 @@ "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "trpc-cli/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], @@ -717,25 +928,47 @@ "zod-from-json-schema/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], - "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@a2a-js/sdk/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "@a2a-js/sdk/express/body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], - "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "@a2a-js/sdk/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], - "@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + "@a2a-js/sdk/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], - "@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "@a2a-js/sdk/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "@a2a-js/sdk/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], - "@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "@a2a-js/sdk/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - "@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "@a2a-js/sdk/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], - "@modelcontextprotocol/sdk/express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "@a2a-js/sdk/express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], - "@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "@a2a-js/sdk/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], - "@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "@a2a-js/sdk/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "@a2a-js/sdk/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="], + + "@fastify/otel/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="], + + "@opentelemetry/instrumentation-fs/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@opentelemetry/sql-common/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + + "@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ=="], + + "@prisma/instrumentation/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="], + + "@sentry/node/@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], + + "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -743,25 +976,27 @@ "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "express/body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + "@a2a-js/sdk/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], - "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "@a2a-js/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "@a2a-js/sdk/express/body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], - "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "@a2a-js/sdk/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@a2a-js/sdk/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], - "@modelcontextprotocol/sdk/express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } diff --git a/docs/src/content/docs/contributing.md b/docs/src/content/docs/contributing.md index c4ee61408..37e39de3c 100644 --- a/docs/src/content/docs/contributing.md +++ b/docs/src/content/docs/contributing.md @@ -70,6 +70,7 @@ cli/ │ │ ├── explore.ts # Query aggregate event data (Explore) │ │ ├── help.ts # Help command │ │ ├── init.ts # Initialize Sentry in your project (experimental) +│ │ ├── local.ts # Run a local Spotlight sidecar to capture dev SDK events │ │ └── schema.ts # Browse the Sentry API schema │ ├── lib/ # Shared utilities │ └── types/ # TypeScript types and Zod schemas diff --git a/docs/src/fragments/commands/local.md b/docs/src/fragments/commands/local.md new file mode 100644 index 000000000..ea78aac95 --- /dev/null +++ b/docs/src/fragments/commands/local.md @@ -0,0 +1,50 @@ + + +[Spotlight](https://spotlightjs.com) is "Sentry for Development" — a lightweight local proxy that ingests Sentry envelopes from SDKs running in your dev stack and surfaces them in real time. `sentry local` runs a minimal [Hono](https://hono.dev/) HTTP server that's wire-compatible with Spotlight's sidecar protocol, so your existing SDKs and the [Spotlight overlay](https://spotlightjs.com/about/) work without any changes. + +No authentication is required — the sidecar binds to `localhost` by default and is purely a development tool. + +## Examples + +```bash +# Start the sidecar on the default port (8969) +sentry local + +# Use a custom port and bind to all interfaces +sentry local --port 9000 --host 0.0.0.0 + +# Run quietly (suppress per-envelope tail output) +sentry local --quiet + +# Open the SSE endpoint in a browser on startup +sentry local --open +``` + +## Endpoints + +| Method | Path | Description | +|--------|---------------------------------|----------------------------------------------------| +| `POST` | `/stream` | Spotlight-compatible envelope ingest | +| `POST` | `/api/{projectId}/envelope/` | Sentry SDK ingest path | +| `GET` | `/stream` | Server-Sent Events feed of incoming envelopes | +| `GET` | `/health` | Liveness check (returns `OK`) | + +## Pointing your SDK at the sidecar + +Set a localhost DSN that resolves to the sidecar's port — the public key and project ID can be any non-empty value because the sidecar accepts everything: + +```bash +SENTRY_DSN=http://public@localhost:8969/1 +``` + +Or configure your SDK's transport explicitly to send envelopes to `http://localhost:8969/stream`. + +## Tail output + +By default, every envelope received is logged as a single line: + +``` +14:32:01.456 • event+attachment +``` + +The label is the joined list of envelope item types (`event`, `transaction`, `log`, `attachment`, etc.). Use `--quiet` to suppress this output if you only need the SSE stream for the Spotlight overlay. diff --git a/package.json b/package.json index 54722e2b9..c996578bb 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "@anthropic-ai/sdk": "^0.39.0", "@biomejs/biome": "2.3.8", "@clack/prompts": "^0.11.0", + "@hono/node-server": "^2.0.0", "@mastra/client-js": "^1.4.0", "@sentry/api": "^0.113.0", "@sentry/node-core": "10.50.0", "@sentry/sqlish": "^1.0.0", + "@spotlightjs/spotlight": "^4.11.3", "@stricli/auto-complete": "^1.2.4", "@stricli/core": "^1.2.4", "@types/bun": "latest", @@ -28,6 +30,7 @@ "consola": "^3.4.2", "esbuild": "^0.25.0", "fast-check": "^4.5.3", + "hono": "^4.12.15", "http-cache-semantics": "^4.2.0", "ignore": "^7.0.5", "marked": "^15", diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index 5a7ae22b9..f2e7d7b8c 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -456,6 +456,14 @@ Initialize Sentry in your project (experimental) → Full flags and examples: `references/init.md` +### Local + +Run a local Spotlight sidecar to capture dev SDK events + +- `sentry local` — Run a local Spotlight sidecar to capture dev SDK events + +→ Full flags and examples: `references/local.md` + ### Schema Browse the Sentry API schema diff --git a/plugins/sentry-cli/skills/sentry-cli/references/local.md b/plugins/sentry-cli/skills/sentry-cli/references/local.md new file mode 100644 index 000000000..92ee5ab9a --- /dev/null +++ b/plugins/sentry-cli/skills/sentry-cli/references/local.md @@ -0,0 +1,42 @@ +--- +name: sentry-cli-local +version: 0.31.0-dev.0 +description: Run a local Spotlight sidecar to capture dev SDK events +requires: + bins: ["sentry"] + auth: true +--- + +# Local Commands + +Run a local Spotlight sidecar to capture dev SDK events + +### `sentry local` + +Run a local Spotlight sidecar to capture dev SDK events + +**Flags:** +- `-p, --port - Port to listen on (default 8969) - (default: "8969")` +- `-H, --host - Hostname to bind to (default localhost) - (default: "localhost")` +- `-o, --open - Open the sidecar SSE URL in a browser` +- `-q, --quiet - Suppress per-envelope tail output` + +**Examples:** + +```bash +# Start the sidecar on the default port (8969) +sentry local + +# Use a custom port and bind to all interfaces +sentry local --port 9000 --host 0.0.0.0 + +# Run quietly (suppress per-envelope tail output) +sentry local --quiet + +# Open the SSE endpoint in a browser on startup +sentry local --open + +SENTRY_DSN=http://public@localhost:8969/1 +``` + +All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags. diff --git a/src/app.ts b/src/app.ts index 52174001e..e5871191d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -18,6 +18,7 @@ import { helpCommand } from "./commands/help.js"; import { initCommand } from "./commands/init.js"; import { issueRoute } from "./commands/issue/index.js"; import { listCommand as issueListCommand } from "./commands/issue/list.js"; +import { localCommand } from "./commands/local.js"; import { logRoute } from "./commands/log/index.js"; import { listCommand as logListCommand } from "./commands/log/list.js"; import { orgRoute } from "./commands/org/index.js"; @@ -99,6 +100,7 @@ export const routes = buildRouteMap({ trace: traceRoute, trial: trialRoute, init: initCommand, + local: localCommand, api: apiCommand, schema: schemaCommand, dashboards: dashboardListCommand, diff --git a/src/commands/local.ts b/src/commands/local.ts new file mode 100644 index 000000000..02791b951 --- /dev/null +++ b/src/commands/local.ts @@ -0,0 +1,369 @@ +/** + * sentry local + * + * Run a local Spotlight-compatible sidecar server. + * + * Spotlight (https://github.com/getsentry/spotlight) is "Sentry for + * Development" — a small local proxy that ingests Sentry envelopes from + * SDKs running in your dev stack and surfaces them in real time. + * + * This command starts a minimal Hono HTTP server that: + * + * 1. Accepts envelopes from Sentry SDKs at the standard sidecar endpoints: + * - `POST /stream` (Spotlight-compatible) + * - `POST /api/{projectId}/envelope/` (Sentry SDK ingest path) + * 2. Pushes them into the buffer provided by `@spotlightjs/spotlight/sdk`, + * which lazily parses each envelope. + * 3. Streams new envelopes back to subscribers via Server-Sent Events at + * `GET /stream` — compatible with the Spotlight overlay/UI. + * 4. Tails events to the terminal as they arrive so you can see what your + * app is sending without leaving the CLI. + * + * To point your SDK at the local sidecar, use a placeholder DSN that + * resolves to localhost — for example: + * + * SENTRY_DSN=http://public@localhost:8969/1 + * + * Or configure your SDK's transport to send to `http://localhost:8969/stream`. + * + * The command runs until interrupted (Ctrl-C / SIGTERM). + */ + +import type { Server } from "node:http"; +import { serve } from "@hono/node-server"; +import { + createSpotlightBuffer, + pushToSpotlightBuffer, +} from "@spotlightjs/spotlight/sdk"; +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { streamSSE } from "hono/streaming"; +import type { SentryContext } from "../context.js"; +import { openOrShowUrl } from "../lib/browser.js"; +import { buildCommand, numberParser } from "../lib/command.js"; +import { ValidationError } from "../lib/errors.js"; +import { bold, cyan, muted } from "../lib/formatters/colors.js"; +import { logger } from "../lib/logger.js"; + +const log = logger.withTag("local"); + +/** Default port matches Spotlight's `DEFAULT_PORT`. */ +const DEFAULT_PORT = 8969; + +/** Buffer size: how many recent envelopes to retain for late subscribers. */ +const BUFFER_SIZE = 500; + +/** SSE event payload — what we send to GET /stream subscribers. */ +type EventPayload = { + contentType: string; + data: string; // base64-encoded raw envelope bytes +}; + +type LocalFlags = { + readonly port: number; + readonly host: string; + readonly open: boolean; + readonly quiet: boolean; +}; + +/** + * Validate a port number from `--port`. + * + * Hard-fails on out-of-range values so users get a clean error rather than + * a `listen EADDRNOTAVAIL` from the kernel. + */ +function parsePort(value: string): number { + const port = numberParser(value); + if (!Number.isInteger(port) || port < 0 || port > 65_535) { + throw new ValidationError( + `Invalid port: ${value}. Must be an integer between 0 and 65535.`, + "port" + ); + } + return port; +} + +/** + * Build the Hono application that backs the sidecar. + * + * We expose three concerns: + * - CORS: open to `*` because dev stacks send from arbitrary `localhost:*` + * origins (Vite, Next, Astro, etc.). The sidecar binds to localhost by + * default, so this isn't a security regression. + * - Ingest: `POST /stream` and `POST /api/.../envelope/` accept envelope + * bodies. We hand the raw buffer to `pushToSpotlightBuffer`, which + * decompresses (gzip/deflate/br) and decodes lazily. + * - Subscribe: `GET /stream` opens an SSE stream of every envelope that + * enters the buffer, including those buffered before the subscriber + * connected (so a freshly-opened Spotlight overlay can still see + * recent events). + */ +function buildSidecarApp( + spotlightBuffer: ReturnType, + onEnvelope: (contentType: string, data: Buffer) => void +): Hono { + const app = new Hono(); + + // Open CORS — sidecar binds to localhost; this is a dev-only tool. + app.use( + "*", + cors({ + origin: "*", + allowMethods: ["GET", "POST", "OPTIONS"], + allowHeaders: ["Content-Type", "Content-Encoding", "User-Agent"], + }) + ); + + /** Health check — useful for `curl` and for SDKs that probe before sending. */ + app.get("/health", (c) => c.text("OK")); + + /** Ingest handler shared by `/stream` and `/api/.../envelope/`. */ + const ingest = async (c: { + req: { + arrayBuffer: () => Promise; + header: (name: string) => string | undefined; + }; + body: (data: null, status: number) => Response; + }) => { + const arrayBuf = await c.req.arrayBuffer(); + const body = Buffer.from(arrayBuf); + const contentType = c.req.header("content-type") ?? ""; + const contentEncoding = c.req.header("content-encoding") as + | "gzip" + | "deflate" + | "br" + | undefined; + const userAgent = c.req.header("user-agent"); + + const container = pushToSpotlightBuffer({ + spotlightBuffer, + body, + encoding: contentEncoding, + contentType, + userAgent, + }); + + if (container) { + // Surface the decoded payload to the tail/subscribe pipeline. We push + // the (potentially decompressed) raw body so SSE subscribers don't + // have to redo the work and so the tail formatter can rely on a + // single representation. + onEnvelope(container.getContentType(), container.getData()); + } + + return c.body(null, 204); + }; + + app.post("/stream", ingest); + // SDK-style envelope ingestion: /api/{projectId}/envelope/?... + app.post("/api/:projectId/envelope/", ingest); + app.post("/api/:projectId/envelope", ingest); + + /** + * SSE stream — Spotlight overlay / UI clients connect here to receive a + * live feed of envelopes. Each event is emitted as a JSON object with + * the content type and base64-encoded body. + */ + app.get("/stream", (c) => + streamSSE(c, async (stream) => { + // Tie the subscriber lifetime to the response stream. We unsubscribe + // when the client disconnects so the buffer doesn't leak readers. + const readerId = spotlightBuffer.subscribe((container) => { + const payload: EventPayload = { + contentType: container.getContentType(), + data: container.getData().toString("base64"), + }; + stream + .writeSSE({ + event: "envelope", + data: JSON.stringify(payload), + }) + .catch((err: unknown) => { + log.debug( + `SSE write failed (client likely disconnected): ${ + err instanceof Error ? err.message : String(err) + }` + ); + }); + }); + + stream.onAbort(() => { + spotlightBuffer.unsubscribe(readerId); + }); + + // Keep the stream open until the client disconnects. + // hono/streaming resolves the promise on abort. + await new Promise((resolve) => { + stream.onAbort(() => resolve()); + }); + }) + ); + + return app; +} + +/** + * Resolve the human label for an envelope. + * + * - When the envelope parses cleanly, we use the joined item types + * (e.g. `event+attachment`). + * - Otherwise we fall back to the content type, with a friendly alias + * for the canonical Sentry envelope mime type. + */ +function describeEnvelope(contentType: string, eventTypes: string[]): string { + if (eventTypes.length > 0) { + return eventTypes.join("+"); + } + if (contentType === "application/x-sentry-envelope") { + return "envelope"; + } + return contentType; +} + +/** + * Format a freshly received envelope for terminal output. + * + * Keeps the formatting deliberately minimal — this is a tail, not a UI. + * If users want rich rendering, they can point the Spotlight overlay at + * `http://localhost:/stream` instead. + */ +function formatTailLine(contentType: string, eventTypes: string[]): string { + const ts = new Date().toISOString().slice(11, 23); // HH:MM:SS.sss + const label = describeEnvelope(contentType, eventTypes); + return `${muted(ts)} ${cyan("•")} ${bold(label)}`; +} + +/** + * Install signal handlers that stop the HTTP server on Ctrl-C / SIGTERM. + * + * Returns a Promise that resolves when shutdown is complete. The command + * awaits this so the generator stays alive until the user interrupts. + */ +function waitForShutdown(server: Server): Promise { + return new Promise((resolve) => { + let shuttingDown = false; + const shutdown = (signal: NodeJS.Signals) => { + if (shuttingDown) { + // Second signal — force exit. Bypasses the `process.exit` hook so + // we don't dangle on stuck connections. + process.exit(0); + } + shuttingDown = true; + log.info(`Received ${signal}, shutting down...`); + server.close(() => resolve()); + // Force-close keep-alive connections so we don't wait on long-lived + // SSE subscribers. + if (typeof server.closeAllConnections === "function") { + server.closeAllConnections(); + } + }; + + process.once("SIGINT", () => shutdown("SIGINT")); + process.once("SIGTERM", () => shutdown("SIGTERM")); + }); +} + +export const localCommand = buildCommand({ + docs: { + brief: "Run a local Spotlight sidecar to capture dev SDK events", + fullDescription: + "Start a local Spotlight-compatible sidecar server.\n\n" + + "Spotlight is Sentry for Development — it gives you a live view of\n" + + "errors, traces, and logs emitted by Sentry SDKs in your dev stack.\n" + + "This command runs a minimal Hono server that ingests envelopes\n" + + "from any Sentry SDK and tails them to your terminal.\n\n" + + "Endpoints:\n" + + " POST /stream — Spotlight ingest\n" + + " POST /api/{projectId}/envelope/ — Sentry SDK ingest\n" + + " GET /stream — SSE feed (for the Spotlight overlay)\n" + + " GET /health — health check\n\n" + + "Configure your SDK to send to the sidecar with a localhost DSN, e.g.:\n" + + " SENTRY_DSN=http://public@localhost:8969/1\n\n" + + "Press Ctrl-C to stop the server.", + }, + // No `output` config: this is a long-running server, not a data command. + // We write progress directly to stderr via the logger. + parameters: { + flags: { + port: { + kind: "parsed", + parse: parsePort, + brief: `Port to listen on (default ${DEFAULT_PORT})`, + default: String(DEFAULT_PORT), + }, + host: { + kind: "parsed", + parse: String, + brief: "Hostname to bind to (default localhost)", + default: "localhost", + }, + open: { + kind: "boolean", + brief: "Open the sidecar SSE URL in a browser", + default: false, + }, + quiet: { + kind: "boolean", + brief: "Suppress per-envelope tail output", + default: false, + }, + }, + aliases: { + p: "port", + H: "host", + o: "open", + q: "quiet", + }, + }, + // No auth required — this is a local-only dev server. + auth: false, + async *func(this: SentryContext, flags: LocalFlags) { + const buffer = createSpotlightBuffer(BUFFER_SIZE); + + // Tail subscriber: prints a one-line summary for each envelope. We + // route through the logger (stderr) rather than stdout so the tail + // doesn't pollute pipelines that consume the CLI's stdout, and so it + // honors `--log-level` / `SENTRY_LOG_LEVEL` like the rest of the CLI. + // Skipped entirely when `--quiet` is set. + if (!flags.quiet) { + buffer.subscribe((container) => { + const types = container.getEventTypes() ?? []; + log.info(formatTailLine(container.getContentType(), types)); + }); + } + + const app = buildSidecarApp(buffer, () => { + // Tail output is driven by the buffer subscriber above so we don't + // have to repeat the formatting work. This callback is a no-op for + // now; future hooks (e.g. metrics, file logging) can plug in here. + }); + + // `serve` returns a Node http.Server — we use it for graceful shutdown. + const server = serve({ + fetch: app.fetch, + port: flags.port, + hostname: flags.host, + }) as unknown as Server; + + const url = `http://${flags.host}:${flags.port}`; + log.info(`Spotlight sidecar listening on ${bold(url)}`); + log.info(` ${muted("Ingest:")} POST ${url}/stream`); + log.info(` ${muted("Stream:")} GET ${url}/stream`); + log.info(` ${muted("Health:")} GET ${url}/health`); + log.info(`Point your SDK at ${bold(`${url}/stream`)} or use a DSN like:`); + log.info( + ` ${muted(`SENTRY_DSN=${url.replace("http://", "http://public@")}/1`)}` + ); + log.info("Press Ctrl-C to stop."); + + if (flags.open) { + // Best-effort — never blocks shutdown. + await openOrShowUrl(`${url}/stream`); + } + + // Block until the user interrupts. We don't yield any CommandOutput + // because there's no structured payload — this command is a server. + await waitForShutdown(server); + log.info("Sidecar stopped."); + }, +}); From 3beec45f04c81fd0ffb051123865f9c1b2317544 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Tue, 5 May 2026 20:13:36 +0000 Subject: [PATCH 02/21] feat(local): pretty-print errors, transactions, and logs in tail output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the minimal 'timestamp • type' one-liner with rich formatted output that shows actual event content: error type/message with stack location, transaction name/op/duration/span count, and log messages with attributes. Uses the CLI's own color system since Spotlight's humanFormatters aren't publicly exported from the package. --- AGENTS.md | 83 +---- .../skills/sentry-cli/references/dashboard.md | 2 +- .../skills/sentry-cli/references/event.md | 2 +- .../skills/sentry-cli/references/explore.md | 2 +- .../skills/sentry-cli/references/issue.md | 4 +- .../skills/sentry-cli/references/log.md | 2 +- .../skills/sentry-cli/references/span.md | 2 +- .../skills/sentry-cli/references/trace.md | 4 +- src/commands/local.ts | 309 ++++++++++++++++-- 9 files changed, 298 insertions(+), 112 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1bfd4b629..5050a2838 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1001,86 +1001,11 @@ mock.module("./some-module", () => ({ ### Architecture - -* **@sentry/api SDK integration: type wrapping pattern and pagination helpers**: @sentry/api SDK integration: wrap SDK types at \`src/lib/api/\*.ts\` boundaries with \`as unknown as SentryX\` casts; never leak SDK types to commands. Wrappers in \`src/types/sentry.ts\` use \`Partial\ & RequiredCore\`. \`src/lib/region.ts\` imports \`retrieveAnOrganization\` directly to avoid circular dep with api-client. \`unwrapResult\`/\`unwrapPaginatedResult\` MUST stay CLI-owned — SDK versions throw plain \`Error\`, breaking the 'all errors are CliError subclasses' invariant (see also 365e4299). Body-shape casts use \`Parameters\\[0]\["body"]\`. - - -* **apiRequestToRegion/rawApiRequest options shape — no timeout/signal/headers on the typed API**: \`ApiRequestOptions\\` in \`src/lib/api/infrastructure.ts\` has only \`{ method, body, params, schema }\`. \`rawApiRequest\` adds \`headers?\`; neither exposes \`timeout\`/\`signal\`. Call sites pass \`(url, init: RequestInit)\` to authenticated fetch — never a \`Request\` (only @sentry/api SDK does). \`apiRequestToRegion\` auto-sets JSON Content-Type and \`JSON.stringify\`s body; \`rawApiRequest\` preserves string bodies, only sets JSON Content-Type when body is object and caller didn't provide one. 204/205 throw \`ApiError\` rather than crashing on \`response.json()\` — bulk-mutate callers must catch. - - -* **Completion fast-path skips Sentry SDK via SENTRY\_CLI\_NO\_TELEMETRY and SQLite telemetry queue**: Shell completions (\`\_\_complete\`) set \`SENTRY\_CLI\_NO\_TELEMETRY=1\` in \`bin.ts\` before any imports, skipping \`createTracedDatabase\` and avoiding \`@sentry/node-core/light\` load (~85ms). Completion timing queued to \`completion\_telemetry\_queue\` SQLite table (~1ms); normal runs drain via \`DELETE FROM ... RETURNING\` and emit as \`Sentry.metrics.distribution\`. Achieves ~60ms dev / ~140ms CI within 200ms e2e budget. - - -* **Fuzzy recovery auto-resolves dash/underscore slug mismatches without original-slug fallback**: Display-name project input (contains spaces) skips slug lookup, goes to name-based fuzzy search across four resolution sites: \`resolveProjectBySlug\`, \`resolveOrgProjectTarget\` (project-search), \`org-list.ts#handleProjectSearch\`, \`project/list.ts#handleProjectNotFound\`. \`parseOrgProjectArg\` detects spaces via \`looksLikeDisplayName()\` and sets \`originalSlug\` on \`project-search\`; sites check \`isDisplayName = originalSlug !== undefined\` and skip \`findProjectsBySlug\` (404s on URL-encoded spaces), going directly to \`triageProjectNotFound\` → \`findSimilarProjectsAcrossOrgs\`. \*\*Critical\*\*: recursive fuzzy recovery calls must NOT pass \`originalSlug\` — otherwise the recovered slug also skips lookup, causing infinite skip→empty→not-found loop. - - -* **Project cache is org-scoped with three key formats and three population paths**: \`project\_cache\` SQLite table uses three key shapes: \`{orgId}:{projectId}\` (DSN resolution), \`dsn:{publicKey}\` (DSN without orgId), \`list:{orgSlug}/{projectSlug}\` (batch from API). Helpers: \`getCachedProject\`, \`getCachedProjectByDsnKey\`, \`getCachedProjectsForOrg\` (completions), \`getCachedProjectBySlug\` (queries all three shapes for hot-path slug lookups; used by \`fetchProjectId\` to skip \`GET /projects/{org}/{project}/\`). Population paths: DSN resolution in resolve-target.ts, \`listProjects()\` batch via \`cacheProjectsForOrg\`, \`fetchProjectId\` seeds on API success. Resolution errors use live API via \`findSimilarProjectsAcrossOrgs\` — no cross-org cache search. - - -* **Sentry API: events require org+project, issues have legacy global endpoint**: Sentry API scoping/auth quirks: (1) Events require org+project (\`/projects/{org}/{project}/events/{id}/\`); issues use legacy global \`/api/0/issues/{id}/\`; traces need only org. Cross-project search via Discover \`/organizations/{org}/events/?query=id:{eventId}\`. (2) \`/users/me/\` returns 403 for OAuth tokens — use \`/auth/\` instead (all token types, control silo). \`getControlSiloUrl()\` routes; \`SentryUserSchema\` uses \`.passthrough()\` since \`/auth/\` only requires \`id\`. (3) Chunk upload endpoint returns camelCase (\`chunkSize\`, \`chunksPerRequest\`, \`maxRequestSize\`, \`hashAlgorithm\`); \`AssembleResponse\` also camelCase — exception to snake\_case convention. - - -* **Sentry CLI authenticated fetch architecture with response caching**: Authenticated fetch (\`createAuthenticatedFetch\` in \`src/lib/sentry-client.ts\`): auth headers, 30s \`REQUEST\_TIMEOUT\_MS\`, retry max 2, 401 refresh, span tracing. Dual input: SDK \`Request\` vs \`(url, init)\`. \`buildAttemptFactory\` yields fresh \`(input, init)\` per attempt; \`Request\` clones; \`FormData\`/\`Blob\`/\`URLSearchParams\` pass through. Only bare \`ReadableStream\` needs materialization. Do NOT materialize FormData — strips multipart boundary. Internal aborts tagged \`INTERNAL\_TIMEOUT\_MARKER\` Symbol; last attempt throws \`TimeoutError\`. Per-endpoint \`ENDPOINT\_TIMEOUT\_OVERRIDES\` (e.g. \`/autofix/\` 120s). Response cache: \`http-cache-semantics\` RFC 7234 at \`~/.sentry/cache/responses/\`; GET 2xx only. On 4xx/5xx, \`apiRequestToRegion\` attaches allow-listed response headers to Sentry scope as \`api\_response\_headers\` context. Cache hit invisibility solved via module-level \`lastCacheHitAgeMs\` (set on hit, cleared per-call); \`src/lib/cache-hint.ts\` provides \`formatCacheHint()\`/\`appendCacheHint()\`, wired in \`buildCommand\` only when generator returns \`CommandReturn\` (bare \`return;\` paths skip). - - -* **Sentry CLI resolve-target cascade has 5 priority levels with env var support**: resolve-target.ts cascade has 5 priority levels: (1) Explicit CLI flags, (2) SENTRY\_ORG/SENTRY\_PROJECT env vars, (3) SQLite config defaults, (4) DSN auto-detection, (5) Directory name inference. SENTRY\_PROJECT supports combo notation \`org/project\` — when used, SENTRY\_ORG is ignored. If combo parse fails (e.g. \`org/\`), the entire value is discarded. \`resolveFromEnvVars()\` helper is injected into all four resolution functions. - -### Decision - - -* **Issue list global limit with fair per-project distribution and representation guarantees**: \`issue list --limit\` is a global total across all detected projects. \`fetchWithBudget\` Phase 1 divides evenly, Phase 2 redistributes surplus via cursor resume. \`trimWithProjectGuarantee\` ensures ≥1 issue per project before filling remaining slots. JSON output wraps in \`{ data, hasMore }\` with optional \`errors\` array. Compound cursor (pipe-separated) enables \`-c last\` for multi-target pagination, keyed by sorted target fingerprint. - - -* **Prefer dedicated SQLite tables + migrations over metadata KV for non-trivial caches**: Prefer dedicated SQLite tables + migrations over \`metadata\` KV for non-trivial caches. Schema migrations are cheap — don't shoehorn structured caches into \`metadata\` with dotted-prefix keys. Dedicated tables give clearer schema, proper indexes, simpler bulk-clear, no prefix collisions. \`metadata\` KV is fine for small scalars (defaults.\*, install.\*). Example: \`issue\_org\_cache\` (schema v15) replaced \`metadata\` keys \`issue\_org.{numericId}\`. Migration pattern: bump \`CURRENT\_SCHEMA\_VERSION\`, add \`EXPECTED\_TABLES.foo\`, add \`if (currentVersion < N) db.exec(EXPECTED\_TABLES.foo)\`. HTTP response cache (URL+headers, short TTLs) can't answer structural questions like 'which org owns issue 123?' — use dedicated tables for structural/mapping questions, HTTP cache for content. - - -* **Top-level --help stays terse Stricli output, not branded help**: \`sentry --help\` and \`sentry -h\` MUST render Stricli's terse default template, NOT the branded help (Flags + Environment Variables sections). Agents parse \`--help\` output and branding wastes tokens. Branded help is reserved for human discovery paths: \`sentry\` (no-args, via \`defaultCommand: "help"\`) and \`sentry help\`. Do NOT add interception logic in \`src/cli.ts\` to rewrite \`--help\` → \`help\`. TTY/agent detection is not worth the complexity — agents have skills documentation; humans get the footer hint pointing to \`sentry help\`. Subcommand help (e.g. \`sentry issue --help\`) is also left to Stricli for command-specific flag rendering. + +* **sentry local command uses getParsedEnvelope() for envelope item dispatch**: \`src/commands/local.ts\` receives raw Sentry envelopes via a Hono HTTP server, pushes them into a Spotlight \`MessageBuffer\\`, then in the subscriber calls \`container.getParsedEnvelope()\` to get \`\[header, items\[]]\`. Each item's \`\[itemHeader, itemPayload]\` is dispatched: \`event\`/\`error\` types → \`formatErrorItem\`, \`transaction\` → \`formatTransactionItem\`, \`log\` → \`formatLogItem\` (returns multiple lines), all others fall back to a minimal \`timestamp • type\` line. Formatters are inline in \`local.ts\` and use the CLI's color helpers. ### Gotcha - -* **@sentry/api SDK can return non-array data for empty/edge responses**: \`@sentry/api\` SDK (in \`node\_modules/@sentry/api/dist/index.js\`) returns \`data = {}\` (not \`\[]\`) when response body is empty, has \`Content-Length: 0\`, or status 204; and returns a \`ReadableStream\` when \`Content-Type\` is missing. \`unwrapResult\` from \`src/lib/api/infrastructure.ts\` returns \`data\` as-is, and \`as unknown as SentryX\[]\` casts silently lie. Always guard array-typed SDK results with \`Array.isArray(data)\` before \`.map()\` — applied in \`listOrganizationsInRegion\` (CLI-1CQ). Self-hosted instances behind reverse proxies (nginx, Cloudflare, WAFs) commonly trigger this by stripping bodies or wrapping responses. Throw a descriptive \`ApiError\` on mismatch rather than letting \`TypeError: x.map is not a function\` bubble up minified. - - -* **Bun bytecode: true crashes esbuild→compile ESM bundles (Bun 1.3.11)**: Bun build flags for compiled CLI (\`script/build.ts\`): (1) Do NOT enable \`bytecode: true\` with esbuild→\`Bun.build({ compile })\` pipeline. Still broken on Bun 1.3.13 — crashes \`TypeError: Expected CommonJS module to have a function wrapper\` at entry.instantiate (esbuild emits ESM; bytecode loader mis-caches as CJS). Exit 0, no output. Upstream: oven-sh/bun#21097, #23490. (2) Pass \`autoloadDotenv: false\` and \`autoloadBunfig: false\` — otherwise user's \`.env\`/\`bunfig.toml\` silently injects into \`process.env\` (e.g. Next.js \`.env.local\` could override stored OAuth token). Shell env vars still work; suggest direnv for dir-scoped vars. - - -* **dist/bin.cjs runtime Node version check must match engines.node**: \`engines.node >=22.12\` matches Astro 6 floor. CI builds matrix \`\["22","24"]\`; docs jobs pin \`actions/setup-node@v6\` with \`node-version: "24"\` after \`setup-bun\`. The npm package's \`dist/bin.cjs\` (from \`script/bundle.ts\`) contains an inline Node guard that MUST match \`engines.node\`. Simple \`parseInt(process.versions.node) < 22\` misses 22.0.0–22.11.x — use explicit major+minor: \`let v=process.versions.node.split('.').map(Number);if(v\[0]<22||(v\[0]===22&\&v\[1]<12)){...}\`. When bumping, update BIN\_WRAPPER string AND error message in lockstep. Without \`engine-strict=true\`, npm only warns — the runtime guard is real enforcement. - - -* **Making clearAuth() async breaks model-based tests — use non-async Promise\ return instead**: Making \`clearAuth()\` \`async\` breaks fast-check model-based tests — real async yields during \`asyncModelRun\` cause \`createIsolatedDbContext\` cleanup to interleave. Keep non-async; return \`clearResponseCache().catch(...)\` directly. Model-based tests also need explicit timeouts (e.g., \`30\_000\`) — Bun's default 5s causes false failures during shrinking. - - -* **script/generate-api-schema.ts regex is brittle against SDK bundler output changes**: \`script/generate-api-schema.ts\` parses \`node\_modules/@sentry/api/dist/index.js\` with a regex (\`/var (\w+) = \\(options\S\*\\) => \\(options\S\*client \\?\\? client\\)\\.(\w+)\\(/g\`) to map SDK function names to URL+method pairs, producing \`src/generated/api-schema.json\`. If the SDK changes its generator's bundle format (e.g., switches to \`const\`, arrow vs function, different client-selection pattern), schema generation silently produces empty \`fn\` fields. When bumping \`@sentry/api\`, verify \`sentry schema\` output still populates function names. \`src/generated/api-schema.json\` is gitignored — regenerates on every dev/build/typecheck via \`bun run generate:schema\`. - - -* **Source Map v3 spec allows null entries in sources array**: The Source Map v3 spec allows \`null\` entries in the \`sources\` array, and bundlers like esbuild actually produce them. Any code iterating over \`sources\` and calling string methods (e.g., \`.replaceAll()\`) must guard against null: \`map.sources.map((s) => typeof s === "string" ? s.replaceAll("\\\\", "/") : s)\`. Without the guard, \`null.replaceAll()\` throws \`TypeError\`. This applies to \`src/lib/sourcemap/debug-id.ts\` and any future sourcemap manipulation code. - - -* **Starlight 0.33+ route data moved from Astro.props to Astro.locals.starlightRoute**: Starlight 0.33+ / Astro 6 docs migration: (1) Route data moved from \`Astro.props\` to \`Astro.locals.starlightRoute\` — old \`Astro.props.sidebar\` is \`undefined\`. Field rename: \`slug\` → \`id\`. Import types via \`@astrojs/starlight/route-data\`. Built-in children (SiteTitle, Search, SocialIcons) take no props. \`starlight.social\` is array-form. (2) Content collections must migrate to Content Layer API: rename \`src/content/config.ts\` → \`src/content.config.ts\`, use \`docsLoader()\` + \`docsSchema()\`. Landing-page detection: \`id === ""\` (\`normalizeIndexSlug\` maps \`"index"\` → \`""\`). - -### Pattern - - -* **Bun global installs use .bun path segment for detection**: Bun global installs place scripts under \`~/.bun/install/global/node\_modules/\`. In \`detectPackageManagerFromPath()\`, check \`segments.includes('.bun')\` before npm fallback. Order: \`.pnpm\` → pnpm, \`.bun\` → bun, other \`node\_modules\` → npm. Yarn classic shares npm layout so falls through — acceptable because path detection is \*\*fallback\*\* after subprocess calls (which identify yarn correctly). Path detection must NOT override stored DB info, only serve as fallback when subprocess fails (e.g., Windows \`.cmd\` ENOENT). - - -* **Evict-then-read pattern: return cacheEvicted flag from helpers that clear cache on 404**: When a helper function transparently evicts a stale cache entry on 404 and falls back to an unscoped call, callers holding the now-stale cached value will let it win \`??\` chains. Fix: helper must return \`{ result, cacheEvicted }\` so callers compute \`effectiveCachedValue = cacheEvicted ? null : cachedValue\` before the \`??\` fallback, and re-cache the freshly-derived value. Applied in \`fetchIssueByNumericId\` in \`src/commands/issue/utils.ts\` — both \`resolveNumericIssue\` and \`resolveShareIssue\` consume the flag. A local cached variable outliving its DB entry is the common shape of this bug; always audit post-eviction read paths. - - -* **Non-essential DB cache writes should be guarded with try-catch**: Non-essential DB cache writes (e.g., \`setUserInfo()\`, \`setInstallInfo()\`) must be wrapped in try-catch so a broken/read-only DB doesn't crash a command whose primary operation succeeded. Pattern: \`try { setInstallInfo(...) } catch { log.debug(...) }\`. In login.ts, \`getCurrentUser()\` failure after token save must not block auth — log warning, continue. In upgrade.ts, \`setInstallInfo\` after legacy detection is guarded same way. Exception: \`getUserRegions()\` failure should \`clearAuth()\` and fail hard (indicates invalid token). This is enforced by BugBot reviews — any \`setInstallInfo\`/\`setUserInfo\` call outside setup.ts's \`bestEffort()\` wrapper needs its own try-catch. - - -* **Sentry CLI command docs are auto-generated from Stricli route tree with CI freshness check**: Sentry CLI command docs are auto-generated from Stricli route tree: Docs in \`docs/src/content/docs/commands/\*.md\` and skill files in \`plugins/sentry-cli/skills/sentry-cli/references/\*.md\` are generated via \`bun run generate:docs\`. Content between \`\\` markers is regenerated; hand-written examples go in \`docs/src/fragments/commands/\`. CI checks \`check:command-docs\` and \`check:skill\` fail if stale. Run generators after changing command parameters/flags/docs. - - -* **Stricli buildCommand output config injects json flag into func params**: Stricli command gotchas: (1) In \`func()\` handlers use \`this.stdout\`/\`this.stderr\` directly — NOT \`this.process.stdout\`. \`SentryContext\` has \`process\` and \`stdout\`/\`stderr\` as separate top-level properties; test mocks omit full \`process\` so \`this.process.stdout\` throws \`TypeError\` at runtime (TS doesn't catch). (2) \`output: { json: true, human: formatFn }\` auto-injects \`--json\`/\`--fields\` flags — type flags explicitly (\`flags: { json?: boolean }\`). Commands with interactive side effects (prompts, QR codes) should check \`flags.json\` and skip. (3) Route maps with \`defaultCommand\` blend the default command's flags into subcommand completions — completion tests must track \`hasDefaultCommand\` and skip strict subcommand-matching. - - -* **Token-type classification via literal prefix match (classifySentryToken)**: Token-type classification via literal prefix match: \`src/lib/token-type.ts\` \`classifySentryToken(token)\` returns \`'org-auth-token'\` (\`sntrys\_\` prefix), \`'user-auth-token'\` (\`sntryu\_\` prefix), or \`'oauth-or-legacy'\`. Case-sensitive \`startsWith\` matches Sentry backend's \`SENTRY\_ORG\_AUTH\_TOKEN\_PREFIX\`. Use to short-circuit commands where a token type is semantically invalid (e.g. \`whoami\` with org token — \`/auth/\` rejects \`sntrys\_\`) before a confusing API failure. \`getAuthToken()\` from \`db/auth\` returns the effective token (env + DB fallback). - -### Preference - - -* **PR workflow: address review comments, resolve threads, wait for CI**: PR workflow: (1) wait for CI; (2) check unresolved comments via \`gh api repos/.../pulls/N/comments\`; (3) fix in follow-up commits (NEVER amend a pushed commit without explicit user request + force push); (4) reply explaining fix; (5) resolve thread via \`gh api graphql resolveReviewThread\`; (6) push + re-check CI. BugBot/Seer/Warden/Cursor post new comments per-commit and often catch bugs in fix commits — re-check after each push. Dispatch a subagent review before declaring merge-ready. Branches: \`fix/\*\` or \`feat/\*\`. Style: \`Array.from(set)\` over spreads; 'allowlist' not 'whitelist'; \`arr.at(-1)\` over index math. Reviewer questions may be inquiries — confirm intent before changing. After reverts/changes affecting PR scope, update the PR description to match. + +* **@spotlightjs/spotlight exports only two paths — no formatter/parser access**: The \`@spotlightjs/spotlight\` package's \`exports\` map exposes only \`.\` (main server) and \`./sdk\` (buffer API). Formatter registries (\`humanFormatters\`, \`applyFormatter\`) and parser helpers (\`isErrorEvent\`, type guards) live under \`dist/server/formatters/\` and \`dist/server/parser/\` but are not in \`exports\`. Bun's strict module resolution blocks deep \`dist/\` imports at runtime. Workaround: write inline formatters using the CLI's own color system (\`muted\`, \`bold\`, \`cyan\`, etc.) following the same pattern as Spotlight's human formatters. diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index a6dd162e9..799575e39 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -42,7 +42,7 @@ View a dashboard - `-w, --web - Open in browser` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-r, --refresh - Auto-refresh interval in seconds (default: 60, min: 10)` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01"` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01"` **Examples:** diff --git a/plugins/sentry-cli/skills/sentry-cli/references/event.md b/plugins/sentry-cli/skills/sentry-cli/references/event.md index 43c3e7c07..05dd3fbd1 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/event.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/event.md @@ -37,7 +37,7 @@ List events for an issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/explore.md b/plugins/sentry-cli/skills/sentry-cli/references/explore.md index 7c58e148d..91c0c05c2 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/explore.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/explore.md @@ -21,7 +21,7 @@ Query aggregate event data (Explore) - `-q, --query - Search query (Sentry search syntax)` - `-s, --sort - Sort field (prefix with - for desc, e.g., "-count()")` - `-n, --limit - Number of rows (1-1000) - (default: "25")` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "24h")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "24h")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/issue.md b/plugins/sentry-cli/skills/sentry-cli/references/issue.md index 03beef4ab..83a254cc1 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/issue.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/issue.md @@ -19,7 +19,7 @@ List issues in a project - `-q, --query - Search query (Sentry syntax, implicit AND, no OR operator)` - `-n, --limit - Maximum number of issues to list - (default: "25")` - `-s, --sort - Sort by: date, new, freq, user - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "90d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "90d")` - `-c, --cursor - Pagination cursor (use "next" for next page, "prev" for previous)` - `--compact - Single-line rows for compact output (auto-detects if omitted)` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` @@ -87,7 +87,7 @@ List events for a specific issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/log.md b/plugins/sentry-cli/skills/sentry-cli/references/log.md index 05fb7c1d3..fbfe59c81 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/log.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/log.md @@ -19,7 +19,7 @@ List logs from a project - `-n, --limit - Number of log entries (1-1000) - (default: "100")` - `-q, --query - Filter query (e.g., "level:error", "project:backend", "project:[a,b]")` - `-f, --follow - Stream logs (optionally specify poll interval in seconds)` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01"` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01"` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` - `--fresh - Bypass cache, re-detect projects, and fetch fresh data` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/span.md b/plugins/sentry-cli/skills/sentry-cli/references/span.md index 057f34877..f503d51e8 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/span.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/span.md @@ -19,7 +19,7 @@ List spans in a project or trace - `-n, --limit - Number of spans (<=1000) - (default: "25")` - `-q, --query - Filter spans (e.g., "op:db", "project:backend", "project:[cli,api]")` - `-s, --sort - Sort order: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/trace.md b/plugins/sentry-cli/skills/sentry-cli/references/trace.md index 92611d6d5..b7ea69603 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/trace.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/trace.md @@ -19,7 +19,7 @@ List recent traces in a project - `-n, --limit - Number of traces (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `-s, --sort - Sort by: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` @@ -91,7 +91,7 @@ View logs associated with a trace **Flags:** - `-w, --web - Open trace in browser` -- `-t, --period - Time range: "7d", "2026-03-01..2026-04-01", ">=2026-03-01" - (default: "14d")` +- `-t, --period - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "14d")` - `-n, --limit - Number of log entries (<=1000) - (default: "100")` - `-q, --query - Filter query (e.g., "level:error", "project:backend", "project:[a,b]")` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` diff --git a/src/commands/local.ts b/src/commands/local.ts index 02791b951..c0d0b1d9a 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -42,7 +42,15 @@ import type { SentryContext } from "../context.js"; import { openOrShowUrl } from "../lib/browser.js"; import { buildCommand, numberParser } from "../lib/command.js"; import { ValidationError } from "../lib/errors.js"; -import { bold, cyan, muted } from "../lib/formatters/colors.js"; +import { + bold, + cyan, + green, + magenta, + muted, + red, + yellow, +} from "../lib/formatters/colors.js"; import { logger } from "../lib/logger.js"; const log = logger.withTag("local"); @@ -202,37 +210,289 @@ function buildSidecarApp( return app; } +/** Format a local timestamp as HH:MM:SS from a Sentry timestamp. */ +function formatTime(timestamp?: number | string): string { + let date: Date; + if (!timestamp) { + date = new Date(); + } else if (typeof timestamp === "string") { + date = new Date(timestamp); + } else { + date = new Date(timestamp * 1000); + } + if (Number.isNaN(date.getTime())) { + return "??:??:??"; + } + return date.toLocaleTimeString("en-US", { hour12: false }); +} + +/** Level → color map for tail output. */ +const LEVEL_COLORS: Record string> = { + error: (s) => red(bold(s)), + fatal: (s) => red(bold(s)), + warning: yellow, + info: cyan, + trace: green, + debug: muted, +}; + +/** Colorize a log/event level label. */ +function colorLevel(level: string): string { + const colorFn = LEVEL_COLORS[level]; + return colorFn ? colorFn(level) : level; +} + +/** Mobile SDK name substrings. */ +const MOBILE_MARKERS = ["cocoa", "android", "react-native", "flutter"]; + +/** Server-side JS SDK name substrings — exclude from browser detection. */ +const SERVER_JS_MARKERS = [ + "node", + "bun", + "deno", + "nextjs", + "remix", + "astro", + "nuxt", + "sveltekit", +]; + +/** + * Infer the source platform from the envelope header's `sdk.name` field. + * Returns a short colored label like "server", "browser", or "mobile". + */ +function inferSource(header: Record): string { + const sdk = header.sdk as { name?: string } | undefined; + const name = sdk?.name ?? ""; + if (MOBILE_MARKERS.some((m) => name.includes(m))) { + return magenta("mobile"); + } + if ( + name.startsWith("sentry.javascript.") && + !SERVER_JS_MARKERS.some((m) => name.includes(m)) + ) { + return yellow("browser"); + } + return cyan("server"); +} + +/** Shape of a single stack frame in the exception value. */ +type StackFrame = { + filename?: string; + lineno?: number; + colno?: number; + function?: string; + in_app?: boolean; +}; + +/** Build the `[file:line:col] [func]` suffix for the best stack frame. */ +function formatFrameHint(frames: StackFrame[]): string { + const frame = frames.find((f) => f.in_app) ?? frames.at(-1); + if (!frame) { + return ""; + } + let hint = ""; + if (frame.filename && frame.lineno) { + const loc = frame.colno + ? `${frame.filename}:${frame.lineno}:${frame.colno}` + : `${frame.filename}:${frame.lineno}`; + hint += ` ${muted(`[${loc}]`)}`; + } + if (frame.function) { + hint += ` ${muted(`[${frame.function}]`)}`; + } + return hint; +} + +/** + * Format an error event item into a colored one-liner. + * + * Output: `HH:MM:SS error server TypeError: x is not a function [file.ts:42:5] [handleRequest]` + */ +function formatErrorItem( + event: Record, + header: Record +): string { + const exception = event.exception as + | { + values?: { + type?: string; + value?: string; + stacktrace?: { frames?: StackFrame[] }; + }[]; + } + | undefined; + const first = exception?.values?.[0]; + const errorType = first?.type ?? "Error"; + const errorValue = + first?.value ?? (event.message as string | undefined) ?? "Unknown error"; + + let msg = `${errorType}: ${errorValue}`; + + const frames = first?.stacktrace?.frames; + if (frames?.length) { + msg += formatFrameHint(frames); + } + + const ts = formatTime(event.timestamp as number | undefined); + return `${muted(ts)} ${colorLevel("error")} ${inferSource(header)} ${msg}`; +} + /** - * Resolve the human label for an envelope. + * Format a transaction event item into a colored one-liner. * - * - When the envelope parses cleanly, we use the joined item types - * (e.g. `event+attachment`). - * - Otherwise we fall back to the content type, with a friendly alias - * for the canonical Sentry envelope mime type. + * Output: `HH:MM:SS trace browser [http.client] GET /api/users [245ms] [3 spans]` */ -function describeEnvelope(contentType: string, eventTypes: string[]): string { - if (eventTypes.length > 0) { - return eventTypes.join("+"); +function formatTransactionItem( + event: Record, + header: Record +): string { + const trace = (event.contexts as Record | undefined) + ?.trace as + | { op?: string; status?: string; description?: string } + | undefined; + let msg = + (event.transaction as string) ?? trace?.description ?? "Transaction"; + + const op = trace?.op; + if (op && op !== "default" && op !== "unknown") { + msg = `[${op}] ${msg}`; } - if (contentType === "application/x-sentry-envelope") { - return "envelope"; + + const start = event.start_timestamp as number | undefined; + const end = event.timestamp as number | undefined; + if (start !== undefined && end !== undefined) { + const durationMs = Math.round((end - start) * 1000); + msg += ` ${muted(`[${durationMs}ms]`)}`; } - return contentType; + + const status = trace?.status; + if (status && status !== "ok") { + msg += ` ${muted(`[${status}]`)}`; + } + + const spans = event.spans as unknown[] | undefined; + if (spans?.length) { + msg += ` ${muted(`[${spans.length} span${spans.length === 1 ? "" : "s"}]`)}`; + } + + const ts = formatTime(event.timestamp as number | undefined); + return `${muted(ts)} ${colorLevel("trace")} ${inferSource(header)} ${msg}`; +} + +/** Shape of a single log entry inside a log envelope item. */ +type LogEntry = { + level?: string; + body?: string; + timestamp?: number; + attributes?: Record; +}; + +/** Format one log entry into a colored tail line. */ +function formatSingleLog(logEntry: LogEntry, source: string): string { + const level = logEntry.level ?? "log"; + let msg = logEntry.body ?? ""; + + if (logEntry.attributes) { + const attrs = Object.entries(logEntry.attributes) + .filter( + ([k, v]) => + !k.startsWith("sentry.") && v.value !== null && v.value !== undefined + ) + .map(([k, v]) => `${k}=${v.value}`); + if (attrs.length > 0) { + msg += ` ${muted(`[${attrs.join(", ")}]`)}`; + } + } + + const ts = formatTime(logEntry.timestamp); + return `${muted(ts)} ${colorLevel(level)} ${source} ${msg}`; } /** - * Format a freshly received envelope for terminal output. + * Format a log event item. A log envelope item contains an `items` array + * of individual log entries; each gets its own line. * - * Keeps the formatting deliberately minimal — this is a tail, not a UI. - * If users want rich rendering, they can point the Spotlight overlay at - * `http://localhost:/stream` instead. + * Output: `HH:MM:SS info server User logged in [user_id=1234]` */ -function formatTailLine(contentType: string, eventTypes: string[]): string { - const ts = new Date().toISOString().slice(11, 23); // HH:MM:SS.sss - const label = describeEnvelope(contentType, eventTypes); +function formatLogItem( + event: Record, + header: Record +): string[] { + const items = event.items as LogEntry[] | undefined; + if (!items?.length) { + return []; + } + + const source = inferSource(header); + return items.map((logEntry) => formatSingleLog(logEntry, source)); +} + +/** Item types that map to the error formatter. */ +const ERROR_TYPES = new Set(["event", "error"]); + +/** Produce a fallback one-liner for unparseable or unsupported items. */ +function formatFallbackLine(label: string): string { + const ts = new Date().toISOString().slice(11, 23); return `${muted(ts)} ${cyan("•")} ${bold(label)}`; } +/** Resolve a human label for a completely unparseable envelope. */ +function resolveUnparseableLabel(container: { + getContentType: () => string; + getEventTypes: () => string[] | null; +}): string { + const types = container.getEventTypes(); + if (types && types.length > 0) { + return types.join("+"); + } + const ct = container.getContentType(); + return ct === "application/x-sentry-envelope" ? "envelope" : ct; +} + +/** + * Format a freshly received envelope for terminal output. + * + * For recognized item types (errors, transactions, logs), produces richly + * colored one-liners with context (stack location, span count, duration, + * log attributes, etc.). Items without a dedicated formatter (attachments, + * profiles, sessions, check-ins) fall back to a minimal timestamp + type line. + */ +function formatEnvelopeLines(container: { + getParsedEnvelope: () => { + envelope: [Record, [{ type?: string }, unknown][]]; + } | null; + getContentType: () => string; + getEventTypes: () => string[] | null; +}): string[] { + const parsed = container.getParsedEnvelope(); + if (!parsed) { + return [formatFallbackLine(resolveUnparseableLabel(container))]; + } + + const [header, items] = parsed.envelope; + const lines: string[] = []; + for (const [itemHeader, itemPayload] of items) { + const itemType = itemHeader.type; + const payload = itemPayload as Record; + + if (itemType && ERROR_TYPES.has(itemType)) { + lines.push(formatErrorItem(payload, header)); + } else if (itemType === "transaction") { + lines.push(formatTransactionItem(payload, header)); + } else if (itemType === "log") { + lines.push(...formatLogItem(payload, header)); + } else { + lines.push(formatFallbackLine(itemType ?? container.getContentType())); + } + } + + if (lines.length > 0) { + return lines; + } + return [formatFallbackLine(resolveUnparseableLabel(container))]; +} + /** * Install signal handlers that stop the HTTP server on Ctrl-C / SIGTERM. * @@ -320,15 +580,16 @@ export const localCommand = buildCommand({ async *func(this: SentryContext, flags: LocalFlags) { const buffer = createSpotlightBuffer(BUFFER_SIZE); - // Tail subscriber: prints a one-line summary for each envelope. We - // route through the logger (stderr) rather than stdout so the tail - // doesn't pollute pipelines that consume the CLI's stdout, and so it + // Tail subscriber: pretty-prints each envelope item using Spotlight's + // human formatters. Routes through the logger (stderr) so the tail + // doesn't pollute pipelines that consume the CLI's stdout, and // honors `--log-level` / `SENTRY_LOG_LEVEL` like the rest of the CLI. // Skipped entirely when `--quiet` is set. if (!flags.quiet) { buffer.subscribe((container) => { - const types = container.getEventTypes() ?? []; - log.info(formatTailLine(container.getContentType(), types)); + for (const line of formatEnvelopeLines(container)) { + log.info(line); + } }); } From d030cabba901825f73fb6e65be8aa0811b950320 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Tue, 5 May 2026 20:24:52 +0000 Subject: [PATCH 03/21] feat(local): add --filter/-f flag to show only specific event types Adds a repeatable --filter flag that accepts error, transaction, or log. When set, only matching envelope items are rendered in the tail output; non-matching items are silently dropped. No filter = show everything. Usage: sentry local -f error # errors only sentry local -f error -f log # errors and logs sentry local -f transaction # transactions only --- .../skills/sentry-cli/references/local.md | 1 + src/commands/local.ts | 146 ++++++++++++++---- 2 files changed, 119 insertions(+), 28 deletions(-) diff --git a/plugins/sentry-cli/skills/sentry-cli/references/local.md b/plugins/sentry-cli/skills/sentry-cli/references/local.md index 92ee5ab9a..2f7d40f31 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/local.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/local.md @@ -20,6 +20,7 @@ Run a local Spotlight sidecar to capture dev SDK events - `-H, --host - Hostname to bind to (default localhost) - (default: "localhost")` - `-o, --open - Open the sidecar SSE URL in a browser` - `-q, --quiet - Suppress per-envelope tail output` +- `-f, --filter ... - Only show items of this type (repeatable: error, transaction, log)` **Examples:** diff --git a/src/commands/local.ts b/src/commands/local.ts index c0d0b1d9a..26e48a9db 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -67,11 +67,31 @@ type EventPayload = { data: string; // base64-encoded raw envelope bytes }; +/** Envelope item categories that can be filtered via `--filter`. */ +const FILTER_VALUES = ["error", "transaction", "log"] as const; +type FilterValue = (typeof FILTER_VALUES)[number]; + +/** + * Parse and validate a `--filter` value. + * Accepts the canonical names: error, transaction, log. + */ +function parseFilter(value: string): FilterValue { + const lower = value.toLowerCase(); + if (!FILTER_VALUES.includes(lower as FilterValue)) { + throw new ValidationError( + `Invalid filter "${value}". Valid values: ${FILTER_VALUES.join(", ")}`, + "filter" + ); + } + return lower as FilterValue; +} + type LocalFlags = { readonly port: number; readonly host: string; readonly open: boolean; readonly quiet: boolean; + readonly filter: FilterValue[]; }; /** @@ -431,6 +451,24 @@ function formatLogItem( /** Item types that map to the error formatter. */ const ERROR_TYPES = new Set(["event", "error"]); +/** + * Map envelope item `type` to the corresponding `FilterValue`. + * Returns undefined for item types that don't map to a filter category. + */ +function itemTypeToFilterCategory( + itemType: string | undefined +): FilterValue | undefined { + if (!itemType) { + return; + } + if (ERROR_TYPES.has(itemType)) { + return "error"; + } + if (itemType === "transaction" || itemType === "log") { + return itemType; + } +} + /** Produce a fallback one-liner for unparseable or unsupported items. */ function formatFallbackLine(label: string): string { const ts = new Date().toISOString().slice(11, 23); @@ -450,46 +488,84 @@ function resolveUnparseableLabel(container: { return ct === "application/x-sentry-envelope" ? "envelope" : ct; } +/** Format a single envelope item into one or more output lines. */ +function formatItem( + itemType: string | undefined, + payload: Record, + header: Record, + fallbackLabel: string +): string[] { + if (itemType && ERROR_TYPES.has(itemType)) { + return [formatErrorItem(payload, header)]; + } + if (itemType === "transaction") { + return [formatTransactionItem(payload, header)]; + } + if (itemType === "log") { + return formatLogItem(payload, header); + } + return [formatFallbackLine(fallbackLabel)]; +} + +/** Check whether an item should be shown given active filters. */ +function isItemIncluded( + itemType: string | undefined, + activeFilters: ReadonlySet +): boolean { + if (activeFilters.size === 0) { + return true; + } + const category = itemTypeToFilterCategory(itemType); + return category !== undefined && activeFilters.has(category); +} + /** * Format a freshly received envelope for terminal output. * - * For recognized item types (errors, transactions, logs), produces richly - * colored one-liners with context (stack location, span count, duration, - * log attributes, etc.). Items without a dedicated formatter (attachments, - * profiles, sessions, check-ins) fall back to a minimal timestamp + type line. + * When `activeFilters` is non-empty, only items whose category matches + * one of the filter values are rendered; non-matching items are silently + * dropped. When empty, all items are shown. */ -function formatEnvelopeLines(container: { - getParsedEnvelope: () => { - envelope: [Record, [{ type?: string }, unknown][]]; - } | null; - getContentType: () => string; - getEventTypes: () => string[] | null; -}): string[] { +function formatEnvelopeLines( + container: { + getParsedEnvelope: () => { + envelope: [Record, [{ type?: string }, unknown][]]; + } | null; + getContentType: () => string; + getEventTypes: () => string[] | null; + }, + activeFilters: ReadonlySet +): string[] { const parsed = container.getParsedEnvelope(); if (!parsed) { + if (activeFilters.size > 0) { + return []; + } return [formatFallbackLine(resolveUnparseableLabel(container))]; } const [header, items] = parsed.envelope; const lines: string[] = []; for (const [itemHeader, itemPayload] of items) { - const itemType = itemHeader.type; - const payload = itemPayload as Record; - - if (itemType && ERROR_TYPES.has(itemType)) { - lines.push(formatErrorItem(payload, header)); - } else if (itemType === "transaction") { - lines.push(formatTransactionItem(payload, header)); - } else if (itemType === "log") { - lines.push(...formatLogItem(payload, header)); - } else { - lines.push(formatFallbackLine(itemType ?? container.getContentType())); + if (!isItemIncluded(itemHeader.type, activeFilters)) { + continue; } + lines.push( + ...formatItem( + itemHeader.type, + itemPayload as Record, + header, + itemHeader.type ?? container.getContentType() + ) + ); } if (lines.length > 0) { return lines; } + if (activeFilters.size > 0) { + return []; + } return [formatFallbackLine(resolveUnparseableLabel(container))]; } @@ -567,12 +643,21 @@ export const localCommand = buildCommand({ brief: "Suppress per-envelope tail output", default: false, }, + filter: { + kind: "parsed", + parse: parseFilter, + brief: + "Only show items of this type (repeatable: error, transaction, log)", + variadic: true, + optional: true, + }, }, aliases: { p: "port", H: "host", o: "open", q: "quiet", + f: "filter", }, }, // No auth required — this is a local-only dev server. @@ -580,14 +665,16 @@ export const localCommand = buildCommand({ async *func(this: SentryContext, flags: LocalFlags) { const buffer = createSpotlightBuffer(BUFFER_SIZE); - // Tail subscriber: pretty-prints each envelope item using Spotlight's - // human formatters. Routes through the logger (stderr) so the tail - // doesn't pollute pipelines that consume the CLI's stdout, and - // honors `--log-level` / `SENTRY_LOG_LEVEL` like the rest of the CLI. - // Skipped entirely when `--quiet` is set. + // Build the active filter set once — empty set means "show everything". + const activeFilters: ReadonlySet = new Set(flags.filter); + + // Tail subscriber: pretty-prints each envelope item. Routes through + // the logger (stderr) so the tail doesn't pollute pipelines that + // consume the CLI's stdout, and honors `--log-level` / `SENTRY_LOG_LEVEL` + // like the rest of the CLI. Skipped entirely when `--quiet` is set. if (!flags.quiet) { buffer.subscribe((container) => { - for (const line of formatEnvelopeLines(container)) { + for (const line of formatEnvelopeLines(container, activeFilters)) { log.info(line); } }); @@ -615,6 +702,9 @@ export const localCommand = buildCommand({ log.info( ` ${muted(`SENTRY_DSN=${url.replace("http://", "http://public@")}/1`)}` ); + if (activeFilters.size > 0) { + log.info(`Filtering: ${[...activeFilters].join(", ")}`); + } log.info("Press Ctrl-C to stop."); if (flags.open) { From bda0e68ef3d1f1678de47990a63977fdf8b434a7 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Tue, 5 May 2026 20:59:28 +0000 Subject: [PATCH 04/21] fix(local): fix signal handling, SSE protocol, and browser SDK compat Three fixes based on audit against Spotlight's reference implementation: 1. Signal handling: process.once -> process.on so the 'second signal = force exit' code path is reachable (process.once unregisters after the first signal, making the shuttingDown check dead code). 2. SSE format: match the Spotlight protocol so the overlay UI works. - event name is the content type (not 'envelope') - id field is the Spotlight-assigned envelope UUID - data is the parsed envelope JSON (not base64-encoded raw bytes) - Last-Event-ID reconnection is now supported 3. Browser SDK: detect sendBeacon() payloads (Content-Type: text/plain with sentry_client query param) and override to the canonical application/x-sentry-envelope, matching Spotlight's workaround. --- src/commands/local.ts | 49 ++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 26e48a9db..228d724d7 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -61,11 +61,8 @@ const DEFAULT_PORT = 8969; /** Buffer size: how many recent envelopes to retain for late subscribers. */ const BUFFER_SIZE = 500; -/** SSE event payload — what we send to GET /stream subscribers. */ -type EventPayload = { - contentType: string; - data: string; // base64-encoded raw envelope bytes -}; +/** Canonical content type for Sentry envelopes. */ +const SENTRY_CONTENT_TYPE = "application/x-sentry-envelope"; /** Envelope item categories that can be filtered via `--filter`. */ const FILTER_VALUES = ["error", "transaction", "log"] as const; @@ -150,12 +147,22 @@ function buildSidecarApp( req: { arrayBuffer: () => Promise; header: (name: string) => string | undefined; + query: (name: string) => string | undefined; }; body: (data: null, status: number) => Response; }) => { const arrayBuf = await c.req.arrayBuffer(); const body = Buffer.from(arrayBuf); - const contentType = c.req.header("content-type") ?? ""; + // Browser SDKs using sendBeacon() set Content-Type to text/plain to + // avoid CORS preflight. Detect this via the sentry_client query param + // and override to the canonical Sentry envelope content type. + let contentType = c.req.header("content-type") ?? ""; + if ( + c.req.query("sentry_client")?.startsWith("sentry.javascript.browser") && + c.req.header("origin") + ) { + contentType = SENTRY_CONTENT_TYPE; + } const contentEncoding = c.req.header("content-encoding") as | "gzip" | "deflate" @@ -189,22 +196,26 @@ function buildSidecarApp( /** * SSE stream — Spotlight overlay / UI clients connect here to receive a - * live feed of envelopes. Each event is emitted as a JSON object with - * the content type and base64-encoded body. + * live feed of envelopes. The event format matches Spotlight's protocol: + * - `event` is the content type (e.g., "application/x-sentry-envelope") + * - `id` is the Spotlight-assigned envelope UUID (enables reconnection) + * - `data` is the parsed envelope JSON ([header, items]) */ app.get("/stream", (c) => streamSSE(c, async (stream) => { - // Tie the subscriber lifetime to the response stream. We unsubscribe - // when the client disconnects so the buffer doesn't leak readers. + const lastEventId = c.req.header("Last-Event-ID"); const readerId = spotlightBuffer.subscribe((container) => { - const payload: EventPayload = { - contentType: container.getContentType(), - data: container.getData().toString("base64"), - }; + const parsed = container.getParsedEnvelope(); + if (!parsed) { + return; + } + const header = parsed.envelope[0] as Record; + const envelopeId = header.__spotlight_envelope_id; stream .writeSSE({ - event: "envelope", - data: JSON.stringify(payload), + id: envelopeId ? String(envelopeId) : undefined, + event: container.getContentType(), + data: JSON.stringify(parsed.envelope), }) .catch((err: unknown) => { log.debug( @@ -213,7 +224,7 @@ function buildSidecarApp( }` ); }); - }); + }, lastEventId); stream.onAbort(() => { spotlightBuffer.unsubscribe(readerId); @@ -594,8 +605,8 @@ function waitForShutdown(server: Server): Promise { } }; - process.once("SIGINT", () => shutdown("SIGINT")); - process.once("SIGTERM", () => shutdown("SIGTERM")); + process.on("SIGINT", () => shutdown("SIGINT")); + process.on("SIGTERM", () => shutdown("SIGTERM")); }); } From 935b0604024b37e95a5f389b384e50ba912a3df5 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Tue, 5 May 2026 21:29:02 +0000 Subject: [PATCH 05/21] refactor(local): remove --open flag and update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --open flag opened the raw SSE endpoint in a browser, which just shows streaming text — not useful without the Spotlight overlay UI. Removed it and updated the fragment docs to document the new pretty-print tail output and --filter flag instead. --- docs/src/fragments/commands/local.md | 20 +++++++++++++++----- src/commands/local.ts | 13 ------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/docs/src/fragments/commands/local.md b/docs/src/fragments/commands/local.md index ea78aac95..5275d58f5 100644 --- a/docs/src/fragments/commands/local.md +++ b/docs/src/fragments/commands/local.md @@ -16,8 +16,8 @@ sentry local --port 9000 --host 0.0.0.0 # Run quietly (suppress per-envelope tail output) sentry local --quiet -# Open the SSE endpoint in a browser on startup -sentry local --open +# Only show errors and logs (filter out transactions) +sentry local -f error -f log ``` ## Endpoints @@ -41,10 +41,20 @@ Or configure your SDK's transport explicitly to send envelopes to `http://localh ## Tail output -By default, every envelope received is logged as a single line: +By default, incoming envelopes are pretty-printed to the terminal: ``` -14:32:01.456 • event+attachment +14:32:01 error server TypeError: x is not a function [app.ts:42:5] [handleRequest] +14:32:02 trace browser [http.client] GET /api/users [245ms] [3 spans] +14:32:03 info server User logged in [user_id=1234] ``` -The label is the joined list of envelope item types (`event`, `transaction`, `log`, `attachment`, etc.). Use `--quiet` to suppress this output if you only need the SSE stream for the Spotlight overlay. +Errors show the exception type, message, and top stack frame. Transactions show the operation, duration, and span count. Logs show the severity level, message, and custom attributes. + +Use `--filter` / `-f` to narrow the output to specific event types (repeatable): + +```bash +sentry local -f error -f log # only errors and logs +``` + +Use `--quiet` to suppress tail output entirely if you only need the SSE stream for the Spotlight overlay. diff --git a/src/commands/local.ts b/src/commands/local.ts index 228d724d7..32c6521a6 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -39,7 +39,6 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { streamSSE } from "hono/streaming"; import type { SentryContext } from "../context.js"; -import { openOrShowUrl } from "../lib/browser.js"; import { buildCommand, numberParser } from "../lib/command.js"; import { ValidationError } from "../lib/errors.js"; import { @@ -86,7 +85,6 @@ function parseFilter(value: string): FilterValue { type LocalFlags = { readonly port: number; readonly host: string; - readonly open: boolean; readonly quiet: boolean; readonly filter: FilterValue[]; }; @@ -644,11 +642,6 @@ export const localCommand = buildCommand({ brief: "Hostname to bind to (default localhost)", default: "localhost", }, - open: { - kind: "boolean", - brief: "Open the sidecar SSE URL in a browser", - default: false, - }, quiet: { kind: "boolean", brief: "Suppress per-envelope tail output", @@ -666,7 +659,6 @@ export const localCommand = buildCommand({ aliases: { p: "port", H: "host", - o: "open", q: "quiet", f: "filter", }, @@ -718,11 +710,6 @@ export const localCommand = buildCommand({ } log.info("Press Ctrl-C to stop."); - if (flags.open) { - // Best-effort — never blocks shutdown. - await openOrShowUrl(`${url}/stream`); - } - // Block until the user interrupts. We don't yield any CommandOutput // because there's no structured payload — this command is a server. await waitForShutdown(server); From 0afaf5a001e672f65071b348ae3a2f90fa4a169d Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 09:50:42 +0000 Subject: [PATCH 06/21] fix(local): clean up startup banner, add port auto-retry - Remove logger tag so 'local ' no longer clutters every line - Remove 'Spotlight sidecar' wording, use 'Listening on ' instead - Remove endpoint listing and DSN instructions from banner - Add Spotlight docs link for getting started - Auto-increment port on EADDRINUSE (up to 10 attempts) --- docs/src/content/docs/contributing.md | 2 +- docs/src/fragments/commands/local.md | 18 +-- plugins/sentry-cli/skills/sentry-cli/SKILL.md | 4 +- .../skills/sentry-cli/references/local.md | 10 +- src/commands/local.ts | 106 ++++++++++++------ 5 files changed, 86 insertions(+), 54 deletions(-) diff --git a/docs/src/content/docs/contributing.md b/docs/src/content/docs/contributing.md index 700fcbfd3..0e2c2f5b5 100644 --- a/docs/src/content/docs/contributing.md +++ b/docs/src/content/docs/contributing.md @@ -71,7 +71,7 @@ cli/ │ │ ├── explore.ts # Query aggregate event data (Explore) │ │ ├── help.ts # Help command │ │ ├── init.ts # Initialize Sentry in your project (experimental) -│ │ ├── local.ts # Run a local Spotlight sidecar to capture dev SDK events +│ │ ├── local.ts # Run a local Spotlight server to capture dev SDK events │ │ └── schema.ts # Browse the Sentry API schema │ ├── lib/ # Shared utilities │ └── types/ # TypeScript types and Zod schemas diff --git a/docs/src/fragments/commands/local.md b/docs/src/fragments/commands/local.md index 5275d58f5..70f30faf9 100644 --- a/docs/src/fragments/commands/local.md +++ b/docs/src/fragments/commands/local.md @@ -1,13 +1,15 @@ -[Spotlight](https://spotlightjs.com) is "Sentry for Development" — a lightweight local proxy that ingests Sentry envelopes from SDKs running in your dev stack and surfaces them in real time. `sentry local` runs a minimal [Hono](https://hono.dev/) HTTP server that's wire-compatible with Spotlight's sidecar protocol, so your existing SDKs and the [Spotlight overlay](https://spotlightjs.com/about/) work without any changes. +[Spotlight](https://spotlightjs.com) is "Sentry for Development" — a lightweight local proxy that ingests Sentry envelopes from SDKs running in your dev stack and surfaces them in real time. `sentry local` runs a minimal [Hono](https://hono.dev/) HTTP server that's wire-compatible with Spotlight's protocol, so your existing SDKs and the [Spotlight overlay](https://spotlightjs.com/about/) work without any changes. -No authentication is required — the sidecar binds to `localhost` by default and is purely a development tool. +No authentication is required — the server binds to `localhost` by default and is purely a development tool. + +Learn more about Spotlight at [spotlightjs.com/docs/getting-started](https://spotlightjs.com/docs/getting-started/). ## Examples ```bash -# Start the sidecar on the default port (8969) +# Start the server on the default port (8969) sentry local # Use a custom port and bind to all interfaces @@ -29,16 +31,6 @@ sentry local -f error -f log | `GET` | `/stream` | Server-Sent Events feed of incoming envelopes | | `GET` | `/health` | Liveness check (returns `OK`) | -## Pointing your SDK at the sidecar - -Set a localhost DSN that resolves to the sidecar's port — the public key and project ID can be any non-empty value because the sidecar accepts everything: - -```bash -SENTRY_DSN=http://public@localhost:8969/1 -``` - -Or configure your SDK's transport explicitly to send envelopes to `http://localhost:8969/stream`. - ## Tail output By default, incoming envelopes are pretty-printed to the terminal: diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index 602a64d25..9254ab07c 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -470,9 +470,9 @@ Initialize Sentry in your project (experimental) ### Local -Run a local Spotlight sidecar to capture dev SDK events +Run a local Spotlight server to capture dev SDK events -- `sentry local` — Run a local Spotlight sidecar to capture dev SDK events +- `sentry local` — Run a local Spotlight server to capture dev SDK events → Full flags and examples: `references/local.md` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/local.md b/plugins/sentry-cli/skills/sentry-cli/references/local.md index 6f1ea1759..4bb11aea8 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/local.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/local.md @@ -1,7 +1,7 @@ --- name: sentry-cli-local version: 0.34.0-dev.0 -description: Run a local Spotlight sidecar to capture dev SDK events +description: Run a local Spotlight server to capture dev SDK events requires: bins: ["sentry"] auth: true @@ -9,11 +9,11 @@ requires: # Local Commands -Run a local Spotlight sidecar to capture dev SDK events +Run a local Spotlight server to capture dev SDK events ### `sentry local` -Run a local Spotlight sidecar to capture dev SDK events +Run a local Spotlight server to capture dev SDK events **Flags:** - `-p, --port - Port to listen on (default 8969) - (default: "8969")` @@ -24,7 +24,7 @@ Run a local Spotlight sidecar to capture dev SDK events **Examples:** ```bash -# Start the sidecar on the default port (8969) +# Start the server on the default port (8969) sentry local # Use a custom port and bind to all interfaces @@ -36,8 +36,6 @@ sentry local --quiet # Only show errors and logs (filter out transactions) sentry local -f error -f log -SENTRY_DSN=http://public@localhost:8969/1 - sentry local -f error -f log # only errors and logs ``` diff --git a/src/commands/local.ts b/src/commands/local.ts index 32c6521a6..565dcfcfa 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -1,15 +1,15 @@ /** * sentry local * - * Run a local Spotlight-compatible sidecar server. + * Run a local Spotlight-compatible server. * - * Spotlight (https://github.com/getsentry/spotlight) is "Sentry for - * Development" — a small local proxy that ingests Sentry envelopes from - * SDKs running in your dev stack and surfaces them in real time. + * Spotlight (https://spotlightjs.com/) is "Sentry for Development" — a small + * local proxy that ingests Sentry envelopes from SDKs running in your dev + * stack and surfaces them in real time. * * This command starts a minimal Hono HTTP server that: * - * 1. Accepts envelopes from Sentry SDKs at the standard sidecar endpoints: + * 1. Accepts envelopes from Sentry SDKs at the standard endpoints: * - `POST /stream` (Spotlight-compatible) * - `POST /api/{projectId}/envelope/` (Sentry SDK ingest path) * 2. Pushes them into the buffer provided by `@spotlightjs/spotlight/sdk`, @@ -19,12 +19,7 @@ * 4. Tails events to the terminal as they arrive so you can see what your * app is sending without leaving the CLI. * - * To point your SDK at the local sidecar, use a placeholder DSN that - * resolves to localhost — for example: - * - * SENTRY_DSN=http://public@localhost:8969/1 - * - * Or configure your SDK's transport to send to `http://localhost:8969/stream`. + * Learn more: https://spotlightjs.com/docs/getting-started/ * * The command runs until interrupted (Ctrl-C / SIGTERM). */ @@ -52,7 +47,7 @@ import { } from "../lib/formatters/colors.js"; import { logger } from "../lib/logger.js"; -const log = logger.withTag("local"); +const log = logger; /** Default port matches Spotlight's `DEFAULT_PORT`. */ const DEFAULT_PORT = 8969; @@ -608,11 +603,64 @@ function waitForShutdown(server: Server): Promise { }); } +/** Maximum number of consecutive ports to try before giving up. */ +const MAX_PORT_ATTEMPTS = 10; + +/** + * Try to start the HTTP server, auto-incrementing the port on EADDRINUSE. + * + * `@hono/node-server`'s `serve()` calls `server.listen()` synchronously and + * returns immediately — the actual bind happens asynchronously. We wrap it in + * a Promise that resolves on the `listening` event and rejects on `error`. + * When the port is busy we bump the port number and retry up to + * {@link MAX_PORT_ATTEMPTS} times, warning the user on each bump. + */ +function tryListen( + app: Hono, + startPort: number, + hostname: string +): Promise<{ server: Server; port: number }> { + let port = startPort; + let attempts = 0; + + const attempt = (): Promise<{ server: Server; port: number }> => + new Promise((resolve, reject) => { + const server = serve({ + fetch: app.fetch, + port, + hostname, + }) as unknown as Server; + + server.once("listening", () => resolve({ server, port })); + server.once("error", (err: NodeJS.ErrnoException) => { + if (err.code === "EADDRINUSE") { + attempts += 1; + if (attempts >= MAX_PORT_ATTEMPTS) { + reject( + new ValidationError( + `Port ${startPort} is in use and no open port found after ${MAX_PORT_ATTEMPTS} attempts`, + "port" + ) + ); + return; + } + log.warn(`Port ${port} is in use, trying ${port + 1}...`); + port += 1; + resolve(attempt()); + return; + } + reject(err); + }); + }); + + return attempt(); +} + export const localCommand = buildCommand({ docs: { - brief: "Run a local Spotlight sidecar to capture dev SDK events", + brief: "Run a local Spotlight server to capture dev SDK events", fullDescription: - "Start a local Spotlight-compatible sidecar server.\n\n" + + "Start a local Spotlight-compatible server.\n\n" + "Spotlight is Sentry for Development — it gives you a live view of\n" + "errors, traces, and logs emitted by Sentry SDKs in your dev stack.\n" + "This command runs a minimal Hono server that ingests envelopes\n" + @@ -622,8 +670,7 @@ export const localCommand = buildCommand({ " POST /api/{projectId}/envelope/ — Sentry SDK ingest\n" + " GET /stream — SSE feed (for the Spotlight overlay)\n" + " GET /health — health check\n\n" + - "Configure your SDK to send to the sidecar with a localhost DSN, e.g.:\n" + - " SENTRY_DSN=http://public@localhost:8969/1\n\n" + + "Learn more: https://spotlightjs.com/docs/getting-started/\n\n" + "Press Ctrl-C to stop the server.", }, // No `output` config: this is a long-running server, not a data command. @@ -689,30 +736,25 @@ export const localCommand = buildCommand({ // now; future hooks (e.g. metrics, file logging) can plug in here. }); - // `serve` returns a Node http.Server — we use it for graceful shutdown. - const server = serve({ - fetch: app.fetch, - port: flags.port, - hostname: flags.host, - }) as unknown as Server; - - const url = `http://${flags.host}:${flags.port}`; - log.info(`Spotlight sidecar listening on ${bold(url)}`); - log.info(` ${muted("Ingest:")} POST ${url}/stream`); - log.info(` ${muted("Stream:")} GET ${url}/stream`); - log.info(` ${muted("Health:")} GET ${url}/health`); - log.info(`Point your SDK at ${bold(`${url}/stream`)} or use a DSN like:`); - log.info( - ` ${muted(`SENTRY_DSN=${url.replace("http://", "http://public@")}/1`)}` + const { server, port: boundPort } = await tryListen( + app, + flags.port, + flags.host ); + + const url = `http://${flags.host}:${boundPort}`; + log.info(`Listening on ${bold(url)}`); if (activeFilters.size > 0) { log.info(`Filtering: ${[...activeFilters].join(", ")}`); } + log.info( + `Learn more about Spotlight: ${bold("https://spotlightjs.com/docs/getting-started/")}` + ); log.info("Press Ctrl-C to stop."); // Block until the user interrupts. We don't yield any CommandOutput // because there's no structured payload — this command is a server. await waitForShutdown(server); - log.info("Sidecar stopped."); + log.info("Server stopped."); }, }); From d0e70ad756f9dab64487074c29e278ba67d98771 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:00:16 +0000 Subject: [PATCH 07/21] chore(local): remove code slop from local.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove pointless `log = logger` alias; use `logger` directly - Strip narrating comments that restate the code - Rename buildSidecarApp → buildApp; drop remaining 'sidecar' references - Make onEnvelope callback optional instead of passing noop - Remove redundant type annotation on activeFilters --- src/commands/local.ts | 72 +++++++++++-------------------------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 565dcfcfa..26a79776b 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -47,8 +47,6 @@ import { } from "../lib/formatters/colors.js"; import { logger } from "../lib/logger.js"; -const log = logger; - /** Default port matches Spotlight's `DEFAULT_PORT`. */ const DEFAULT_PORT = 8969; @@ -64,7 +62,7 @@ type FilterValue = (typeof FILTER_VALUES)[number]; /** * Parse and validate a `--filter` value. - * Accepts the canonical names: error, transaction, log. + * Accepts the canonical names: error, transaction, logger. */ function parseFilter(value: string): FilterValue { const lower = value.toLowerCase(); @@ -102,27 +100,17 @@ function parsePort(value: string): number { } /** - * Build the Hono application that backs the sidecar. + * Build the Hono application. * - * We expose three concerns: - * - CORS: open to `*` because dev stacks send from arbitrary `localhost:*` - * origins (Vite, Next, Astro, etc.). The sidecar binds to localhost by - * default, so this isn't a security regression. - * - Ingest: `POST /stream` and `POST /api/.../envelope/` accept envelope - * bodies. We hand the raw buffer to `pushToSpotlightBuffer`, which - * decompresses (gzip/deflate/br) and decodes lazily. - * - Subscribe: `GET /stream` opens an SSE stream of every envelope that - * enters the buffer, including those buffered before the subscriber - * connected (so a freshly-opened Spotlight overlay can still see - * recent events). + * CORS is open to `*` because dev stacks send from arbitrary `localhost:*` + * origins (Vite, Next, Astro, etc.) and we only bind to localhost. */ -function buildSidecarApp( +function buildApp( spotlightBuffer: ReturnType, - onEnvelope: (contentType: string, data: Buffer) => void + onEnvelope?: (contentType: string, data: Buffer) => void ): Hono { const app = new Hono(); - // Open CORS — sidecar binds to localhost; this is a dev-only tool. app.use( "*", cors({ @@ -132,10 +120,8 @@ function buildSidecarApp( }) ); - /** Health check — useful for `curl` and for SDKs that probe before sending. */ app.get("/health", (c) => c.text("OK")); - /** Ingest handler shared by `/stream` and `/api/.../envelope/`. */ const ingest = async (c: { req: { arrayBuffer: () => Promise; @@ -172,18 +158,13 @@ function buildSidecarApp( }); if (container) { - // Surface the decoded payload to the tail/subscribe pipeline. We push - // the (potentially decompressed) raw body so SSE subscribers don't - // have to redo the work and so the tail formatter can rely on a - // single representation. - onEnvelope(container.getContentType(), container.getData()); + onEnvelope?.(container.getContentType(), container.getData()); } return c.body(null, 204); }; app.post("/stream", ingest); - // SDK-style envelope ingestion: /api/{projectId}/envelope/?... app.post("/api/:projectId/envelope/", ingest); app.post("/api/:projectId/envelope", ingest); @@ -211,7 +192,7 @@ function buildSidecarApp( data: JSON.stringify(parsed.envelope), }) .catch((err: unknown) => { - log.debug( + logger.debug( `SSE write failed (client likely disconnected): ${ err instanceof Error ? err.message : String(err) }` @@ -223,8 +204,6 @@ function buildSidecarApp( spotlightBuffer.unsubscribe(readerId); }); - // Keep the stream open until the client disconnects. - // hono/streaming resolves the promise on abort. await new Promise((resolve) => { stream.onAbort(() => resolve()); }); @@ -589,7 +568,7 @@ function waitForShutdown(server: Server): Promise { process.exit(0); } shuttingDown = true; - log.info(`Received ${signal}, shutting down...`); + logger.info(`Received ${signal}, shutting down...`); server.close(() => resolve()); // Force-close keep-alive connections so we don't wait on long-lived // SSE subscribers. @@ -644,7 +623,7 @@ function tryListen( ); return; } - log.warn(`Port ${port} is in use, trying ${port + 1}...`); + logger.warn(`Port ${port} is in use, trying ${port + 1}...`); port += 1; resolve(attempt()); return; @@ -673,8 +652,6 @@ export const localCommand = buildCommand({ "Learn more: https://spotlightjs.com/docs/getting-started/\n\n" + "Press Ctrl-C to stop the server.", }, - // No `output` config: this is a long-running server, not a data command. - // We write progress directly to stderr via the logger. parameters: { flags: { port: { @@ -710,31 +687,20 @@ export const localCommand = buildCommand({ f: "filter", }, }, - // No auth required — this is a local-only dev server. auth: false, async *func(this: SentryContext, flags: LocalFlags) { const buffer = createSpotlightBuffer(BUFFER_SIZE); + const activeFilters = new Set(flags.filter); - // Build the active filter set once — empty set means "show everything". - const activeFilters: ReadonlySet = new Set(flags.filter); - - // Tail subscriber: pretty-prints each envelope item. Routes through - // the logger (stderr) so the tail doesn't pollute pipelines that - // consume the CLI's stdout, and honors `--log-level` / `SENTRY_LOG_LEVEL` - // like the rest of the CLI. Skipped entirely when `--quiet` is set. if (!flags.quiet) { buffer.subscribe((container) => { for (const line of formatEnvelopeLines(container, activeFilters)) { - log.info(line); + logger.info(line); } }); } - const app = buildSidecarApp(buffer, () => { - // Tail output is driven by the buffer subscriber above so we don't - // have to repeat the formatting work. This callback is a no-op for - // now; future hooks (e.g. metrics, file logging) can plug in here. - }); + const app = buildApp(buffer); const { server, port: boundPort } = await tryListen( app, @@ -743,18 +709,16 @@ export const localCommand = buildCommand({ ); const url = `http://${flags.host}:${boundPort}`; - log.info(`Listening on ${bold(url)}`); + logger.info(`Listening on ${bold(url)}`); if (activeFilters.size > 0) { - log.info(`Filtering: ${[...activeFilters].join(", ")}`); + logger.info(`Filtering: ${[...activeFilters].join(", ")}`); } - log.info( + logger.info( `Learn more about Spotlight: ${bold("https://spotlightjs.com/docs/getting-started/")}` ); - log.info("Press Ctrl-C to stop."); + logger.info("Press Ctrl-C to stop."); - // Block until the user interrupts. We don't yield any CommandOutput - // because there's no structured payload — this command is a server. await waitForShutdown(server); - log.info("Server stopped."); + logger.info("Server stopped."); }, }); From 736985fefd04c9d72966df2d60120ce0f1313e59 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:11:37 +0000 Subject: [PATCH 08/21] fix(local): use logger.log for tail output to drop icon prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Startup banner keeps logger.info (shows ℹ icon), while tail output and shutdown messages use logger.log (no icon prefix). --- src/commands/local.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 26a79776b..ffb73ad5b 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -568,7 +568,7 @@ function waitForShutdown(server: Server): Promise { process.exit(0); } shuttingDown = true; - logger.info(`Received ${signal}, shutting down...`); + logger.log(`Received ${signal}, shutting down...`); server.close(() => resolve()); // Force-close keep-alive connections so we don't wait on long-lived // SSE subscribers. @@ -695,7 +695,7 @@ export const localCommand = buildCommand({ if (!flags.quiet) { buffer.subscribe((container) => { for (const line of formatEnvelopeLines(container, activeFilters)) { - logger.info(line); + logger.log(line); } }); } @@ -719,6 +719,6 @@ export const localCommand = buildCommand({ logger.info("Press Ctrl-C to stop."); await waitForShutdown(server); - logger.info("Server stopped."); + logger.log("Server stopped."); }, }); From 51d1538853beda057a3ef6746fe6c6f73fb54040 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:15:54 +0000 Subject: [PATCH 09/21] fix(local): restrict CORS to localhost origins only The wildcard origin allowed any webpage to connect to the SSE stream and exfiltrate envelope data. Restrict to localhost/127.0.0.1 origins which is sufficient for local dev stacks (Vite, Next, Astro, etc.). --- src/commands/local.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index ffb73ad5b..4b7bc10e6 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -99,11 +99,15 @@ function parsePort(value: string): number { return port; } +/** Match localhost origins on any port (http or https). */ +const LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/; + /** * Build the Hono application. * - * CORS is open to `*` because dev stacks send from arbitrary `localhost:*` - * origins (Vite, Next, Astro, etc.) and we only bind to localhost. + * CORS is restricted to localhost origins — dev stacks send from arbitrary + * `localhost:*` ports (Vite, Next, Astro, etc.) but we must not allow + * arbitrary remote origins to read the SSE envelope stream. */ function buildApp( spotlightBuffer: ReturnType, @@ -114,7 +118,8 @@ function buildApp( app.use( "*", cors({ - origin: "*", + origin: (origin) => + LOCALHOST_ORIGIN_RE.test(origin) ? origin : "http://localhost", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["Content-Type", "Content-Encoding", "User-Agent"], }) From 5d69091ce6a7b887d83df7600f62397438f4dd0d Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:15:09 +0000 Subject: [PATCH 10/21] fix(local): address Warden security findings - Fix subscription leak: merge dual stream.onAbort() into one callback so unsubscribe and promise resolution both fire on disconnect - Sanitize envelope content with stripAnsi() before rendering to terminal to prevent ANSI escape injection from crafted payloads - Add 10 MB body size guard on ingest to reject oversized payloads (returns 413) --- src/commands/local.ts | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 4b7bc10e6..d4960270a 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -45,6 +45,7 @@ import { red, yellow, } from "../lib/formatters/colors.js"; +import { stripAnsi } from "../lib/formatters/plain-detect.js"; import { logger } from "../lib/logger.js"; /** Default port matches Spotlight's `DEFAULT_PORT`. */ @@ -56,6 +57,9 @@ const BUFFER_SIZE = 500; /** Canonical content type for Sentry envelopes. */ const SENTRY_CONTENT_TYPE = "application/x-sentry-envelope"; +/** Maximum ingest body size (10 MB). Rejects oversized payloads early. */ +const MAX_BODY_BYTES = 10 * 1024 * 1024; + /** Envelope item categories that can be filtered via `--filter`. */ const FILTER_VALUES = ["error", "transaction", "log"] as const; type FilterValue = (typeof FILTER_VALUES)[number]; @@ -135,7 +139,14 @@ function buildApp( }; body: (data: null, status: number) => Response; }) => { + const contentLength = Number(c.req.header("content-length") ?? 0); + if (contentLength > MAX_BODY_BYTES) { + return c.body(null, 413); + } const arrayBuf = await c.req.arrayBuffer(); + if (arrayBuf.byteLength > MAX_BODY_BYTES) { + return c.body(null, 413); + } const body = Buffer.from(arrayBuf); // Browser SDKs using sendBeacon() set Content-Type to text/plain to // avoid CORS preflight. Detect this via the sentry_client query param @@ -205,12 +216,11 @@ function buildApp( }); }, lastEventId); - stream.onAbort(() => { - spotlightBuffer.unsubscribe(readerId); - }); - await new Promise((resolve) => { - stream.onAbort(() => resolve()); + stream.onAbort(() => { + spotlightBuffer.unsubscribe(readerId); + resolve(); + }); }); }) ); @@ -302,12 +312,12 @@ function formatFrameHint(frames: StackFrame[]): string { let hint = ""; if (frame.filename && frame.lineno) { const loc = frame.colno - ? `${frame.filename}:${frame.lineno}:${frame.colno}` - : `${frame.filename}:${frame.lineno}`; + ? `${stripAnsi(frame.filename)}:${frame.lineno}:${frame.colno}` + : `${stripAnsi(frame.filename)}:${frame.lineno}`; hint += ` ${muted(`[${loc}]`)}`; } if (frame.function) { - hint += ` ${muted(`[${frame.function}]`)}`; + hint += ` ${muted(`[${stripAnsi(frame.function)}]`)}`; } return hint; } @@ -331,9 +341,10 @@ function formatErrorItem( } | undefined; const first = exception?.values?.[0]; - const errorType = first?.type ?? "Error"; - const errorValue = - first?.value ?? (event.message as string | undefined) ?? "Unknown error"; + const errorType = stripAnsi(first?.type ?? "Error"); + const errorValue = stripAnsi( + first?.value ?? (event.message as string | undefined) ?? "Unknown error" + ); let msg = `${errorType}: ${errorValue}`; @@ -359,8 +370,9 @@ function formatTransactionItem( ?.trace as | { op?: string; status?: string; description?: string } | undefined; - let msg = - (event.transaction as string) ?? trace?.description ?? "Transaction"; + let msg = stripAnsi( + (event.transaction as string) ?? trace?.description ?? "Transaction" + ); const op = trace?.op; if (op && op !== "default" && op !== "unknown") { @@ -399,7 +411,7 @@ type LogEntry = { /** Format one log entry into a colored tail line. */ function formatSingleLog(logEntry: LogEntry, source: string): string { const level = logEntry.level ?? "log"; - let msg = logEntry.body ?? ""; + let msg = stripAnsi(logEntry.body ?? ""); if (logEntry.attributes) { const attrs = Object.entries(logEntry.attributes) @@ -407,7 +419,7 @@ function formatSingleLog(logEntry: LogEntry, source: string): string { ([k, v]) => !k.startsWith("sentry.") && v.value !== null && v.value !== undefined ) - .map(([k, v]) => `${k}=${v.value}`); + .map(([k, v]) => `${stripAnsi(k)}=${stripAnsi(String(v.value))}`); if (attrs.length > 0) { msg += ` ${muted(`[${attrs.join(", ")}]`)}`; } From 6676aea64071258fd2b314b7f800adba86f0db37 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:24:33 +0000 Subject: [PATCH 11/21] feat(local): align tail formatting with Spotlight's human formatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Type labels: uppercase, bracketed, padded — [ERROR] [TRACE] [INFO] - Source labels: uppercase, bracketed, padded — [SERVER] [BROWSER] [MOBILE] - Source colors: match Spotlight Sentinel theme (mobile=blue) - Log attributes: per-attribute brackets [key=value] [key=value] - Update docs fragment example output to match --- docs/src/fragments/commands/local.md | 6 +-- src/commands/local.ts | 57 ++++++++++++++++++---------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/docs/src/fragments/commands/local.md b/docs/src/fragments/commands/local.md index 70f30faf9..e56b4b104 100644 --- a/docs/src/fragments/commands/local.md +++ b/docs/src/fragments/commands/local.md @@ -36,9 +36,9 @@ sentry local -f error -f log By default, incoming envelopes are pretty-printed to the terminal: ``` -14:32:01 error server TypeError: x is not a function [app.ts:42:5] [handleRequest] -14:32:02 trace browser [http.client] GET /api/users [245ms] [3 spans] -14:32:03 info server User logged in [user_id=1234] +14:32:01 [ERROR] [SERVER] TypeError: x is not a function [app.ts:42:5] [handleRequest] +14:32:02 [TRACE] [BROWSER] [http.client] GET /api/users [245ms] [3 spans] +14:32:03 [INFO] [SERVER] User logged in [user_id=1234] [region=us] ``` Errors show the exception type, message, and top stack frame. Transactions show the operation, duration, and span count. Logs show the severity level, message, and custom attributes. diff --git a/src/commands/local.ts b/src/commands/local.ts index d4960270a..3a1293ead 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -37,10 +37,10 @@ import type { SentryContext } from "../context.js"; import { buildCommand, numberParser } from "../lib/command.js"; import { ValidationError } from "../lib/errors.js"; import { + blue, bold, cyan, green, - magenta, muted, red, yellow, @@ -244,7 +244,7 @@ function formatTime(timestamp?: number | string): string { return date.toLocaleTimeString("en-US", { hour12: false }); } -/** Level → color map for tail output. */ +/** Level → color map for tail output, matching Spotlight's Sentinel theme. */ const LEVEL_COLORS: Record string> = { error: (s) => red(bold(s)), fatal: (s) => red(bold(s)), @@ -254,10 +254,18 @@ const LEVEL_COLORS: Record string> = { debug: muted, }; -/** Colorize a log/event level label. */ -function colorLevel(level: string): string { +/** Longest bracketed type label: `[WARNING]` = 9 chars. */ +const TYPE_WIDTH = 9; + +/** Longest bracketed source label: `[BROWSER]` = 9 chars. */ +const SOURCE_WIDTH = 9; + +/** Format a type/level label as `[TYPE]` padded to fixed width. */ +function formatType(level: string): string { + const tag = `[${level.toUpperCase()}]`; const colorFn = LEVEL_COLORS[level]; - return colorFn ? colorFn(level) : level; + const colored = colorFn ? colorFn(tag) : tag; + return colored + " ".repeat(Math.max(0, TYPE_WIDTH - tag.length)); } /** Mobile SDK name substrings. */ @@ -275,23 +283,32 @@ const SERVER_JS_MARKERS = [ "sveltekit", ]; +/** Source color map matching Spotlight's Sentinel theme. */ +const SOURCE_COLORS: Record string> = { + browser: yellow, + mobile: blue, + server: cyan, +}; + /** * Infer the source platform from the envelope header's `sdk.name` field. - * Returns a short colored label like "server", "browser", or "mobile". + * Returns a colored, bracketed, padded label like `[SERVER] `. */ function inferSource(header: Record): string { const sdk = header.sdk as { name?: string } | undefined; const name = sdk?.name ?? ""; + let source = "server"; if (MOBILE_MARKERS.some((m) => name.includes(m))) { - return magenta("mobile"); - } - if ( + source = "mobile"; + } else if ( name.startsWith("sentry.javascript.") && !SERVER_JS_MARKERS.some((m) => name.includes(m)) ) { - return yellow("browser"); + source = "browser"; } - return cyan("server"); + const tag = `[${source.toUpperCase()}]`; + const colorFn = SOURCE_COLORS[source] ?? cyan; + return colorFn(tag) + " ".repeat(Math.max(0, SOURCE_WIDTH - tag.length)); } /** Shape of a single stack frame in the exception value. */ @@ -325,7 +342,7 @@ function formatFrameHint(frames: StackFrame[]): string { /** * Format an error event item into a colored one-liner. * - * Output: `HH:MM:SS error server TypeError: x is not a function [file.ts:42:5] [handleRequest]` + * Output: `HH:MM:SS [ERROR] [SERVER] TypeError: x is not a function [file.ts:42:5] [handleRequest]` */ function formatErrorItem( event: Record, @@ -354,13 +371,13 @@ function formatErrorItem( } const ts = formatTime(event.timestamp as number | undefined); - return `${muted(ts)} ${colorLevel("error")} ${inferSource(header)} ${msg}`; + return `${muted(ts)} ${formatType("error")} ${inferSource(header)} ${msg}`; } /** * Format a transaction event item into a colored one-liner. * - * Output: `HH:MM:SS trace browser [http.client] GET /api/users [245ms] [3 spans]` + * Output: `HH:MM:SS [TRACE] [BROWSER] [http.client] GET /api/users [245ms] [3 spans]` */ function formatTransactionItem( event: Record, @@ -397,7 +414,7 @@ function formatTransactionItem( } const ts = formatTime(event.timestamp as number | undefined); - return `${muted(ts)} ${colorLevel("trace")} ${inferSource(header)} ${msg}`; + return `${muted(ts)} ${formatType("trace")} ${inferSource(header)} ${msg}`; } /** Shape of a single log entry inside a log envelope item. */ @@ -419,21 +436,23 @@ function formatSingleLog(logEntry: LogEntry, source: string): string { ([k, v]) => !k.startsWith("sentry.") && v.value !== null && v.value !== undefined ) - .map(([k, v]) => `${stripAnsi(k)}=${stripAnsi(String(v.value))}`); + .map(([k, v]) => + muted(`[${stripAnsi(k)}=${stripAnsi(String(v.value))}]`) + ); if (attrs.length > 0) { - msg += ` ${muted(`[${attrs.join(", ")}]`)}`; + msg += ` ${attrs.join(" ")}`; } } const ts = formatTime(logEntry.timestamp); - return `${muted(ts)} ${colorLevel(level)} ${source} ${msg}`; + return `${muted(ts)} ${formatType(level)} ${source} ${msg}`; } /** * Format a log event item. A log envelope item contains an `items` array * of individual log entries; each gets its own line. * - * Output: `HH:MM:SS info server User logged in [user_id=1234]` + * Output: `HH:MM:SS [INFO] [SERVER] User logged in [user_id=1234]` */ function formatLogItem( event: Record, From b6e7734c36f8785b74ff6df8ff45b75a493766dc Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:31:28 +0000 Subject: [PATCH 12/21] fix(local): show outermost exception for chained errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit exception.values is ordered oldest→newest per the Sentry protocol, so values[0] is the root cause. Use .at(-1) to display the outermost exception, matching Sentry UI and Spotlight behavior. --- src/commands/local.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 3a1293ead..b82434995 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -357,7 +357,8 @@ function formatErrorItem( }[]; } | undefined; - const first = exception?.values?.[0]; + // values is ordered oldest→newest; show the outermost (last) exception + const first = exception?.values?.at(-1); const errorType = stripAnsi(first?.type ?? "Error"); const errorValue = stripAnsi( first?.value ?? (event.message as string | undefined) ?? "Unknown error" From 32643be2645d46ed9743ca88ceda27bc6907d6b4 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 10:37:11 +0000 Subject: [PATCH 13/21] fix(test): use case-insensitive comparison in colorizeSql property test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @sentry/sqlish uppercases SQL keywords (e.g. "by" → "BY"), so identifiers that happen to match keywords fail the strict equality check. Compare lowercased strings instead. --- test/lib/formatters/sql.property.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/lib/formatters/sql.property.test.ts b/test/lib/formatters/sql.property.test.ts index 9c806eada..3e1f45dd9 100644 --- a/test/lib/formatters/sql.property.test.ts +++ b/test/lib/formatters/sql.property.test.ts @@ -126,13 +126,14 @@ describe("property: isDbSpanOp", () => { }); describe("property: colorizeSql", () => { - test("stripping ANSI preserves original text content", () => { + test("stripping ANSI preserves original text content (case-insensitive)", () => { process.env.SENTRY_PLAIN_OUTPUT = "0"; fcAssert( property(sqlStringArb, (sql) => { const colorized = colorizeSql(sql); const stripped = stripAnsi(colorized); - expect(stripped).toBe(sql); + // Case-insensitive: @sentry/sqlish uppercases SQL keywords (e.g. "by" → "BY") + expect(stripped.toLowerCase()).toBe(sql.toLowerCase()); }), { numRuns: DEFAULT_NUM_RUNS } ); From 16731e3d41db394f785df0d5ba84c7a1715c5a28 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 11:40:22 +0000 Subject: [PATCH 14/21] feat(local): connect to existing server, backoff port retry - On startup, probe the target port for an existing Spotlight server. If one is running, attach as an SSE consumer instead of starting a duplicate server. Uses fetch-based SSE parsing since Bun lacks global EventSource. - Last-Event-ID reconnection already supported via the Spotlight SDK's subscribe(callback, lastEventId) parameter. - Port retry now uses 3 retries with 5s backoff (matching Spotlight) instead of 10 sequential port increments. --- src/commands/local.ts | 210 +++++++++++++++++++++++++++++++++--------- 1 file changed, 166 insertions(+), 44 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index b82434995..c67999a2c 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -1,23 +1,12 @@ /** * sentry local * - * Run a local Spotlight-compatible server. + * Run a local Spotlight-compatible server, or attach to one already running. * - * Spotlight (https://spotlightjs.com/) is "Sentry for Development" — a small - * local proxy that ingests Sentry envelopes from SDKs running in your dev - * stack and surfaces them in real time. - * - * This command starts a minimal Hono HTTP server that: - * - * 1. Accepts envelopes from Sentry SDKs at the standard endpoints: - * - `POST /stream` (Spotlight-compatible) - * - `POST /api/{projectId}/envelope/` (Sentry SDK ingest path) - * 2. Pushes them into the buffer provided by `@spotlightjs/spotlight/sdk`, - * which lazily parses each envelope. - * 3. Streams new envelopes back to subscribers via Server-Sent Events at - * `GET /stream` — compatible with the Spotlight overlay/UI. - * 4. Tails events to the terminal as they arrive so you can see what your - * app is sending without leaving the CLI. + * On startup the command probes `http://:/health`. If a server + * is already listening (e.g. a Spotlight sidecar or another `sentry local`), + * the command attaches as an SSE consumer and tails events from it. Otherwise + * it starts its own Hono HTTP server. * * Learn more: https://spotlightjs.com/docs/getting-started/ * @@ -57,6 +46,9 @@ const BUFFER_SIZE = 500; /** Canonical content type for Sentry envelopes. */ const SENTRY_CONTENT_TYPE = "application/x-sentry-envelope"; +/** Trailing carriage return — stripped from SSE lines. */ +const CR_RE = /\r$/; + /** Maximum ingest body size (10 MB). Rejects oversized payloads early. */ const MAX_BODY_BYTES = 10 * 1024 * 1024; @@ -619,24 +611,23 @@ function waitForShutdown(server: Server): Promise { }); } -/** Maximum number of consecutive ports to try before giving up. */ -const MAX_PORT_ATTEMPTS = 10; +/** Maximum retries on EADDRINUSE before giving up. */ +const MAX_PORT_RETRIES = 3; + +/** Delay between EADDRINUSE retries in milliseconds. */ +const PORT_RETRY_DELAY_MS = 5000; /** - * Try to start the HTTP server, auto-incrementing the port on EADDRINUSE. + * Try to start the HTTP server, retrying with backoff on EADDRINUSE. * - * `@hono/node-server`'s `serve()` calls `server.listen()` synchronously and - * returns immediately — the actual bind happens asynchronously. We wrap it in - * a Promise that resolves on the `listening` event and rejects on `error`. - * When the port is busy we bump the port number and retry up to - * {@link MAX_PORT_ATTEMPTS} times, warning the user on each bump. + * Retries up to {@link MAX_PORT_RETRIES} times with a {@link PORT_RETRY_DELAY_MS} + * delay between attempts, matching Spotlight's retry strategy. */ function tryListen( app: Hono, - startPort: number, + port: number, hostname: string ): Promise<{ server: Server; port: number }> { - let port = startPort; let attempts = 0; const attempt = (): Promise<{ server: Server; port: number }> => @@ -648,20 +639,22 @@ function tryListen( }) as unknown as Server; server.once("listening", () => resolve({ server, port })); - server.once("error", (err: NodeJS.ErrnoException) => { + server.once("error", async (err: NodeJS.ErrnoException) => { if (err.code === "EADDRINUSE") { attempts += 1; - if (attempts >= MAX_PORT_ATTEMPTS) { + if (attempts > MAX_PORT_RETRIES) { reject( new ValidationError( - `Port ${startPort} is in use and no open port found after ${MAX_PORT_ATTEMPTS} attempts`, + `Port ${port} is in use after ${MAX_PORT_RETRIES} retries`, "port" ) ); return; } - logger.warn(`Port ${port} is in use, trying ${port + 1}...`); - port += 1; + logger.warn( + `Port ${port} is in use, retrying in ${PORT_RETRY_DELAY_MS / 1000}s (attempt ${attempts}/${MAX_PORT_RETRIES})...` + ); + await Bun.sleep(PORT_RETRY_DELAY_MS); resolve(attempt()); return; } @@ -672,22 +665,120 @@ function tryListen( return attempt(); } +/** + * Check whether a Spotlight server is already running on the given URL. + * Returns `true` if the health endpoint responds successfully. + */ +async function isServerRunning(url: string): Promise { + try { + const res = await fetch(`${url}/health`); + return res.ok; + } catch { + return false; + } +} + +/** Mutable state for the SSE line parser. */ +type SSEParserState = { + eventType: string; + dataLines: string[]; +}; + +/** Process a single SSE line, dispatching complete events via callback. */ +function feedSSELine( + line: string, + state: SSEParserState, + onEvent: (type: string, data: string) => void +): void { + if (line.startsWith("event:")) { + state.eventType = line.slice(6).trim(); + } else if (line.startsWith("data:")) { + state.dataLines.push(line.slice(5).trimStart()); + } else if (line === "" && state.dataLines.length > 0) { + onEvent(state.eventType, state.dataLines.join("\n")); + state.eventType = ""; + state.dataLines = []; + } +} + +/** + * Consume SSE events from an upstream Spotlight server and print them. + * + * Bun doesn't have a global `EventSource`, so we use `fetch` with a + * streaming body and parse the SSE wire format manually. + */ +async function consumeSSE( + url: string, + activeFilters: ReadonlySet, + signal: AbortSignal +): Promise { + const res = await fetch(`${url}/stream`, { + headers: { Accept: "text/event-stream" }, + signal, + }); + if (!res.body) { + return; + } + + const decoder = new TextDecoder(); + const state: SSEParserState = { eventType: "", dataLines: [] }; + + for await (const chunk of res.body) { + const text = decoder.decode(chunk as Uint8Array, { stream: true }); + for (const rawLine of text.split("\n")) { + feedSSELine(rawLine.replace(CR_RE, ""), state, (type, data) => { + if (type === SENTRY_CONTENT_TYPE) { + processSSEEvent(data, activeFilters); + } + }); + } + } +} + +/** Parse and format a single SSE data payload from upstream. */ +function processSSEEvent( + data: string, + activeFilters: ReadonlySet +): void { + try { + const envelope = JSON.parse(data) as [ + Record, + [{ type?: string }, unknown][], + ]; + const [header, items] = envelope; + for (const [itemHeader, itemPayload] of items) { + if (!isItemIncluded(itemHeader.type, activeFilters)) { + continue; + } + for (const line of formatItem( + itemHeader.type, + itemPayload as Record, + header, + itemHeader.type ?? "envelope" + )) { + logger.log(line); + } + } + } catch (err) { + logger.debug( + `Failed to parse SSE event: ${err instanceof Error ? err.message : String(err)}` + ); + } +} + export const localCommand = buildCommand({ docs: { brief: "Run a local Spotlight server to capture dev SDK events", fullDescription: - "Start a local Spotlight-compatible server.\n\n" + + "Start a local Spotlight-compatible server, or attach to one\n" + + "already running on the same port.\n\n" + "Spotlight is Sentry for Development — it gives you a live view of\n" + - "errors, traces, and logs emitted by Sentry SDKs in your dev stack.\n" + - "This command runs a minimal Hono server that ingests envelopes\n" + - "from any Sentry SDK and tails them to your terminal.\n\n" + - "Endpoints:\n" + - " POST /stream — Spotlight ingest\n" + - " POST /api/{projectId}/envelope/ — Sentry SDK ingest\n" + - " GET /stream — SSE feed (for the Spotlight overlay)\n" + - " GET /health — health check\n\n" + + "errors, traces, and logs emitted by Sentry SDKs in your dev stack.\n\n" + + "If a server is already listening on the port, the command connects\n" + + "as an SSE consumer and tails events from it. Otherwise it starts\n" + + "its own server.\n\n" + "Learn more: https://spotlightjs.com/docs/getting-started/\n\n" + - "Press Ctrl-C to stop the server.", + "Press Ctrl-C to stop.", }, parameters: { flags: { @@ -726,8 +817,39 @@ export const localCommand = buildCommand({ }, auth: false, async *func(this: SentryContext, flags: LocalFlags) { - const buffer = createSpotlightBuffer(BUFFER_SIZE); const activeFilters = new Set(flags.filter); + const url = `http://${flags.host}:${flags.port}`; + + if (await isServerRunning(url)) { + logger.info(`Connected to existing server at ${bold(url)}`); + if (activeFilters.size > 0) { + logger.info(`Filtering: ${[...activeFilters].join(", ")}`); + } + logger.info("Press Ctrl-C to stop."); + + const ac = new AbortController(); + const stop = () => ac.abort(); + process.on("SIGINT", stop); + process.on("SIGTERM", stop); + + if (flags.quiet) { + await new Promise((resolve) => { + ac.signal.addEventListener("abort", () => resolve()); + }); + } else { + await consumeSSE(url, activeFilters, ac.signal).catch( + (err: unknown) => { + if (!(err instanceof DOMException && err.name === "AbortError")) { + throw err; + } + } + ); + } + logger.log("Disconnected."); + return; + } + + const buffer = createSpotlightBuffer(BUFFER_SIZE); if (!flags.quiet) { buffer.subscribe((container) => { @@ -745,8 +867,8 @@ export const localCommand = buildCommand({ flags.host ); - const url = `http://${flags.host}:${boundPort}`; - logger.info(`Listening on ${bold(url)}`); + const listenUrl = `http://${flags.host}:${boundPort}`; + logger.info(`Listening on ${bold(listenUrl)}`); if (activeFilters.size > 0) { logger.info(`Filtering: ${[...activeFilters].join(", ")}`); } From f411cf5b1adf01082e53841160d87102d4620503 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 11:45:16 +0000 Subject: [PATCH 15/21] fix(local): return null from CORS origin callback for non-localhost origins Returning a mismatched string still worked (browser blocks on mismatch) but returning null correctly omits the Access-Control-Allow-Origin header per Hono's CORS middleware API. --- src/commands/local.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index c67999a2c..e7c03c3ea 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -115,7 +115,7 @@ function buildApp( "*", cors({ origin: (origin) => - LOCALHOST_ORIGIN_RE.test(origin) ? origin : "http://localhost", + LOCALHOST_ORIGIN_RE.test(origin) ? origin : null, allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["Content-Type", "Content-Encoding", "User-Agent"], }) From 45fb65ddccf6c193fdae7f422182d54ef323d503 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 11:49:33 +0000 Subject: [PATCH 16/21] fix(local): address review findings, remove learn-more line - Close failed server on EADDRINUSE before retrying (prevents fd leak) - Fix SSE chunk boundary: use partial line buffer across chunks - Fix SSE data field: spec-compliant single-space strip instead of trimStart - Add logger.debug to isServerRunning catch block (repo policy) - Use process.once for signal handlers in consumer mode - Guard abort race in quiet consumer mode - Remove dead onEnvelope parameter from buildApp - Remove 'Learn more about Spotlight' line from startup banner --- src/commands/local.ts | 56 +++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index e7c03c3ea..0a4e737e0 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -106,8 +106,7 @@ const LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/; * arbitrary remote origins to read the SSE envelope stream. */ function buildApp( - spotlightBuffer: ReturnType, - onEnvelope?: (contentType: string, data: Buffer) => void + spotlightBuffer: ReturnType ): Hono { const app = new Hono(); @@ -157,7 +156,7 @@ function buildApp( | undefined; const userAgent = c.req.header("user-agent"); - const container = pushToSpotlightBuffer({ + pushToSpotlightBuffer({ spotlightBuffer, body, encoding: contentEncoding, @@ -165,10 +164,6 @@ function buildApp( userAgent, }); - if (container) { - onEnvelope?.(container.getContentType(), container.getData()); - } - return c.body(null, 204); }; @@ -640,6 +635,7 @@ function tryListen( server.once("listening", () => resolve({ server, port })); server.once("error", async (err: NodeJS.ErrnoException) => { + server.close(); if (err.code === "EADDRINUSE") { attempts += 1; if (attempts > MAX_PORT_RETRIES) { @@ -673,7 +669,11 @@ async function isServerRunning(url: string): Promise { try { const res = await fetch(`${url}/health`); return res.ok; - } catch { + } catch (err) { + logger.debug( + `No existing server at ${url}`, + err instanceof Error ? err.message : String(err) + ); return false; } } @@ -691,9 +691,11 @@ function feedSSELine( onEvent: (type: string, data: string) => void ): void { if (line.startsWith("event:")) { - state.eventType = line.slice(6).trim(); + const value = line.slice(6); + state.eventType = value.startsWith(" ") ? value.slice(1) : value; } else if (line.startsWith("data:")) { - state.dataLines.push(line.slice(5).trimStart()); + const value = line.slice(5); + state.dataLines.push(value.startsWith(" ") ? value.slice(1) : value); } else if (line === "" && state.dataLines.length > 0) { onEvent(state.eventType, state.dataLines.join("\n")); state.eventType = ""; @@ -722,17 +724,25 @@ async function consumeSSE( const decoder = new TextDecoder(); const state: SSEParserState = { eventType: "", dataLines: [] }; + const onEvent = (type: string, data: string) => { + if (type === SENTRY_CONTENT_TYPE) { + processSSEEvent(data, activeFilters); + } + }; + let partial = ""; for await (const chunk of res.body) { - const text = decoder.decode(chunk as Uint8Array, { stream: true }); - for (const rawLine of text.split("\n")) { - feedSSELine(rawLine.replace(CR_RE, ""), state, (type, data) => { - if (type === SENTRY_CONTENT_TYPE) { - processSSEEvent(data, activeFilters); - } - }); + const text = + partial + decoder.decode(chunk as Uint8Array, { stream: true }); + const lines = text.split("\n"); + partial = lines.pop() ?? ""; + for (const rawLine of lines) { + feedSSELine(rawLine.replace(CR_RE, ""), state, onEvent); } } + if (partial) { + feedSSELine(partial.replace(CR_RE, ""), state, onEvent); + } } /** Parse and format a single SSE data payload from upstream. */ @@ -777,7 +787,6 @@ export const localCommand = buildCommand({ "If a server is already listening on the port, the command connects\n" + "as an SSE consumer and tails events from it. Otherwise it starts\n" + "its own server.\n\n" + - "Learn more: https://spotlightjs.com/docs/getting-started/\n\n" + "Press Ctrl-C to stop.", }, parameters: { @@ -829,11 +838,15 @@ export const localCommand = buildCommand({ const ac = new AbortController(); const stop = () => ac.abort(); - process.on("SIGINT", stop); - process.on("SIGTERM", stop); + process.once("SIGINT", stop); + process.once("SIGTERM", stop); if (flags.quiet) { await new Promise((resolve) => { + if (ac.signal.aborted) { + resolve(); + return; + } ac.signal.addEventListener("abort", () => resolve()); }); } else { @@ -872,9 +885,6 @@ export const localCommand = buildCommand({ if (activeFilters.size > 0) { logger.info(`Filtering: ${[...activeFilters].join(", ")}`); } - logger.info( - `Learn more about Spotlight: ${bold("https://spotlightjs.com/docs/getting-started/")}` - ); logger.info("Press Ctrl-C to stop."); await waitForShutdown(server); From 5aa1a83eabdd4334e8c8e04c62328cd298d7b5b6 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 11:51:28 +0000 Subject: [PATCH 17/21] fix(local): biome formatting for cors origin ternary --- src/commands/local.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 0a4e737e0..3a59ba5f3 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -113,8 +113,7 @@ function buildApp( app.use( "*", cors({ - origin: (origin) => - LOCALHOST_ORIGIN_RE.test(origin) ? origin : null, + origin: (origin) => (LOCALHOST_ORIGIN_RE.test(origin) ? origin : null), allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["Content-Type", "Content-Encoding", "User-Agent"], }) From 580b6fe3a2365afe7779f66d2abb26035717f99c Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 11:55:34 +0000 Subject: [PATCH 18/21] fix(local): add 2s timeout to health check fetch Prevents the command from hanging indefinitely if a non-HTTP service is listening on the target port. --- src/commands/local.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index 3a59ba5f3..da00d876f 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -666,7 +666,9 @@ function tryListen( */ async function isServerRunning(url: string): Promise { try { - const res = await fetch(`${url}/health`); + const res = await fetch(`${url}/health`, { + signal: AbortSignal.timeout(2000), + }); return res.ok; } catch (err) { logger.debug( From 722294bcf1462c2748f86b5df19ebe189a4b1a65 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 12:02:14 +0000 Subject: [PATCH 19/21] fix(local): sanitize trace.op and trace.status with stripAnsi These fields were interpolated into terminal output without sanitization, unlike other user-controlled fields in the same function. --- src/commands/local.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index da00d876f..e536766d4 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -380,7 +380,7 @@ function formatTransactionItem( const op = trace?.op; if (op && op !== "default" && op !== "unknown") { - msg = `[${op}] ${msg}`; + msg = `[${stripAnsi(op)}] ${msg}`; } const start = event.start_timestamp as number | undefined; @@ -392,7 +392,7 @@ function formatTransactionItem( const status = trace?.status; if (status && status !== "ok") { - msg += ` ${muted(`[${status}]`)}`; + msg += ` ${muted(`[${stripAnsi(status)}]`)}`; } const spans = event.spans as unknown[] | undefined; From 5c32850a3cdd34516ca675a203c53f8e6f859de2 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 22:26:32 +0000 Subject: [PATCH 20/21] merge: regenerate pnpm-lock.yaml after merge with main --- .../skills/sentry-cli/references/local.md | 2 +- pnpm-lock.yaml | 817 +++++++++++++++++- 2 files changed, 815 insertions(+), 4 deletions(-) diff --git a/plugins/sentry-cli/skills/sentry-cli/references/local.md b/plugins/sentry-cli/skills/sentry-cli/references/local.md index 4bb11aea8..b349a52db 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/local.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/local.md @@ -1,6 +1,6 @@ --- name: sentry-cli-local -version: 0.34.0-dev.0 +version: 0.35.0-dev.0 description: Run a local Spotlight server to capture dev SDK events requires: bins: ["sentry"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6873e6de..6aa469854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@clack/prompts': specifier: 0.11.0 version: 0.11.0 + '@hono/node-server': + specifier: ^2.0.0 + version: 2.0.2(hono@4.12.18) '@mastra/client-js': specifier: ^1.4.0 version: 1.19.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@3.25.76))(zod@3.25.76))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@3.25.76))(zod@3.25.76))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.76))(@types/json-schema@7.0.15)(express@5.2.1)(openapi-types@12.1.3)(zod@3.25.76) @@ -39,10 +42,13 @@ importers: version: 10.50.0(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) '@sentry/node-core': specifier: 10.50.0 - version: 10.50.0(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + version: 10.50.0(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) '@sentry/sqlish': specifier: ^1.0.0 version: 1.0.1(react@19.2.6) + '@spotlightjs/spotlight': + specifier: ^4.11.3 + version: 4.11.4(hono-rate-limiter@0.4.2(hono@4.12.18)) '@stricli/auto-complete': specifier: ^1.2.4 version: 1.2.7 @@ -88,6 +94,9 @@ importers: fast-check: specifier: ^4.5.3 version: 4.8.0 + hono: + specifier: ^4.12.15 + version: 4.12.18 http-cache-semantics: specifier: ^4.2.0 version: 4.2.0 @@ -424,16 +433,45 @@ packages: cpu: [x64] os: [win32] + '@fastify/otel@0.18.0': + resolution: {integrity: sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@hono/mcp@0.2.5': + resolution: {integrity: sha512-JsaJes7VlNvUrUQ9j2b9C13xjFLvyKQY515aWtsdJ9cwhBmWz5od2yUCbDu7cX38GeADmlLmpu4BKNNAV6G27w==} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.1 + hono: '*' + hono-rate-limiter: ^0.4.2 + zod: ^3.25.0 || ^4.0.0 + '@hono/node-server@1.19.14': resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 + '@hono/node-server@2.0.2': + resolution: {integrity: sha512-tXlTi1h/4V7sDe7i97IVP+9re9ZU7wXZZggnR5ucCRclf1+AX6YhGStrR5w8bLj+3Mlyl0pKfBh9gqTqqnGKfQ==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + '@isaacs/ttlcache@2.1.4': resolution: {integrity: sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ==} engines: {node: '>=12'} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lukeed/csprng@1.1.0': resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} @@ -470,16 +508,160 @@ packages: '@cfworker/json-schema': optional: true + '@opentelemetry/api-logs@0.207.0': + resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.212.0': + resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.214.0': + resolution: {integrity: sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} + '@opentelemetry/core@2.6.1': + resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.7.1': resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/instrumentation-amqplib@0.61.0': + resolution: {integrity: sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.57.0': + resolution: {integrity: sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dataloader@0.31.0': + resolution: {integrity: sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.33.0': + resolution: {integrity: sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.57.0': + resolution: {integrity: sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.62.0': + resolution: {integrity: sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.60.0': + resolution: {integrity: sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.214.0': + resolution: {integrity: sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.23.0': + resolution: {integrity: sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.58.0': + resolution: {integrity: sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.62.0': + resolution: {integrity: sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.58.0': + resolution: {integrity: sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.67.0': + resolution: {integrity: sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.60.0': + resolution: {integrity: sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.60.0': + resolution: {integrity: sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.60.0': + resolution: {integrity: sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.66.0': + resolution: {integrity: sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.33.0': + resolution: {integrity: sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.207.0': + resolution: {integrity: sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.212.0': + resolution: {integrity: sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.214.0': + resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/resources@2.7.1': resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -496,10 +678,21 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@peggyjs/from-mem@3.1.3': resolution: {integrity: sha512-LLlgtfXIaeYXoOYovOI0spLM8ZXaqkAlmcRRrLzHJzLMqkU6Sw0R4KMoCoHx1PjaP815pSCBlS+BN6aD8t1Jgg==} engines: {node: '>=20.8'} + '@prisma/instrumentation@7.6.0': + resolution: {integrity: sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==} + peerDependencies: + '@opentelemetry/api': ^1.8 + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -516,6 +709,10 @@ packages: resolution: {integrity: sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==} engines: {node: '>=18'} + '@sentry/core@10.53.1': + resolution: {integrity: sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA==} + engines: {node: '>=18'} + '@sentry/node-core@10.50.0': resolution: {integrity: sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw==} engines: {node: '>=18'} @@ -540,6 +737,34 @@ packages: '@opentelemetry/semantic-conventions': optional: true + '@sentry/node-core@10.53.1': + resolution: {integrity: sha512-iH7SMcM/7jPbN+t7+7ussQOiIqI4BMOGt4VYWlV71/z7k0pY+YPaSvlfVkNXfISiDzFAKv0ecCY3BmsLMu1xDQ==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/exporter-trace-otlp-http': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + '@opentelemetry/semantic-conventions': + optional: true + + '@sentry/node@10.53.1': + resolution: {integrity: sha512-rxHVil0tJAmz+keFcZCj1LaUdgdkK66E/l0gqh2p1209PNCGoD3lnClFr6vusy1aF3zF8O9JPtuMEJzXOKhs+w==} + engines: {node: '>=18'} + '@sentry/opentelemetry@10.50.0': resolution: {integrity: sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw==} engines: {node: '>=18'} @@ -549,6 +774,15 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 + '@sentry/opentelemetry@10.53.1': + resolution: {integrity: sha512-Zok6UXla0mFOjd1YnVb1TZtQNOry9v93fXUqx8jmDaygwWM2BwvP8rBQabLz0/OZXo8+35oge+Vmw+QY5aesnA==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + '@sentry/sqlish@1.0.1': resolution: {integrity: sha512-8Ioewv2Qo4Y18T/O8M9FdoUD70VFUs+gu3XKsXQmEqFbgLl1fVkInmk1IL98fJzNADGCyRXsHGptsISPVW8OsA==} peerDependencies: @@ -569,6 +803,11 @@ packages: resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} + '@spotlightjs/spotlight@4.11.4': + resolution: {integrity: sha512-OVbvc7QvPXVK+6L1MNl/9mqfmO/YIRqTnNik2kcw46OASH1uVZeN3RNHoFcLuh6B3vE+XTl4clCAsbhRHTd7Mg==} + engines: {node: '>=20'} + hasBin: true + '@standard-community/standard-json@0.3.5': resolution: {integrity: sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA==} peerDependencies: @@ -649,6 +888,9 @@ packages: '@types/bun@1.3.14': resolution: {integrity: sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -664,6 +906,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -673,6 +918,12 @@ packages: '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/picomatch@4.0.3': resolution: {integrity: sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==} @@ -685,6 +936,9 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -724,6 +978,9 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + anser@2.3.5: + resolution: {integrity: sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==} + ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -1017,6 +1274,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + eventsource@4.1.0: + resolution: {integrity: sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==} + engines: {node: '>=20.0.0'} + execa@9.6.1: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} @@ -1045,6 +1306,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fuzzy@1.12.0: + resolution: {integrity: sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q==} + fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} @@ -1079,6 +1343,9 @@ packages: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1118,6 +1385,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graphemesplit@2.6.0: + resolution: {integrity: sha512-rG9w2wAfkpg0DILa1pjnjNfucng3usON360shisqIMUBw/87pojcBSrHmeE4UwryAuBih7g8m1oilf5/u8EWdQ==} + gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} @@ -1156,6 +1426,11 @@ packages: hono: optional: true + hono-rate-limiter@0.4.2: + resolution: {integrity: sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw==} + peerDependencies: + hono: ^4.1.1 + hono@4.12.18: resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} engines: {node: '>=16.9.0'} @@ -1182,6 +1457,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + import-in-the-middle@3.0.1: resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} engines: {node: '>=18'} @@ -1263,6 +1541,9 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -1287,6 +1568,13 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + launch-editor@2.13.2: + resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} + + logfmt@1.4.0: + resolution: {integrity: sha512-p1Ow0C2dDJYaQBhRHt+HVMP6ELuBm4jYSYNHPMfz0J5wJ9qA6/7oBOlBZBfT1InqguTYcvJzNea5FItDxTcbyw==} + hasBin: true + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -1306,6 +1594,10 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mcp-proxy@5.12.5: + resolution: {integrity: sha512-Vawdc8vi36fXxKCaDpluRvbGcmrUXJdvXcDhkh30HYsws8XqX2rWPBflZpavzeS+6SwijRFV7g+9ypQRJZlrEQ==} + hasBin: true + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -1534,6 +1826,9 @@ packages: resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} engines: {node: '>=20'} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -1578,6 +1873,17 @@ packages: engines: {node: '>=20'} hasBin: true + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1589,6 +1895,22 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -1652,6 +1974,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + restore-cursor@4.0.0: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1744,6 +2070,9 @@ packages: resolution: {integrity: sha512-IlassDs1Ve8nV6uyQZXF9kdkJpVKnMte2JZQXu13M0A5zwc+vu6+LNHfmxsHBMDtoZE21RHiKI0/xvpecZRCNg==} engines: {node: '>=20'} + split@0.2.10: + resolution: {integrity: sha512-e0pKq+UUH2Xq/sXbYpZBZc3BawsfDZ7dgv+JtRTUPNcvF5CMR4Y9cvJqkMY0MoxWzTHvZuz1beg6pNEKlszPiQ==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -1798,6 +2127,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tinyexec@1.1.2: resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} @@ -1867,6 +2202,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -1962,6 +2300,10 @@ packages: utf-8-validate: optional: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -1969,6 +2311,11 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -2197,12 +2544,43 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@fastify/otel@0.18.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@hono/mcp@0.2.5(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(hono-rate-limiter@0.4.2(hono@4.12.18))(hono@4.12.18)(zod@4.4.3)': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + hono: 4.12.18 + hono-rate-limiter: 0.4.2(hono@4.12.18) + pkce-challenge: 5.0.1 + zod: 4.4.3 + '@hono/node-server@1.19.14(hono@4.12.18)': dependencies: hono: 4.12.18 + '@hono/node-server@2.0.2(hono@4.12.18)': + dependencies: + hono: 4.12.18 + '@isaacs/ttlcache@2.1.4': {} + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@lukeed/csprng@1.1.0': {} '@lukeed/uuid@2.0.1': @@ -2306,17 +2684,238 @@ snapshots: pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) + zod-to-json-schema: 3.25.2(zod@4.4.3) transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.18) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.18 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/api-logs@0.207.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api-logs@0.212.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api-logs@0.214.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api@1.9.1': {} + '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/instrumentation-amqplib@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.57.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.31.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.33.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.57.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.23.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.58.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.58.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.67.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.66.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.33.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.207.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.212.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.212.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + import-in-the-middle: 3.0.1 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -2332,10 +2931,22 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@peggyjs/from-mem@3.1.3': dependencies: semver: 7.7.4 + '@prisma/instrumentation@7.6.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@sec-ant/readable-stream@0.4.1': {} '@sentry/api@0.141.0(zod@3.25.76)': @@ -2344,7 +2955,9 @@ snapshots: '@sentry/core@10.50.0(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762)': {} - '@sentry/node-core@10.50.0(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + '@sentry/core@10.53.1(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762)': {} + + '@sentry/node-core@10.50.0(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': dependencies: '@sentry/core': 10.50.0(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) '@sentry/opentelemetry': 10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) @@ -2352,9 +2965,57 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 + '@sentry/node-core@10.53.1(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + dependencies: + '@sentry/core': 10.53.1(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) + '@sentry/opentelemetry': 10.53.1(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 3.0.1 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@sentry/node@10.53.1': + dependencies: + '@fastify/otel': 0.18.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-connect': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dataloader': 0.31.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fs': 0.33.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-generic-pool': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-graphql': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-hapi': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-kafkajs': 0.23.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-knex': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-koa': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-lru-memoizer': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongodb': 0.67.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongoose': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql2': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pg': 0.66.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-tedious': 0.33.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@prisma/instrumentation': 7.6.0(@opentelemetry/api@1.9.1) + '@sentry/core': 10.53.1(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) + '@sentry/node-core': 10.53.1(patch_hash=70711b63f77a0c4b4fe10c70350be7126dc05b220ea303cda74090bce733f467)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/opentelemetry': 10.53.1(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 3.0.1 + transitivePeerDependencies: + - '@opentelemetry/exporter-trace-otlp-http' + - supports-color + '@sentry/opentelemetry@10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -2363,6 +3024,14 @@ snapshots: '@opentelemetry/semantic-conventions': 1.41.1 '@sentry/core': 10.50.0(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) + '@sentry/opentelemetry@10.53.1(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@sentry/core': 10.53.1(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) + '@sentry/sqlish@1.0.1(react@19.2.6)': optionalDependencies: react: 19.2.6 @@ -2378,6 +3047,32 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 + '@spotlightjs/spotlight@4.11.4(hono-rate-limiter@0.4.2(hono@4.12.18))': + dependencies: + '@hono/mcp': 0.2.5(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(hono-rate-limiter@0.4.2(hono@4.12.18))(hono@4.12.18)(zod@4.4.3) + '@hono/node-server': 1.19.14(hono@4.12.18) + '@jridgewell/trace-mapping': 0.3.31 + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + '@sentry/core': 10.50.0(patch_hash=2351f28c53bf19ae9eb2f6d741b6cb880bc9c6e60bf7ddd14fde6a0e25bde762) + '@sentry/node': 10.53.1 + anser: 2.3.5 + chalk: 5.6.2 + eventsource: 4.1.0 + fast-fuzzy: 1.12.0 + hono: 4.12.18 + launch-editor: 2.13.2 + logfmt: 1.4.0 + mcp-proxy: 5.12.5 + semver: 7.8.0 + uuidv7: 1.2.1 + yaml: 2.9.0 + zod: 4.4.3 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@opentelemetry/exporter-trace-otlp-http' + - hono-rate-limiter + - supports-color + '@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@3.25.76))(zod@3.25.76)': dependencies: '@standard-schema/spec': 1.1.0 @@ -2411,6 +3106,10 @@ snapshots: dependencies: bun-types: 1.3.14 + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.19.19 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -2425,6 +3124,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': + dependencies: + '@types/node': 22.19.19 + '@types/node-fetch@2.6.13': dependencies: '@types/node': 22.19.19 @@ -2438,6 +3141,16 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.15.6 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 22.19.19 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + '@types/picomatch@4.0.3': {} '@types/qrcode-terminal@0.12.2': {} @@ -2448,6 +3161,10 @@ snapshots: '@types/semver@7.7.1': {} + '@types/tedious@4.0.14': + dependencies: + '@types/node': 22.19.19 + '@types/unist@3.0.3': {} '@workflow/serde@4.1.0-beta.2': {} @@ -2482,6 +3199,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + anser@2.3.5: {} + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -2750,6 +3469,10 @@ snapshots: dependencies: eventsource-parser: 3.0.8 + eventsource@4.1.0: + dependencies: + eventsource-parser: 3.0.8 + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -2815,6 +3538,10 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-fuzzy@1.12.0: + dependencies: + graphemesplit: 2.6.0 + fast-uri@3.1.2: {} fastq@1.20.1: @@ -2855,6 +3582,8 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 4.0.0-beta.3 + forwarded-parse@2.1.2: {} + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -2896,6 +3625,11 @@ snapshots: gopd@1.2.0: {} + graphemesplit@2.6.0: + dependencies: + js-base64: 3.7.8 + unicode-trie: 2.0.0 + gray-matter@4.0.3: dependencies: js-yaml: 3.14.2 @@ -2926,6 +3660,10 @@ snapshots: optionalDependencies: hono: 4.12.18 + hono-rate-limiter@0.4.2(hono@4.12.18): + dependencies: + hono: 4.12.18 + hono@4.12.18: {} http-cache-semantics@4.2.0: {} @@ -2950,6 +3688,13 @@ snapshots: ignore@7.0.5: {} + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + import-in-the-middle@3.0.1: dependencies: acorn: 8.16.0 @@ -3030,6 +3775,8 @@ snapshots: jose@6.2.3: {} + js-base64@3.7.8: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -3047,6 +3794,16 @@ snapshots: kind-of@6.0.3: {} + launch-editor@2.13.2: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + + logfmt@1.4.0: + dependencies: + split: 0.2.10 + through: 2.3.8 + longest-streak@3.1.0: {} lru-cache@11.3.6: {} @@ -3057,6 +3814,8 @@ snapshots: math-intrinsics@1.1.0: {} + mcp-proxy@5.12.5: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -3433,6 +4192,8 @@ snapshots: dependencies: is-network-error: 1.3.2 + pako@0.2.9: {} + parse-ms@4.0.0: {} parse5-htmlparser2-tree-adapter@6.0.1: @@ -3466,12 +4227,34 @@ snapshots: commander: 14.0.3 source-map-generator: 2.0.6 + pg-int8@1.0.1: {} + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@4.0.4: {} pkce-challenge@5.0.1: {} + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -3547,6 +4330,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + restore-cursor@4.0.0: dependencies: onetime: 5.1.2 @@ -3655,6 +4445,10 @@ snapshots: source-map-generator@2.0.6: {} + split@0.2.10: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} stack-utils@2.0.6: @@ -3702,6 +4496,10 @@ snapshots: dependencies: any-promise: 1.3.0 + through@2.3.8: {} + + tiny-inflate@1.0.3: {} + tinyexec@1.1.2: {} tinyglobby@0.2.16: @@ -3757,6 +4555,11 @@ snapshots: undici-types@6.21.0: {} + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + unicorn-magic@0.3.0: {} unified@11.0.5: @@ -3841,10 +4644,14 @@ snapshots: ws@8.20.1: {} + xtend@4.0.2: {} + xxhash-wasm@1.1.0: {} y18n@5.0.8: {} + yaml@2.9.0: {} + yargs-parser@20.2.9: {} yargs@16.2.0: @@ -3875,6 +4682,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod@3.25.76: {} zod@4.4.3: {} From 26869ed97e80d5c78dfeaa7e668a22bc65255124 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 15 May 2026 22:43:22 +0000 Subject: [PATCH 21/21] fix(local): detect upstream disconnect in --quiet attach mode Previously, --quiet mode waited only for SIGINT/SIGTERM and would hang indefinitely if the upstream server shut down. Now it consumes the SSE stream (draining chunks without parsing) so the for-await loop exits naturally on disconnect. --- src/commands/local.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/commands/local.ts b/src/commands/local.ts index e536766d4..93897d363 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -713,7 +713,8 @@ function feedSSELine( async function consumeSSE( url: string, activeFilters: ReadonlySet, - signal: AbortSignal + signal: AbortSignal, + quiet = false ): Promise { const res = await fetch(`${url}/stream`, { headers: { Accept: "text/event-stream" }, @@ -723,6 +724,15 @@ async function consumeSSE( return; } + // In quiet mode we still consume the stream to detect disconnection, + // but skip parsing/formatting entirely. + if (quiet) { + for await (const _chunk of res.body) { + // drain + } + return; + } + const decoder = new TextDecoder(); const state: SSEParserState = { eventType: "", dataLines: [] }; const onEvent = (type: string, data: string) => { @@ -842,23 +852,15 @@ export const localCommand = buildCommand({ process.once("SIGINT", stop); process.once("SIGTERM", stop); - if (flags.quiet) { - await new Promise((resolve) => { - if (ac.signal.aborted) { - resolve(); - return; + // Connect to the SSE stream even in quiet mode so we detect when + // the upstream server disconnects (the for-await loop exits). + await consumeSSE(url, activeFilters, ac.signal, flags.quiet).catch( + (err: unknown) => { + if (!(err instanceof DOMException && err.name === "AbortError")) { + throw err; } - ac.signal.addEventListener("abort", () => resolve()); - }); - } else { - await consumeSSE(url, activeFilters, ac.signal).catch( - (err: unknown) => { - if (!(err instanceof DOMException && err.name === "AbortError")) { - throw err; - } - } - ); - } + } + ); logger.log("Disconnected."); return; }