From df7288285d68fbcae3bc39c2d9306102bb210440 Mon Sep 17 00:00:00 2001 From: Joshua Christman Date: Tue, 10 Feb 2026 23:14:10 -0600 Subject: [PATCH 1/3] feat: Add native OpenAI-compatible provider for non-Claude AI backends Introduce a native OpenAI API path that bypasses the Claude Agent SDK, enabling Shannon to run with local models (minimax, ollama, vLLM) and any OpenAI-compatible endpoint. Summary of changes: Provider infrastructure: - src/ai/provider-config.ts: New provider selection (claude|openai) from AI_PROVIDER env var, with AI_BASE_URL/AI_MODEL/AI_API_KEY support - src/ai/claude-executor.ts: Route to OpenAI executor when provider is openai, passing sourceDir and agentName for tool context OpenAI executor & tools: - src/ai/openai-executor.ts: New OpenAI-compatible execution loop with streaming, tool calls, and turn limits; integrates Shannon helper tools and Playwright MCP - src/ai/openai-tools.ts: Built-in tools (bash, read_file, write_file, edit_file, search_files, list_directory) in OpenAI function format - src/ai/mcp-client.ts: MCP client that invokes save_deliverable and generate_totp directly (no MCP protocol) plus Playwright via stdio MCP server updates: - mcp-server: Export createSaveDeliverableHandler and generateTotp for direct use by OpenAI executor; adjust tool-responses type for compatibility CLI & config: - shannon: Add PROVIDER env var passthrough for docker-compose - .env.example: AI_PROVIDER, AI_BASE_URL, AI_MODEL, AI_API_KEY - CLAUDE.md: Document native OpenAI provider usage - docker-compose.yml: Propagate provider-related env vars Other: - .dockerignore: Additional ignores - prompts: Minor pre-recon-code updates - package.json: New dependencies for OpenAI/MCP integration Co-authored-by: Cursor --- .dockerignore | 8 + .env.example | 12 + CLAUDE.md | 19 + docker-compose.yml | 5 + mcp-server/src/index.ts | 4 +- mcp-server/src/tools/generate-totp.ts | 2 +- mcp-server/src/tools/save-deliverable.ts | 6 +- mcp-server/src/types/tool-responses.ts | 2 +- package-lock.json | 1528 ++++++++++++++++++- package.json | 2 + prompts/pipeline-testing/pre-recon-code.txt | 2 +- prompts/pre-recon-code.txt | 2 + shannon | 24 +- src/ai/claude-executor.ts | 25 +- src/ai/mcp-client.ts | 162 ++ src/ai/openai-executor.ts | 264 ++++ src/ai/openai-tools.ts | 394 +++++ src/ai/provider-config.ts | 54 + 18 files changed, 2435 insertions(+), 80 deletions(-) create mode 100644 src/ai/mcp-client.ts create mode 100644 src/ai/openai-executor.ts create mode 100644 src/ai/openai-tools.ts create mode 100644 src/ai/provider-config.ts diff --git a/.dockerignore b/.dockerignore index deaa1cc5..bc5cba7b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,14 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +dist/ + +# Environment and runtime data (passed or mounted at run time, not needed in image) +.env +.env.* +!.env.example +audit-logs/ +repos/ # Runtime directories sessions/ diff --git a/.env.example b/.env.example index b0d1875e..33f12afc 100644 --- a/.env.example +++ b/.env.example @@ -23,8 +23,20 @@ ANTHROPIC_API_KEY=your-api-key-here # OPENROUTER_API_KEY=sk-or-your-openrouter-key # ROUTER_DEFAULT=openrouter,google/gemini-3-flash-preview +# ============================================================================= +# OPTION 3: OpenAI-Compatible (local models, minimax, ollama, vLLM, etc.) +# ============================================================================= +# Native support - no router. Use with: ./shannon start ... PROVIDER=openai +# Or set AI_PROVIDER=openai in .env + +# AI_PROVIDER=openai +# AI_BASE_URL=http://localhost:8080/v1 +# AI_MODEL=minimax +# AI_API_KEY=your-key-or-empty-for-local + # ============================================================================= # Available Models # ============================================================================= # OpenAI: gpt-5.2, gpt-5-mini # OpenRouter: google/gemini-3-flash-preview +# Local: minimax, ollama models, vLLM, etc. (via OPTION 3) diff --git a/CLAUDE.md b/CLAUDE.md index 1ce8d2c8..5d4a0856 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -296,6 +296,25 @@ ROUTER_DEFAULT=openrouter,google/gemini-3-flash-preview **Note:** Shannon is optimized for Anthropic's Claude models. Alternative providers are useful for cost savings during development but may produce varying results. +### Native OpenAI-Compatible Provider + +Shannon supports a **native** OpenAI-compatible API path (no router) for local models and services like minimax, ollama, vLLM, etc. This bypasses the Claude Agent SDK entirely and uses direct API calls. + +**Enable OpenAI provider:** +```bash +./shannon start URL= REPO= PROVIDER=openai AI_BASE_URL=http://localhost:8080/v1 AI_MODEL=minimax AI_API_KEY=optional +``` + +**Configuration (in .env):** +```bash +AI_PROVIDER=openai +AI_BASE_URL=http://localhost:8080/v1 +AI_MODEL=minimax +AI_API_KEY=your-key-or-empty-for-local +``` + +**Benefits over router mode:** Direct API integration, no translation layer, works with any OpenAI-compatible endpoint. Supports built-in tools (bash, file read/write, search) and MCP (Shannon helper, Playwright). + ## Troubleshooting ### Common Issues diff --git a/docker-compose.yml b/docker-compose.yml index b68c0b3b..25d0dad8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,11 @@ services: - ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:-} # Auth token for router - ROUTER_DEFAULT=${ROUTER_DEFAULT:-} # Model name when using router (e.g., "gemini,gemini-2.5-pro") - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-} + - CLAUDE_CODE_MAX_OUTPUT_TOKENS=${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-64000} + - AI_PROVIDER=${AI_PROVIDER:-claude} + - AI_BASE_URL=${AI_BASE_URL:-} + - AI_MODEL=${AI_MODEL:-} + - AI_API_KEY=${AI_API_KEY:-} depends_on: temporal: condition: service_healthy diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index 0844e967..577182ee 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -39,8 +39,8 @@ export function createShannonHelperServer(targetDir: string): ReturnType Promise<{ content: Array<{ type: 'text'; text: string }>; [key: string]: unknown }> ); diff --git a/mcp-server/src/tools/save-deliverable.ts b/mcp-server/src/tools/save-deliverable.ts index 3484d5f5..019adc72 100644 --- a/mcp-server/src/tools/save-deliverable.ts +++ b/mcp-server/src/tools/save-deliverable.ts @@ -37,8 +37,9 @@ export type SaveDeliverableInput = z.infer; * * This factory pattern ensures each MCP server instance has its own targetDir, * preventing race conditions when multiple workflows run in parallel. + * Exported for direct use by OpenAI executor (bypasses MCP protocol). */ -function createSaveDeliverableHandler(targetDir: string) { +export function createSaveDeliverableHandler(targetDir: string) { return async function saveDeliverable(args: SaveDeliverableInput): Promise { try { const { deliverable_type, content } = args; @@ -92,10 +93,11 @@ function createSaveDeliverableHandler(targetDir: string) { * deliverables are saved to the correct workflow's directory. */ export function createSaveDeliverableTool(targetDir: string) { + const handler = createSaveDeliverableHandler(targetDir); return tool( 'save_deliverable', 'Saves deliverable files with automatic validation. Queue files must have {"vulnerabilities": [...]} structure.', SaveDeliverableInputSchema.shape, - createSaveDeliverableHandler(targetDir) + handler as unknown as (args: SaveDeliverableInput, extra?: unknown) => Promise<{ content: Array<{ type: 'text'; text: string }>; [key: string]: unknown }> ); } diff --git a/mcp-server/src/types/tool-responses.ts b/mcp-server/src/types/tool-responses.ts index 960d18f3..6899d930 100644 --- a/mcp-server/src/types/tool-responses.ts +++ b/mcp-server/src/types/tool-responses.ts @@ -47,7 +47,7 @@ export type ToolResponse = | GenerateTotpResponse; export interface ToolResultContent { - type: string; + type: 'text'; text: string; } diff --git a/package-lock.json b/package-lock.json index b3095922..81cfd313 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.38", + "@modelcontextprotocol/sdk": "^1.26.0", "@temporalio/activity": "^1.11.0", "@temporalio/client": "^1.11.0", "@temporalio/worker": "^1.11.0", @@ -21,6 +22,7 @@ "figlet": "^1.9.3", "gradient-string": "^3.0.0", "js-yaml": "^4.1.0", + "openai": "^4.77.0", "zod": "^4.3.6", "zx": "^8.0.0" }, @@ -83,6 +85,18 @@ "node": ">=6" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -537,6 +551,63 @@ "tslib": "2" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -607,7 +678,6 @@ "integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -999,6 +1069,16 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, "node_modules/@types/tinycolor2": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", @@ -1175,12 +1255,49 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1200,12 +1317,23 @@ "acorn": "^8.14.0" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1326,6 +1454,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.9.14", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", @@ -1335,6 +1469,46 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/boxen": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", @@ -1376,7 +1550,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1397,6 +1570,44 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", @@ -1567,6 +1778,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", @@ -1576,6 +1799,118 @@ "node": ">=20" } }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -1588,6 +1923,26 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -1600,6 +1955,15 @@ "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", @@ -1613,12 +1977,57 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1628,6 +2037,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -1671,6 +2086,15 @@ "node": ">=4.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -1689,41 +2113,222 @@ "node": ">=0.8.x" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/figlet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.3.tgz", - "integrity": "sha512-majPgOpVtrZN1iyNGbsUP6bOtZ6eaJgg5HHh0vFvm5DJhh8dc+FJpOC4GABvMZ/A7XHAJUuJujhgUY/2jPWgMA==", + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", "dependencies": { - "commander": "^14.0.0" - }, - "bin": { - "figlet": "bin/index.js" + "eventsource-parser": "^3.0.1" }, "engines": { - "node": ">= 17.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/figlet": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.3.tgz", + "integrity": "sha512-majPgOpVtrZN1iyNGbsUP6bOtZ6eaJgg5HHh0vFvm5DJhh8dc+FJpOC4GABvMZ/A7XHAJUuJujhgUY/2jPWgMA==", + "license": "MIT", + "dependencies": { + "commander": "^14.0.0" + }, + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 17.0.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/fs-monkey": { @@ -1732,6 +2337,15 @@ "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", "license": "Unlicense" }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1753,6 +2367,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-to-regex.js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", @@ -1775,6 +2426,18 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "license": "BSD-2-Clause" }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1803,6 +2466,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/heap-js": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz", @@ -1812,6 +2514,50 @@ "node": ">=10.0.0" } }, + "node_modules/hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/humanize-ms/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", @@ -1833,6 +2579,30 @@ "node": ">=0.10.0" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1842,6 +2612,18 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -1856,6 +2638,15 @@ "node": ">= 10.13.0" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1880,6 +2671,12 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", @@ -1905,6 +2702,24 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memfs": { "version": "4.51.1", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.51.1.tgz", @@ -1923,6 +2738,18 @@ "url": "https://github.com/sponsors/streamich" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1938,47 +2765,211 @@ "node": ">= 0.6" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "license": "MIT", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nexus-rpc": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.1.tgz", + "integrity": "sha512-hAWn8Hh2eewpB5McXR5EW81R3pR/ziuGhKCF3wFyUVCklanPqrIgMNr7jKCbzXeNVad0nUDfWpFRqh2u+zxQtw==", + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "undici-types": "~5.26.4" } }, - "node_modules/ms": { - "version": "3.0.0-canary.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", - "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { - "node": ">=12.13" + "node": ">= 0.8" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/nexus-rpc": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.1.tgz", - "integrity": "sha512-hAWn8Hh2eewpB5McXR5EW81R3pR/ziuGhKCF3wFyUVCklanPqrIgMNr7jKCbzXeNVad0nUDfWpFRqh2u+zxQtw==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { - "node": ">= 18.0.0" + "node": ">=8" } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/picocolors": { "version": "1.1.1", @@ -1986,6 +2977,15 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/proto3-json-serializer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", @@ -2022,6 +3022,34 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2031,6 +3059,46 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2049,6 +3117,22 @@ "node": ">=0.10.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -2103,6 +3187,63 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -2112,6 +3253,124 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -2169,6 +3428,15 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -2332,6 +3600,21 @@ "tinycolor2": "^1.0.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -2352,8 +3635,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/type-fest": { "version": "4.41.0", @@ -2367,6 +3649,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2395,6 +3716,15 @@ "fs-monkey": "^1.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2438,6 +3768,15 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/watchpack": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", @@ -2451,12 +3790,26 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack": { "version": "5.104.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -2509,6 +3862,31 @@ "node": ">=10.13.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/widest-line": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", @@ -2541,6 +3919,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2623,11 +4007,19 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, "node_modules/zx": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.1.tgz", diff --git a/package.json b/package.json index c38b5ddb..311bde8c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.38", + "@modelcontextprotocol/sdk": "^1.26.0", + "openai": "^4.77.0", "@temporalio/activity": "^1.11.0", "@temporalio/client": "^1.11.0", "@temporalio/worker": "^1.11.0", diff --git a/prompts/pipeline-testing/pre-recon-code.txt b/prompts/pipeline-testing/pre-recon-code.txt index 3911d8a8..634e5663 100644 --- a/prompts/pipeline-testing/pre-recon-code.txt +++ b/prompts/pipeline-testing/pre-recon-code.txt @@ -1 +1 @@ -Use the save_deliverable MCP tool with `deliverable_type: "CODE_ANALYSIS"` and `content: "Pre-recon analysis complete"`. Then say "Done". \ No newline at end of file +Call save_deliverable exactly once with `deliverable_type: "CODE_ANALYSIS"` and `content: "Pre-recon analysis complete"`. After you receive a success response, do not call any more tools. Reply with "Done" only. \ No newline at end of file diff --git a/prompts/pre-recon-code.txt b/prompts/pre-recon-code.txt index 2d54c5d3..d705cf88 100644 --- a/prompts/pre-recon-code.txt +++ b/prompts/pre-recon-code.txt @@ -377,6 +377,8 @@ A component is **out-of-scope** if it **cannot** be invoked through the running - Service mesh configuration retrievers +**CRITICAL - SAVE_DELIVERABLE USAGE:** Call `save_deliverable` with type `CODE_ANALYSIS` **exactly once**. After you receive a success response from the tool, do **not** call any more tools. Reply with your completion message only (e.g. "PRE-RECON CODE ANALYSIS COMPLETE") and stop. + **COMPLETION REQUIREMENTS (ALL must be satisfied):** 1. **Systematic Analysis:** ALL phases of the task agent strategy must be completed: diff --git a/shannon b/shannon index d141ddca..4a57300d 100755 --- a/shannon +++ b/shannon @@ -47,6 +47,10 @@ Options for 'start': OUTPUT= Output directory for reports (default: ./audit-logs/) PIPELINE_TESTING=true Use minimal prompts for fast testing ROUTER=true Route requests through claude-code-router (multi-model support) + PROVIDER=openai Use OpenAI-compatible API (local models, minimax, etc.) + AI_BASE_URL= Base URL for OpenAI-compatible API (e.g. http://localhost:8080/v1) + AI_MODEL= Model name (e.g. minimax, gpt-4o) + AI_API_KEY= API key (optional for local servers) Options for 'stop': CLEAN=true Remove all data including volumes @@ -55,6 +59,7 @@ Examples: ./shannon start URL=https://example.com REPO=repo-name ./shannon start URL=https://example.com REPO=repo-name CONFIG=./config.yaml ./shannon start URL=https://example.com REPO=repo-name OUTPUT=./my-reports + ./shannon start URL=https://example.com REPO=repo-name PROVIDER=openai AI_BASE_URL=http://localhost:8080/v1 AI_MODEL=minimax ./shannon logs ID=example.com_shannon-1234567890 ./shannon query ID=shannon-1234567890 ./shannon stop CLEAN=true @@ -76,6 +81,10 @@ parse_args() { PIPELINE_TESTING=*) PIPELINE_TESTING="${arg#PIPELINE_TESTING=}" ;; REBUILD=*) REBUILD="${arg#REBUILD=}" ;; ROUTER=*) ROUTER="${arg#ROUTER=}" ;; + PROVIDER=*) PROVIDER="${arg#PROVIDER=}" ;; + AI_BASE_URL=*) AI_BASE_URL="${arg#AI_BASE_URL=}" ;; + AI_MODEL=*) AI_MODEL="${arg#AI_MODEL=}" ;; + AI_API_KEY=*) AI_API_KEY="${arg#AI_API_KEY=}" ;; esac done } @@ -134,14 +143,25 @@ cmd_start() { exit 1 fi - # Check for API key (router mode can use alternative provider API keys) - if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then + # Handle PROVIDER=openai - use OpenAI-compatible API (native, no router) + if [ "$PROVIDER" = "openai" ]; then + export AI_PROVIDER=openai + fi + + # Check for API key (router mode and OpenAI provider have alternative requirements) + if [ "$AI_PROVIDER" = "openai" ]; then + # OpenAI provider uses AI_BASE_URL, AI_MODEL, AI_API_KEY - no Anthropic key needed + if [ -z "$AI_BASE_URL" ] && [ -z "$AI_MODEL" ]; then + echo "WARNING: AI_PROVIDER=openai but AI_BASE_URL and AI_MODEL not set. Using defaults from .env" + fi + elif [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then if [ "$ROUTER" = "true" ] && { [ -n "$OPENAI_API_KEY" ] || [ -n "$OPENROUTER_API_KEY" ]; }; then # Router mode with alternative provider - set a placeholder for SDK init export ANTHROPIC_API_KEY="router-mode" else echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env" echo " (or use ROUTER=true with OPENAI_API_KEY or OPENROUTER_API_KEY)" + echo " (or use PROVIDER=openai with AI_BASE_URL, AI_MODEL, AI_API_KEY)" exit 1 fi fi diff --git a/src/ai/claude-executor.ts b/src/ai/claude-executor.ts index 8bf5b8c6..f64ad14b 100644 --- a/src/ai/claude-executor.ts +++ b/src/ai/claude-executor.ts @@ -26,6 +26,8 @@ import { detectExecutionContext, formatErrorOutput, formatCompletionMessage } fr import { createProgressManager } from './progress-manager.js'; import { createAuditLogger } from './audit-logger.js'; import { getActualModelName } from './router-utils.js'; +import { isOpenAIProvider } from './provider-config.js'; +import { openaiQuery } from './openai-executor.js'; declare global { var SHANNON_DISABLE_LOADER: boolean | undefined; @@ -243,7 +245,9 @@ export async function runClaudePrompt( fullPrompt, options, { execContext, description, colorFn, progress, auditLogger }, - timer + timer, + sourceDir, + agentName ); turnCount = messageLoopResult.turnCount; @@ -336,7 +340,9 @@ async function processMessageStream( fullPrompt: string, options: NonNullable[0]['options']>, deps: MessageLoopDeps, - timer: Timer + timer: Timer, + sourceDir: string, + agentName: string | null ): Promise { const { execContext, description, colorFn, progress, auditLogger } = deps; const HEARTBEAT_INTERVAL = 30000; @@ -348,7 +354,20 @@ async function processMessageStream( let model: string | undefined; let lastHeartbeat = Date.now(); - for await (const message of query({ prompt: fullPrompt, options })) { + const messageSource = isOpenAIProvider() + ? openaiQuery({ + prompt: fullPrompt, + options: { + cwd: sourceDir, + maxTurns: options.maxTurns ?? 10_000, + ...(options.mcpServers && { mcpServers: options.mcpServers as Record }), + }, + sourceDir, + agentName, + }) + : query({ prompt: fullPrompt, options }); + + for await (const message of messageSource) { // Heartbeat logging when loader is disabled const now = Date.now(); if (global.SHANNON_DISABLE_LOADER && now - lastHeartbeat > HEARTBEAT_INTERVAL) { diff --git a/src/ai/mcp-client.ts b/src/ai/mcp-client.ts new file mode 100644 index 00000000..f362bcf0 --- /dev/null +++ b/src/ai/mcp-client.ts @@ -0,0 +1,162 @@ +// Copyright (C) 2025 Keygraph, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation. + +/** + * MCP client for OpenAI provider - manages Shannon helper tools (direct calls) + * and Playwright MCP server (stdio protocol). + */ + +import { createSaveDeliverableHandler, generateTotp } from '../../mcp-server/dist/index.js'; +import type { DeliverableType } from '../../mcp-server/dist/types/deliverables.js'; +import type { OpenAITool } from './openai-tools.js'; + +export interface StdioMcpConfig { + command: string; + args: string[]; + env: Record; +} + +/** + * Shannon helper tools - OpenAI format definitions. + * Handlers are called directly (no MCP protocol). + */ +const DELIVERABLE_TYPES = [ + 'CODE_ANALYSIS', 'RECON', + 'INJECTION_ANALYSIS', 'INJECTION_QUEUE', 'XSS_ANALYSIS', 'XSS_QUEUE', + 'AUTH_ANALYSIS', 'AUTH_QUEUE', 'AUTHZ_ANALYSIS', 'AUTHZ_QUEUE', + 'SSRF_ANALYSIS', 'SSRF_QUEUE', + 'INJECTION_EVIDENCE', 'XSS_EVIDENCE', 'AUTH_EVIDENCE', 'AUTHZ_EVIDENCE', 'SSRF_EVIDENCE', +]; + +export function getShannonHelperTools(targetDir: string): { + tools: OpenAITool[]; + execute: (name: string, args: Record) => Promise; +} { + const saveDeliverableHandler = createSaveDeliverableHandler(targetDir); + + const tools: OpenAITool[] = [ + { + type: 'function', + function: { + name: 'save_deliverable', + description: 'Saves deliverable files with automatic validation. Queue files must have {"vulnerabilities": [...]} structure.', + parameters: { + type: 'object', + properties: { + deliverable_type: { + type: 'string', + description: `Type of deliverable to save. One of: ${DELIVERABLE_TYPES.join(', ')}`, + }, + content: { + type: 'string', + description: 'File content (markdown for analysis/evidence, JSON for queues)', + }, + }, + required: ['deliverable_type', 'content'], + }, + }, + }, + { + type: 'function', + function: { + name: 'generate_totp', + description: 'Generates 6-digit TOTP code for authentication. Secret must be base32-encoded.', + parameters: { + type: 'object', + properties: { + secret: { + type: 'string', + description: 'Base32-encoded TOTP secret', + }, + }, + required: ['secret'], + }, + }, + }, + ]; + + async function execute(name: string, args: Record): Promise { + if (name === 'save_deliverable') { + const result = await saveDeliverableHandler({ + deliverable_type: args.deliverable_type as DeliverableType, + content: args.content as string, + }); + return result.content[0]?.text ?? JSON.stringify(result); + } + if (name === 'generate_totp') { + const result = await generateTotp({ secret: args.secret as string }); + return result.content[0]?.text ?? JSON.stringify(result); + } + return JSON.stringify({ error: `Unknown Shannon helper tool: ${name}` }); + } + + return { tools, execute }; +} + +export interface PlaywrightMcpClient { + tools: OpenAITool[]; + executeTool: (name: string, args: Record) => Promise; + close: () => void; +} + +/** + * Create Playwright MCP client - spawns stdio process and connects via MCP protocol. + * Uses dynamic import for @modelcontextprotocol/sdk to avoid loading when not in OpenAI mode. + */ +export async function createPlaywrightMcpClient( + config: StdioMcpConfig +): Promise { + const { Client } = await import('@modelcontextprotocol/sdk/client/index.js'); + const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js'); + + const transport = new StdioClientTransport({ + command: config.command, + args: config.args, + env: config.env, + }); + + const client = new Client({ + name: 'shannon-openai', + version: '1.0.0', + }); + + await client.connect(transport); + + const mcpTools = await client.listTools(); + const tools = mcpTools.tools.map((t) => { + const schema = (t.inputSchema as { type?: string; properties?: Record; required?: string[] }) ?? {}; + return { + type: 'function' as const, + function: { + name: t.name, + description: t.description ?? '', + parameters: { + type: 'object' as const, + properties: (schema.properties ?? {}) as Record, + required: schema.required, + }, + }, + }; + }) as OpenAITool[]; + + async function executeTool(name: string, args: Record): Promise { + const result = await client.callTool({ name, arguments: args }); + const content = result.content; + if (Array.isArray(content) && content.length > 0) { + const part = content[0]; + if (part && 'text' in part) { + return part.text; + } + } + return JSON.stringify(content); + } + + return { + tools, + executeTool, + close: () => client.close(), + }; +} diff --git a/src/ai/openai-executor.ts b/src/ai/openai-executor.ts new file mode 100644 index 00000000..569b2d7f --- /dev/null +++ b/src/ai/openai-executor.ts @@ -0,0 +1,264 @@ +// Copyright (C) 2025 Keygraph, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation. + +/** + * OpenAI-compatible provider executor. + * Implements agentic loop with tool calling, yielding messages in Claude SDK format. + */ + +import OpenAI from 'openai'; +import { getProviderConfig } from './provider-config.js'; +import { + getBuiltinOpenAITools, + executeBuiltinTool, + isBuiltinTool, + type OpenAITool, +} from './openai-tools.js'; +import { getShannonHelperTools, createPlaywrightMcpClient, type StdioMcpConfig } from './mcp-client.js'; +import { getPromptNameForAgent } from '../types/agents.js'; +import { MCP_AGENT_MAPPING } from '../constants.js'; +import type { AgentName } from '../types/index.js'; + +const MAX_TURNS = 10_000; + +export interface OpenAIOptions { + cwd: string; + maxTurns?: number; + mcpServers?: Record | undefined; +} + +export interface OpenAIQueryParams { + prompt: string; + options: OpenAIOptions; + sourceDir: string; + agentName: string | null; +} + +type MessageStreamYield = + | { type: 'system'; subtype: 'init'; model?: string; permissionMode?: string; mcp_servers?: Array<{ name: string; status: string }> } + | { type: 'assistant'; message: { content: Array<{ type?: string; text?: string }> | string } } + | { type: 'tool_use'; name: string; input?: Record } + | { type: 'tool_result'; content?: unknown } + | { type: 'result'; result?: string; total_cost_usd?: number; duration_ms?: number }; + +function buildPlaywrightStdioConfig( + sourceDir: string, + agentName: string | null +): StdioMcpConfig | null { + if (!agentName) return null; + + const promptName = getPromptNameForAgent(agentName as AgentName); + const playwrightMcpName = MCP_AGENT_MAPPING[promptName as keyof typeof MCP_AGENT_MAPPING] || null; + if (!playwrightMcpName) return null; + + const userDataDir = `/tmp/${playwrightMcpName}`; + const isDocker = process.env.SHANNON_DOCKER === 'true'; + + const mcpArgs: string[] = [ + '@playwright/mcp@latest', + '--isolated', + '--user-data-dir', userDataDir, + ]; + + if (isDocker) { + mcpArgs.push('--executable-path', '/usr/bin/chromium-browser'); + mcpArgs.push('--browser', 'chromium'); + } + + const envVars: Record = Object.fromEntries( + Object.entries({ + ...process.env, + PLAYWRIGHT_HEADLESS: 'true', + ...(isDocker && { PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' }), + }).filter((entry): entry is [string, string] => entry[1] !== undefined) + ); + + return { + command: 'npx', + args: mcpArgs, + env: envVars, + }; +} + +async function* openaiQueryInternal(params: OpenAIQueryParams): AsyncGenerator { + const { prompt, options, sourceDir, agentName } = params; + const config = getProviderConfig(); + const openaiConfig = config.openai; + if (!openaiConfig) { + throw new Error('OpenAI provider config missing'); + } + + const client = new OpenAI({ + baseURL: openaiConfig.baseUrl, + apiKey: openaiConfig.apiKey || 'dummy', // Some local servers accept any key + }); + + const ctx = { cwd: sourceDir }; + const tools: OpenAITool[] = [...getBuiltinOpenAITools()]; + const toolHandlers: Map) => Promise> = new Map(); + + // Add Shannon helper tools + const shannonHelper = getShannonHelperTools(sourceDir); + tools.push(...shannonHelper.tools); + for (const t of shannonHelper.tools) { + toolHandlers.set(t.function.name, (args) => shannonHelper.execute(t.function.name, args)); + } + + // Optionally add Playwright MCP + let playwrightClient: Awaited> | null = null; + const playwrightConfig = buildPlaywrightStdioConfig(sourceDir, agentName); + if (playwrightConfig) { + try { + playwrightClient = await createPlaywrightMcpClient(playwrightConfig); + tools.push(...playwrightClient.tools); + for (const t of playwrightClient.tools) { + toolHandlers.set(t.function.name, (args) => playwrightClient!.executeTool(t.function.name, args)); + } + } catch (err) { + console.warn(`Playwright MCP failed to start: ${err instanceof Error ? err.message : err}`); + } + } + + const openaiTools = tools.map((t) => ({ + type: 'function' as const, + function: { + name: t.function.name, + description: t.function.description, + parameters: t.function.parameters, + }, + })); + + yield { + type: 'system', + subtype: 'init', + model: openaiConfig.model, + permissionMode: 'bypassPermissions', + mcp_servers: [ + { name: 'shannon-helper', status: 'connected' }, + ...(playwrightClient ? [{ name: 'playwright', status: 'connected' as const }] : []), + ], + }; + + const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [ + { role: 'user', content: prompt }, + ]; + + const maxTurns = options.maxTurns ?? MAX_TURNS; + const startTime = Date.now(); + let lastAssistantContent = ''; + const savedDeliverableTypes = new Set(); + + for (let turn = 0; turn < maxTurns; turn++) { + const requestParams: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming = { + model: openaiConfig.model, + messages, + stream: false, + }; + if (openaiTools.length > 0) { + requestParams.tools = openaiTools; + } + const response = await client.chat.completions.create(requestParams); + + const choice = response.choices[0]; + if (!choice?.message) { + throw new Error('Empty response from OpenAI-compatible API'); + } + + const msg = choice.message; + const content = typeof msg.content === 'string' ? msg.content : msg.content ?? ''; + + if (content) { + lastAssistantContent = content; + yield { + type: 'assistant', + message: { content: [{ type: 'text', text: content }] }, + }; + } + + const toolCalls = msg.tool_calls ?? []; + if (toolCalls.length === 0) { + break; + } + + messages.push({ + role: 'assistant', + content: msg.content, + tool_calls: toolCalls, + }); + + for (const tc of toolCalls) { + const name = tc.function?.name ?? 'unknown'; + let args: Record = {}; + try { + args = typeof tc.function?.arguments === 'string' + ? (JSON.parse(tc.function.arguments) as Record) + : {}; + } catch { + args = {}; + } + + yield { type: 'tool_use', name, input: args }; + + let result: string; + if (name === 'save_deliverable') { + const deliverableType = String(args.deliverable_type ?? ''); + if (savedDeliverableTypes.has(deliverableType)) { + result = JSON.stringify({ + status: 'success', + message: 'This deliverable was already saved. Do not call save_deliverable again. Reply with your completion message only (e.g. "PRE-RECON CODE ANALYSIS COMPLETE" or "Done") and no further tool calls.', + }); + } else { + const handler = toolHandlers.get(name); + result = handler ? await handler(args) : JSON.stringify({ error: 'Unknown tool' }); + try { + const parsed = JSON.parse(result) as { status?: string }; + if (parsed.status === 'success') { + savedDeliverableTypes.add(deliverableType); + } + } catch { + // ignore parse errors + } + } + } else { + const handler = toolHandlers.get(name); + if (handler) { + result = await handler(args); + } else if (isBuiltinTool(name)) { + result = await executeBuiltinTool(name, args, ctx); + } else { + result = JSON.stringify({ error: `Unknown tool: ${name}` }); + } + } + + yield { type: 'tool_result', content: result }; + + messages.push({ + role: 'tool', + tool_call_id: tc.id ?? `call_${turn}_${name}`, + content: result, + }); + } + } + + playwrightClient?.close(); + + const durationMs = Date.now() - startTime; + + yield { + type: 'result', + result: lastAssistantContent, + total_cost_usd: 0, + duration_ms: durationMs, + }; +} + +/** + * Async generator that yields messages in Claude SDK format. + * Use with processMessageStream for compatibility with existing pipeline. + */ +export async function* openaiQuery(params: OpenAIQueryParams): AsyncGenerator { + yield* openaiQueryInternal(params); +} diff --git a/src/ai/openai-tools.ts b/src/ai/openai-tools.ts new file mode 100644 index 00000000..d51b11b4 --- /dev/null +++ b/src/ai/openai-tools.ts @@ -0,0 +1,394 @@ +// Copyright (C) 2025 Keygraph, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation. + +/** + * Built-in tools for OpenAI-compatible provider. + * Replicates the core capabilities of the Claude Agent SDK's built-in toolset. + */ + +import { exec } from 'child_process'; +import { promisify } from 'util'; +import type { Dirent } from 'fs'; +import { fs, path } from 'zx'; + +const execAsync = promisify(exec); + +const BASH_TIMEOUT_MS = 60000; // 1 minute +const BASH_MAX_OUTPUT = 500 * 1024; // 500KB +const SEARCH_MAX_FILES = 1000; +const SEARCH_MAX_MATCHES = 100; + +export interface OpenAITool { + type: 'function'; + function: { + name: string; + description: string; + parameters: { + type: 'object'; + properties: Record; + required?: string[]; + }; + }; +} + +export interface ToolHandlerContext { + cwd: string; +} + +export type ToolHandler = ( + args: Record, + ctx: ToolHandlerContext +) => Promise; + +function resolvePath(cwd: string, relativePath: string): string { + const resolved = path.resolve(cwd, relativePath); + if (!resolved.startsWith(path.resolve(cwd))) { + throw new Error(`Path traversal not allowed: ${relativePath}`); + } + return resolved; +} + +async function bashHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const command = args.command; + if (typeof command !== 'string' || !command.trim()) { + return JSON.stringify({ error: 'Missing or invalid command' }); + } + + try { + const { stdout, stderr } = await execAsync(command, { + cwd: ctx.cwd, + timeout: BASH_TIMEOUT_MS, + maxBuffer: BASH_MAX_OUTPUT, + shell: '/bin/bash', + }); + + const output = [stdout, stderr].filter(Boolean).join('\n').trim(); + return output || '(command completed with no output)'; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Command failed: ${message}` }); + } +} + +async function readFileHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const filePath = args.path; + if (typeof filePath !== 'string') { + return JSON.stringify({ error: 'Missing path' }); + } + + try { + const resolved = resolvePath(ctx.cwd, filePath); + const content = await fs.readFile(resolved, 'utf-8'); + + const startLine = typeof args.start_line === 'number' ? args.start_line : undefined; + const endLine = typeof args.end_line === 'number' ? args.end_line : undefined; + + if (startLine !== undefined || endLine !== undefined) { + const lines = content.split('\n'); + const start = Math.max(0, (startLine ?? 1) - 1); + const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length; + return lines.slice(start, end).join('\n'); + } + + return content; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Failed to read file: ${message}` }); + } +} + +async function writeFileHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const filePath = args.path; + const contents = args.contents; + + if (typeof filePath !== 'string') { + return JSON.stringify({ error: 'Missing path' }); + } + if (typeof contents !== 'string') { + return JSON.stringify({ error: 'Missing contents' }); + } + + try { + const resolved = resolvePath(ctx.cwd, filePath); + await fs.writeFile(resolved, contents, 'utf-8'); + return JSON.stringify({ success: true, path: resolved }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Failed to write file: ${message}` }); + } +} + +async function editFileHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const filePath = args.path; + const oldString = args.old_string; + const newString = args.new_string; + + if (typeof filePath !== 'string') { + return JSON.stringify({ error: 'Missing path' }); + } + if (typeof oldString !== 'string') { + return JSON.stringify({ error: 'Missing old_string' }); + } + if (typeof newString !== 'string') { + return JSON.stringify({ error: 'Missing new_string' }); + } + + try { + const resolved = resolvePath(ctx.cwd, filePath); + const content = await fs.readFile(resolved, 'utf-8'); + + if (!content.includes(oldString)) { + return JSON.stringify({ error: 'old_string not found in file' }); + } + + const newContent = content.replace(oldString, newString); + await fs.writeFile(resolved, newContent, 'utf-8'); + return JSON.stringify({ success: true, path: resolved }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Failed to edit file: ${message}` }); + } +} + +async function searchFilesHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const pattern = args.pattern; + const directory = args.directory; + + if (typeof pattern !== 'string') { + return JSON.stringify({ error: 'Missing pattern' }); + } + + const patternStr = pattern; + + const searchDir = typeof directory === 'string' + ? resolvePath(ctx.cwd, directory) + : ctx.cwd; + + try { + const results: Array<{ path: string; line: number; content: string }> = []; + let fileCount = 0; + + async function searchDirRecursive(dir: string): Promise { + if (fileCount >= SEARCH_MAX_FILES || results.length >= SEARCH_MAX_MATCHES) return; + + const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []); + for (const entry of entries) { + if (fileCount >= SEARCH_MAX_FILES || results.length >= SEARCH_MAX_MATCHES) return; + + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + const base = path.basename(entry.name); + if (base === 'node_modules' || base === '.git' || base === 'dist' || base === 'build') continue; + await searchDirRecursive(fullPath); + } else if (entry.isFile()) { + fileCount++; + try { + const content = await fs.readFile(fullPath, 'utf-8'); + const lines = content.split('\n'); + for (let i = 0; i < lines.length && results.length < SEARCH_MAX_MATCHES; i++) { + const line = lines[i] ?? ''; + if (new RegExp(patternStr, 'i').test(line)) { + results.push({ + path: path.relative(ctx.cwd, fullPath), + line: i + 1, + content: line.trim(), + }); + } + } + } catch { + // Skip binary/unreadable files + } + } + } + } + + await searchDirRecursive(searchDir); + + return JSON.stringify({ + matches: results, + truncated: results.length >= SEARCH_MAX_MATCHES, + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Search failed: ${message}` }); + } +} + +async function listDirectoryHandler( + args: Record, + ctx: ToolHandlerContext +): Promise { + const directory = args.path; + + if (typeof directory !== 'string') { + return JSON.stringify({ error: 'Missing path' }); + } + + try { + const resolved = resolvePath(ctx.cwd, directory); + const entries = await fs.readdir(resolved, { withFileTypes: true }); + + const items = entries.map((e: Dirent) => ({ + name: e.name, + type: e.isDirectory() ? 'directory' : 'file', + })); + + return JSON.stringify({ path: directory, items }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return JSON.stringify({ error: `Failed to list directory: ${message}` }); + } +} + +const BUILTIN_TOOLS: Array<{ + definition: OpenAITool; + handler: ToolHandler; +}> = [ + { + definition: { + type: 'function', + function: { + name: 'bash', + description: 'Execute a bash command in the project directory. Use for running scripts, package managers, and system commands.', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: 'Bash command to execute' }, + }, + required: ['command'], + }, + }, + }, + handler: bashHandler, + }, + { + definition: { + type: 'function', + function: { + name: 'read_file', + description: 'Read contents of a file. Optionally specify line range with start_line and end_line.', + parameters: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to file (relative to project root)' }, + start_line: { type: 'number', description: 'Optional first line to read (1-indexed)' }, + end_line: { type: 'number', description: 'Optional last line to read (1-indexed)' }, + }, + required: ['path'], + }, + }, + }, + handler: readFileHandler, + }, + { + definition: { + type: 'function', + function: { + name: 'write_file', + description: 'Write or overwrite file contents.', + parameters: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to file (relative to project root)' }, + contents: { type: 'string', description: 'Content to write' }, + }, + required: ['path', 'contents'], + }, + }, + }, + handler: writeFileHandler, + }, + { + definition: { + type: 'function', + function: { + name: 'edit_file', + description: 'Replace old_string with new_string in a file. Use for precise edits.', + parameters: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to file (relative to project root)' }, + old_string: { type: 'string', description: 'Exact string to replace' }, + new_string: { type: 'string', description: 'Replacement string' }, + }, + required: ['path', 'old_string', 'new_string'], + }, + }, + }, + handler: editFileHandler, + }, + { + definition: { + type: 'function', + function: { + name: 'search_files', + description: 'Search for a regex pattern across files in a directory. Returns matching lines with file paths and line numbers.', + parameters: { + type: 'object', + properties: { + pattern: { type: 'string', description: 'Regex pattern to search for' }, + directory: { type: 'string', description: 'Optional directory to search (default: project root)' }, + }, + required: ['pattern'], + }, + }, + }, + handler: searchFilesHandler, + }, + { + definition: { + type: 'function', + function: { + name: 'list_directory', + description: 'List contents of a directory.', + parameters: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to directory (relative to project root)' }, + }, + required: ['path'], + }, + }, + }, + handler: listDirectoryHandler, + }, +]; + +export function getBuiltinOpenAITools(): OpenAITool[] { + return BUILTIN_TOOLS.map((t) => t.definition); +} + +export async function executeBuiltinTool( + name: string, + args: Record, + ctx: ToolHandlerContext +): Promise { + const tool = BUILTIN_TOOLS.find((t) => t.definition.function.name === name); + if (!tool) { + return JSON.stringify({ error: `Unknown built-in tool: ${name}` }); + } + return tool.handler(args, ctx); +} + +export function isBuiltinTool(name: string): boolean { + return BUILTIN_TOOLS.some((t) => t.definition.function.name === name); +} diff --git a/src/ai/provider-config.ts b/src/ai/provider-config.ts new file mode 100644 index 00000000..d29886ff --- /dev/null +++ b/src/ai/provider-config.ts @@ -0,0 +1,54 @@ +// Copyright (C) 2025 Keygraph, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation. + +/** + * Provider selection and configuration for AI execution. + * Supports 'claude' (Claude Agent SDK) and 'openai' (OpenAI-compatible APIs). + */ + +export type AIProvider = 'claude' | 'openai'; + +export interface OpenAIProviderConfig { + baseUrl: string; + apiKey: string; + model: string; +} + +export interface ProviderConfig { + provider: AIProvider; + openai?: OpenAIProviderConfig; +} + +/** + * Get the current AI provider configuration from environment variables. + */ +export function getProviderConfig(): ProviderConfig { + const provider = (process.env.AI_PROVIDER ?? 'claude').toLowerCase() as AIProvider; + + if (provider === 'openai') { + const baseUrl = process.env.AI_BASE_URL ?? 'https://api.openai.com/v1'; + const apiKey = process.env.AI_API_KEY ?? ''; + const model = process.env.AI_MODEL ?? 'gpt-4o'; + + return { + provider: 'openai', + openai: { + baseUrl: baseUrl.replace(/\/$/, ''), // trim trailing slash + apiKey, + model, + }, + }; + } + + return { provider: 'claude' }; +} + +/** + * Check if the OpenAI-compatible provider is active. + */ +export function isOpenAIProvider(): boolean { + return getProviderConfig().provider === 'openai'; +} From 2aee6fc81a30d8b1646607bc9c40d3750a908eec Mon Sep 17 00:00:00 2001 From: Joshua Christman Date: Tue, 10 Feb 2026 23:32:26 -0600 Subject: [PATCH 2/3] - Added legacy peer deps to avoid conflicts between anthropic-ai and openai library zod versions --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index bcfb23cf..cf4a9609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -112,6 +112,9 @@ WORKDIR /app COPY package*.json ./ COPY mcp-server/package*.json ./mcp-server/ +# openai wants zod ^3, @anthropic-ai/claude-agent-sdk wants zod ^4 - use legacy peer deps for all npm ops +ENV npm_config_legacy_peer_deps=true + # Install Node.js dependencies (including devDependencies for TypeScript build) RUN npm ci && \ cd mcp-server && npm ci && cd .. && \ From c6456744f8f6b3b1b3ff8f017d0b5ab93f676b7a Mon Sep 17 00:00:00 2001 From: Joshua Christman Date: Thu, 12 Feb 2026 12:21:52 -0600 Subject: [PATCH 3/3] - Added a semaphore for maximum concurrent requests so that local AI doesn't end up exhausting resources on the graphics card. Also added more logging to make it so that the values are output in the workflow.log for easy viewing --- .env.example | 2 ++ docker-compose.yml | 2 ++ shannon | 15 ++++++-- src/ai/openai-executor.ts | 67 ++++++++++++++++++++++++++++++++++-- src/audit/workflow-logger.ts | 29 +++++++++++++--- 5 files changed, 105 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 33f12afc..d4474765 100644 --- a/.env.example +++ b/.env.example @@ -33,6 +33,8 @@ ANTHROPIC_API_KEY=your-api-key-here # AI_BASE_URL=http://localhost:8080/v1 # AI_MODEL=minimax # AI_API_KEY=your-key-or-empty-for-local +# AI_REQUEST_TIMEOUT_MS=120000 # 2 min default; increase for slow local models +# AI_MAX_CONCURRENT_REQUESTS=2 # Max simultaneous completions API calls (for rate-limiting local models) # ============================================================================= # Available Models diff --git a/docker-compose.yml b/docker-compose.yml index 25d0dad8..b0653b5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,8 @@ services: - AI_BASE_URL=${AI_BASE_URL:-} - AI_MODEL=${AI_MODEL:-} - AI_API_KEY=${AI_API_KEY:-} + - AI_REQUEST_TIMEOUT_MS=${AI_REQUEST_TIMEOUT_MS:-120000} + - AI_MAX_CONCURRENT_REQUESTS=${AI_MAX_CONCURRENT_REQUESTS:-2} depends_on: temporal: condition: service_healthy diff --git a/shannon b/shannon index 4a57300d..86cbc327 100755 --- a/shannon +++ b/shannon @@ -5,12 +5,15 @@ set -e # Detect Podman vs Docker and set compose files accordingly # Podman doesn't support host-gateway, so we only include the Docker override for actual Docker +# Check the actual runtime (docker info), not just whether podman is installed - avoids +# false negative when both docker and podman exist (e.g. podman for other tools) COMPOSE_BASE="docker-compose.yml" -if command -v podman &>/dev/null; then - # Podman detected (either native or via Docker Desktop shim) - use base config only +if docker info 2>/dev/null | grep -qi "podman"; then + # Actually running Podman - skip override (host-gateway not supported) COMPOSE_OVERRIDE="" else - # Docker detected - include extra_hosts override for Linux localhost access + # Docker runtime - include extra_hosts for host.docker.internal (required for + # OpenAI-compatible servers on host, e.g. AI_BASE_URL=http://host.docker.internal:8080/v1) COMPOSE_OVERRIDE="-f docker-compose.docker.yml" fi COMPOSE_FILE="$COMPOSE_BASE" @@ -148,6 +151,12 @@ cmd_start() { export AI_PROVIDER=openai fi + # Export AI_* vars for docker compose (env subst in compose file) - critical for + # command-line usage; .env vars are already exported via set -a + [ -n "$AI_BASE_URL" ] && export AI_BASE_URL + [ -n "$AI_MODEL" ] && export AI_MODEL + [ -n "$AI_API_KEY" ] && export AI_API_KEY + # Check for API key (router mode and OpenAI provider have alternative requirements) if [ "$AI_PROVIDER" = "openai" ]; then # OpenAI provider uses AI_BASE_URL, AI_MODEL, AI_API_KEY - no Anthropic key needed diff --git a/src/ai/openai-executor.ts b/src/ai/openai-executor.ts index 569b2d7f..589c9e1d 100644 --- a/src/ai/openai-executor.ts +++ b/src/ai/openai-executor.ts @@ -23,6 +23,51 @@ import { MCP_AGENT_MAPPING } from '../constants.js'; import type { AgentName } from '../types/index.js'; const MAX_TURNS = 10_000; +const PLAYWRIGHT_MCP_TIMEOUT_MS = 45_000; + +function createSemaphore(max: number): { acquire: () => Promise; release: () => void } { + let permits = max; + const waitQueue: Array<() => void> = []; + return { + acquire: () => { + if (permits > 0) { + permits--; + return Promise.resolve(); + } + return new Promise((resolve) => { + waitQueue.push(() => { + permits--; + resolve(); + }); + }); + }, + release: () => { + permits++; + if (waitQueue.length > 0) { + const next = waitQueue.shift()!; + next(); + } + }, + }; +} + +let requestSemaphore: ReturnType | null = null; + +function getRequestSemaphore(): ReturnType { + if (!requestSemaphore) { + const max = Math.max(1, parseInt(process.env.AI_MAX_CONCURRENT_REQUESTS ?? '2', 10) || 2); + requestSemaphore = createSemaphore(max); + } + return requestSemaphore; +} + +function withTimeout(promise: Promise, ms: number, message: string): Promise { + let timeoutId: ReturnType; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error(message)), ms); + }); + return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId!)); +} export interface OpenAIOptions { cwd: string; @@ -44,6 +89,8 @@ type MessageStreamYield = | { type: 'tool_result'; content?: unknown } | { type: 'result'; result?: string; total_cost_usd?: number; duration_ms?: number }; +const SKIP_PLAYWRIGHT_PROMPTS = new Set(['pre-recon-code', 'report-executive']); + function buildPlaywrightStdioConfig( sourceDir: string, agentName: string | null @@ -51,6 +98,8 @@ function buildPlaywrightStdioConfig( if (!agentName) return null; const promptName = getPromptNameForAgent(agentName as AgentName); + if (SKIP_PLAYWRIGHT_PROMPTS.has(promptName)) return null; + const playwrightMcpName = MCP_AGENT_MAPPING[promptName as keyof typeof MCP_AGENT_MAPPING] || null; if (!playwrightMcpName) return null; @@ -91,9 +140,12 @@ async function* openaiQueryInternal(params: OpenAIQueryParams): AsyncGenerator playwrightClient!.executeTool(t.function.name, args)); @@ -160,7 +216,14 @@ async function* openaiQueryInternal(params: OpenAIQueryParams): AsyncGenerator 0) { requestParams.tools = openaiTools; } - const response = await client.chat.completions.create(requestParams); + const semaphore = getRequestSemaphore(); + await semaphore.acquire(); + let response: Awaited>; + try { + response = await client.chat.completions.create(requestParams); + } finally { + semaphore.release(); + } const choice = response.choices[0]; if (!choice?.message) { diff --git a/src/audit/workflow-logger.ts b/src/audit/workflow-logger.ts index d64ff4f4..14d03f0d 100644 --- a/src/audit/workflow-logger.ts +++ b/src/audit/workflow-logger.ts @@ -15,6 +15,7 @@ import fs from 'fs'; import path from 'path'; import { generateWorkflowLogPath, ensureDirectory, type SessionMetadata } from './utils.js'; import { formatDuration, formatTimestamp } from '../utils/formatting.js'; +import { isOpenAIProvider, getProviderConfig } from '../ai/provider-config.js'; export interface AgentLogDetails { attemptNumber?: number; @@ -83,18 +84,36 @@ export class WorkflowLogger { * Write header to log file */ private async writeHeader(): Promise { - const header = [ + const lines = [ `================================================================================`, `Shannon Pentest - Workflow Log`, `================================================================================`, `Workflow ID: ${this.sessionMetadata.id}`, `Target URL: ${this.sessionMetadata.webUrl}`, `Started: ${formatTimestamp()}`, - `================================================================================`, - ``, - ].join('\n'); + ]; + + if (isOpenAIProvider()) { + const config = getProviderConfig(); + const openai = config.openai; + const maxConcurrent = parseInt(process.env.AI_MAX_CONCURRENT_REQUESTS ?? '2', 10) || 2; + const requestTimeoutMs = parseInt(process.env.AI_REQUEST_TIMEOUT_MS ?? '120000', 10) || 120000; + const requestTimeoutSec = Math.round(requestTimeoutMs / 1000); + lines.push( + `--------------------------------------------------------------------------------`, + `OpenAI Provider`, + `--------------------------------------------------------------------------------`, + `Model: ${openai?.model ?? 'N/A'}`, + `API URL: ${openai?.baseUrl ?? 'N/A'}`, + `Max concurrent: ${maxConcurrent}`, + `Request timeout: ${requestTimeoutSec}s`, + `--------------------------------------------------------------------------------` + ); + } + + lines.push(`================================================================================`, ``); - return this.writeRaw(header); + return this.writeRaw(lines.join('\n')); } /**