From debf584b40bdaf67e453b9b899a77dfd254a6387 Mon Sep 17 00:00:00 2001 From: Pradeep Kumar Date: Fri, 10 Apr 2026 11:14:37 +0530 Subject: [PATCH] chore: added support for using the dashboard via copilot --- .env.example | 4 + package-lock.json | 263 ++++++++++++++++++++++++++++- server/package.json | 3 +- server/src/config.ts | 2 +- server/src/llm.ts | 381 +++++++++++++++++++++++++++++-------------- yarn.lock | 161 +++++++++++++++++- 6 files changed, 687 insertions(+), 127 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a800613 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# GitHub Personal Access Token — required for GitHub Models API (Copilot Pro) +# Get one at: github.com → Settings → Developer Settings → Personal Access Tokens → Classic +# No special scopes needed; access to Claude Sonnet is tied to your Copilot Pro subscription. +GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx diff --git a/package-lock.json b/package-lock.json index aef7050..4fd3aac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1379,12 +1379,21 @@ "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.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/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1469,6 +1478,18 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1482,6 +1503,18 @@ "node": ">= 0.6" } }, + "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/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1542,6 +1575,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "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/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -1930,6 +1969,18 @@ "dev": true, "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": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2086,6 +2137,15 @@ "node": ">=4.0.0" } }, + "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", @@ -2210,6 +2270,21 @@ "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/esbuild": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", @@ -2277,6 +2352,15 @@ "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", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2423,6 +2507,41 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "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", @@ -2607,6 +2726,21 @@ "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", @@ -2639,6 +2773,15 @@ "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/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3039,6 +3182,46 @@ "node": ">=10" } }, + "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.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -3108,6 +3291,51 @@ "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": { + "undici-types": "~5.26.4" + } + }, + "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", @@ -4201,6 +4429,12 @@ "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-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4288,7 +4522,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -4949,6 +5182,31 @@ "resolved": "web", "link": true }, + "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/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/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5026,6 +5284,7 @@ "cors": "^2.8.5", "express": "^4.21.1", "fast-glob": "^3.3.2", + "openai": "^4.104.0", "simple-git": "^3.27.0" }, "devDependencies": { diff --git a/server/package.json b/server/package.json index 2d763cb..78ea8c1 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "tsx watch src/index.ts", + "dev": "tsx watch --env-file=../.env src/index.ts", "build": "tsc -p tsconfig.json", "start": "node dist/index.js", "sync": "tsx src/scripts/syncRepos.ts", @@ -15,6 +15,7 @@ "cors": "^2.8.5", "express": "^4.21.1", "fast-glob": "^3.3.2", + "openai": "^4.104.0", "simple-git": "^3.27.0" }, "devDependencies": { diff --git a/server/src/config.ts b/server/src/config.ts index 6dc190f..f091213 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -33,4 +33,4 @@ export const REPOS: Record< // LLM model selection — extraction is cheap, validation/patching is expensive. export const MODEL_EXTRACT = "sonnet"; -export const MODEL_REASON = "opus"; +export const MODEL_REASON = "sonnet"; diff --git a/server/src/llm.ts b/server/src/llm.ts index 7adf80f..b4732ae 100644 --- a/server/src/llm.ts +++ b/server/src/llm.ts @@ -1,52 +1,60 @@ -import { spawn, type ChildProcess } from "node:child_process"; +import OpenAI from "openai"; +import fs from "node:fs"; +import path from "node:path"; +import fg from "fast-glob"; /** - * LLM wrapper that shells out to the local `claude` CLI in print mode. + * LLM wrapper using GitHub Models API (OpenAI-compatible endpoint). * - * This intentionally avoids @anthropic-ai/sdk so we never need an API key — - * `claude -p` uses the user's existing Claude Code login (e.g. Max plan) - * and bills against the subscription, not API credits. + * GitHub Copilot Pro gives access to Claude Sonnet and other models via: + * https://models.inference.ai.azure.com + * + * Authentication: set GITHUB_TOKEN in .env to a GitHub Personal Access Token. + * Get one at: github.com → Settings → Developer Settings → Personal Access Tokens → Classic + * (No special scopes needed beyond having an active Copilot Pro subscription.) */ -// Track every active claude subprocess we spawn so /analyze/cancel can kill -// them all without touching unrelated claude sessions on the machine. -const activeChildren = new Set(); +const client = new OpenAI({ + baseURL: "https://models.inference.ai.azure.com", + apiKey: process.env.GITHUB_TOKEN ?? "", +}); + +// GitHub Models model IDs +// Check exact IDs at: https://github.com/marketplace/models +// Click a model → "Use this model" → copy the model name shown in the code snippet +const MODEL_IDS: Record = { + sonnet: "gpt-4.1", + haiku: "gpt-4.1-mini", + opus: "gpt-4.1", +}; + +// Cancellation controllers for in-flight requests (replaces subprocess tracking) +const activeControllers = new Set(); export function killAllSubprocesses(): number { - let n = 0; - for (const child of activeChildren) { - try { - child.kill("SIGKILL"); - n++; - } catch { - /* ignore */ - } - } - activeChildren.clear(); + const n = activeControllers.size; + for (const ctrl of activeControllers) ctrl.abort(); + activeControllers.clear(); return n; } export function activeSubprocessCount(): number { - return activeChildren.size; + return activeControllers.size; } export type Model = "sonnet" | "opus" | "haiku"; export interface AskOptions { model?: Model; - /** Hard timeout for the subprocess. */ + /** Hard timeout in ms (default 180s) */ timeoutMs?: number; - /** System prompt prepended to the user prompt. */ + /** System prompt */ system?: string; - /** - * Working directory for the subprocess. Tools like Read/Grep/Glob will - * resolve relative paths against this directory. - */ + /** Working directory for file tools (Read/Write/Edit/Glob/Grep) */ cwd?: string; /** - * Allowed tool names. When provided, the CLI is launched with tool access - * (and `--permission-mode bypassPermissions`) so Claude can read/grep the - * filesystem itself. Defaults to disabled (no tools). + * When provided, runs an agentic loop giving the model access to these tools. + * Supported: "Read", "Write", "Edit", "Glob", "Grep" */ allowedTools?: string[]; } @@ -57,80 +65,231 @@ export class LLMError extends Error { } } +// ─── Tool definitions ──────────────────────────────────────────────────────── + +const ALL_TOOLS: OpenAI.Chat.Completions.ChatCompletionTool[] = [ + { + type: "function", + function: { + name: "Read", + description: "Read a file's contents. Returns the file text (truncated at 50k chars).", + parameters: { + type: "object", + properties: { + file_path: { type: "string", description: "Absolute path or path relative to cwd" }, + }, + required: ["file_path"], + }, + }, + }, + { + type: "function", + function: { + name: "Write", + description: "Write content to a file. Creates parent directories if needed.", + parameters: { + type: "object", + properties: { + file_path: { type: "string", description: "Path to write" }, + content: { type: "string", description: "Full file content" }, + }, + required: ["file_path", "content"], + }, + }, + }, + { + type: "function", + function: { + name: "Edit", + description: + "Replace an exact string in a file. Fails if old_string is not found or is ambiguous (appears more than once). Provide more surrounding context in old_string to disambiguate.", + parameters: { + type: "object", + properties: { + file_path: { type: "string" }, + old_string: { type: "string", description: "Exact text to replace (must be unique in file)" }, + new_string: { type: "string", description: "Replacement text" }, + }, + required: ["file_path", "old_string", "new_string"], + }, + }, + }, + { + type: "function", + function: { + name: "Glob", + description: "Find files matching a glob pattern. Returns newline-separated file paths.", + parameters: { + type: "object", + properties: { + pattern: { type: "string", description: "Glob pattern e.g. 'src/**/*.ts'" }, + path: { type: "string", description: "Directory to search in (defaults to cwd)" }, + }, + required: ["pattern"], + }, + }, + }, + { + type: "function", + function: { + name: "Grep", + description: "Search file contents for a regex pattern. Returns matching lines with file:line context.", + parameters: { + type: "object", + properties: { + pattern: { type: "string", description: "Regex pattern to search for" }, + path: { type: "string", description: "File or directory to search (defaults to cwd)" }, + include: { type: "string", description: "Glob file filter e.g. '*.ts'" }, + }, + required: ["pattern"], + }, + }, + }, +]; + +// ─── Tool execution ─────────────────────────────────────────────────────────── + +function resolvePath(p: string, cwd?: string): string { + return path.isAbsolute(p) ? p : path.resolve(cwd ?? process.cwd(), p); +} + +async function executeTool( + name: string, + args: Record, + cwd?: string, +): Promise { + try { + switch (name) { + case "Read": { + const abs = resolvePath(args.file_path, cwd); + let content = fs.readFileSync(abs, "utf8"); + if (content.length > 50_000) content = content.slice(0, 50_000) + "\n... [truncated at 50k chars]"; + return content; + } + + case "Write": { + const abs = resolvePath(args.file_path, cwd); + fs.mkdirSync(path.dirname(abs), { recursive: true }); + fs.writeFileSync(abs, args.content, "utf8"); + return `Wrote ${args.content.length} bytes to ${args.file_path}`; + } + + case "Edit": { + const abs = resolvePath(args.file_path, cwd); + const raw = fs.readFileSync(abs, "utf8"); + const count = raw.split(args.old_string).length - 1; + if (count === 0) return `Error: old_string not found in ${args.file_path}`; + if (count > 1) return `Error: old_string appears ${count} times in ${args.file_path} — add more surrounding context to make it unique`; + fs.writeFileSync(abs, raw.replace(args.old_string, args.new_string), "utf8"); + return `Edited ${args.file_path} successfully`; + } + + case "Glob": { + const base = args.path ? resolvePath(args.path, cwd) : (cwd ?? process.cwd()); + const files = await fg(args.pattern, { cwd: base, dot: true }); + return files.length === 0 ? "(no matches)" : files.slice(0, 200).join("\n"); + } + + case "Grep": { + const base = args.path ? resolvePath(args.path, cwd) : (cwd ?? process.cwd()); + const regex = new RegExp(args.pattern, "i"); + const results: string[] = []; + + const searchFile = (filePath: string) => { + try { + fs.readFileSync(filePath, "utf8") + .split("\n") + .forEach((line, i) => { + if (regex.test(line)) results.push(`${filePath}:${i + 1}: ${line.trim()}`); + }); + } catch { /* skip unreadable files */ } + }; + + const stat = fs.statSync(base, { throwIfNoEntry: false }); + if (stat?.isFile()) { + searchFile(base); + } else { + const files = await fg(args.include ?? "**/*", { cwd: base, dot: false }); + for (const f of files.slice(0, 500)) { + searchFile(path.join(base, f)); + if (results.length >= 300) break; + } + } + + return results.length === 0 ? "(no matches)" : results.slice(0, 300).join("\n"); + } + + default: + return `Unknown tool: ${name}`; + } + } catch (err) { + return `Tool error (${name}): ${(err as Error).message}`; + } +} + +// ─── Public API ─────────────────────────────────────────────────────────────── + /** - * Send a prompt to Claude via the CLI. Returns the raw text response. + * Send a prompt to the model. Returns raw text. + * If allowedTools is provided, runs an agentic tool-use loop. */ -export function ask(prompt: string, opts: AskOptions = {}): Promise { - const { - model = "sonnet", - timeoutMs = 180_000, - system, - cwd, - allowedTools, - } = opts; - - const args = [ - "-p", - "--model", - model, - "--output-format", - "text", - "--no-session-persistence", - ]; - if (system) args.push("--append-system-prompt", system); - - if (allowedTools && allowedTools.length > 0) { - args.push("--allowed-tools", allowedTools.join(" ")); - args.push("--permission-mode", "bypassPermissions"); - } else { - // No tool use — disable everything for speed. - args.push("--tools", ""); - } +export async function ask(prompt: string, opts: AskOptions = {}): Promise { + const { model = "sonnet", timeoutMs = 180_000, system, cwd, allowedTools } = opts; + const modelId = MODEL_IDS[model] ?? MODEL_IDS.sonnet; - return new Promise((resolve, reject) => { - const child = spawn("claude", args, { - stdio: ["pipe", "pipe", "pipe"], - env: process.env, - cwd, - }); - activeChildren.add(child); - - let stdout = ""; - let stderr = ""; - const timer = setTimeout(() => { - child.kill("SIGKILL"); - reject(new LLMError(`claude CLI timed out after ${timeoutMs}ms`, stderr)); - }, timeoutMs); - - const cleanup = () => { - clearTimeout(timer); - activeChildren.delete(child); - }; - - child.stdout.on("data", (chunk) => (stdout += chunk.toString())); - child.stderr.on("data", (chunk) => (stderr += chunk.toString())); - - child.on("error", (err) => { - cleanup(); - reject(new LLMError(`failed to spawn claude: ${err.message}`, stderr)); - }); - - child.on("close", (code, signal) => { - cleanup(); - if (signal === "SIGKILL") { - reject(new LLMError(`claude CLI cancelled (SIGKILL)`)); - return; - } - if (code !== 0) { - reject(new LLMError(`claude CLI exited with code ${code}`, stderr)); - return; + const controller = new AbortController(); + activeControllers.add(controller); + const timer = setTimeout(() => controller.abort(), timeoutMs); + + try { + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []; + if (system) messages.push({ role: "system", content: system }); + messages.push({ role: "user", content: prompt }); + + // ── Simple completion (no tools) ────────────────────────────────────────── + if (!allowedTools?.length) { + const resp = await client.chat.completions.create( + { model: modelId, messages }, + { signal: controller.signal }, + ); + return resp.choices[0].message.content ?? ""; + } + + // ── Agentic tool loop ───────────────────────────────────────────────────── + const tools = ALL_TOOLS.filter((t) => allowedTools.includes(t.function.name)); + + for (let iter = 0; iter < 50; iter++) { + const resp = await client.chat.completions.create( + { model: modelId, messages, tools, tool_choice: "auto" }, + { signal: controller.signal }, + ); + + const msg = resp.choices[0].message; + // Cast needed because OpenAI SDK's internal type isn't always inferred as the param union + messages.push(msg as OpenAI.Chat.Completions.ChatCompletionMessageParam); + + if (!msg.tool_calls?.length) return msg.content ?? ""; + + for (const call of msg.tool_calls) { + const result = await executeTool( + call.function.name, + JSON.parse(call.function.arguments) as Record, + cwd, + ); + messages.push({ role: "tool", tool_call_id: call.id, content: result }); } - resolve(stdout.trim()); - }); + } - child.stdin.write(prompt); - child.stdin.end(); - }); + throw new LLMError("Agent loop exceeded 50 iterations without completing"); + } catch (err) { + if ((err as Error).name === "AbortError") { + throw new LLMError(`LLM call timed out after ${timeoutMs}ms`); + } + throw err; + } finally { + clearTimeout(timer); + activeControllers.delete(controller); + } } /** @@ -142,39 +301,25 @@ export async function askJson( ): Promise { const text = await ask(prompt, opts); - // Prefer a fenced ```json``` block if present. const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i); const candidates: string[] = []; if (fenced) candidates.push(fenced[1].trim()); - // Always also try the balanced-brace extractor as a fallback. const balanced = extractBalancedJson(text); if (balanced) candidates.push(balanced); for (const c of candidates) { try { return JSON.parse(c) as T; - } catch { - /* try the next candidate */ - } + } catch { /* try next */ } } - throw new LLMError( - `LLM response was not valid JSON.\n--- raw ---\n${text}`, - ); + throw new LLMError(`LLM response was not valid JSON.\n--- raw ---\n${text}`); } -/** - * Walk forward from the first `{` or `[` and return the substring up to its - * matching brace, ignoring quoted strings. Returns null if no balanced - * value is found. - */ function extractBalancedJson(text: string): string | null { const start = text.search(/[\[{]/); if (start < 0) return null; - - let depth = 0; - let inStr = false; - let escape = false; + let depth = 0, inStr = false, escape = false; for (let i = start; i < text.length; i++) { const ch = text[i]; if (inStr) { @@ -183,11 +328,9 @@ function extractBalancedJson(text: string): string | null { else if (ch === '"') inStr = false; continue; } - if (ch === '"') { - inStr = true; - } else if (ch === "{" || ch === "[") { - depth++; - } else if (ch === "}" || ch === "]") { + if (ch === '"') inStr = true; + else if (ch === "{" || ch === "[") depth++; + else if (ch === "}" || ch === "]") { depth--; if (depth === 0) return text.slice(start, i + 1); } diff --git a/yarn.lock b/yarn.lock index 1fba063..fcadd7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -355,6 +355,14 @@ resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz" integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== +"@types/node-fetch@^2.6.4": + version "2.6.13" + resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz" + integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== + dependencies: + "@types/node" "*" + form-data "^4.0.4" + "@types/node@*", "@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^22.10.0": version "22.19.17" resolved "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz" @@ -362,6 +370,13 @@ dependencies: undici-types "~6.21.0" +"@types/node@^18.11.18": + version "18.19.130" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz" + integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== + dependencies: + undici-types "~5.26.4" + "@types/prop-types@*": version "15.7.15" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz" @@ -417,6 +432,13 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.17.0" +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" @@ -425,6 +447,13 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" @@ -460,6 +489,11 @@ array-flatten@1.1.1: resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + autoprefixer@^10.4.20: version "10.4.27" resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz" @@ -634,6 +668,13 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" @@ -722,6 +763,11 @@ deep-extend@^0.6.0: resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@~2.0.0, depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" @@ -800,6 +846,16 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + 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== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + esbuild@^0.25.0: version "0.25.12" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz" @@ -879,6 +935,11 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" @@ -974,6 +1035,30 @@ finalhandler@~1.3.1: statuses "~2.0.2" unpipe "~1.0.0" +form-data-encoder@1.7.2: + 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== + +form-data@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + 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" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -1014,7 +1099,7 @@ get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -1074,11 +1159,18 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" @@ -1097,6 +1189,13 @@ http-errors@~2.0.0, http-errors@~2.0.1: statuses "~2.0.2" toidentifier "~1.0.1" +humanize-ms@^1.2.1: + 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== + dependencies: + ms "^2.0.0" + iconv-lite@~0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -1242,7 +1341,7 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1269,7 +1368,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -ms@^2.1.3, ms@2.1.3: +ms@^2.0.0, ms@^2.1.3, ms@2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -1310,6 +1409,18 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.6.7: + 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== + dependencies: + whatwg-url "^5.0.0" + node-releases@^2.0.36: version "2.0.37" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz" @@ -1349,6 +1460,19 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +openai@^4.104.0: + version "4.104.0" + resolved "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz" + integrity sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== + 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" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" @@ -1691,6 +1815,7 @@ serve-static@~1.16.2: cors "^2.8.5" express "^4.21.1" fast-glob "^3.3.2" + openai "^4.104.0" simple-git "^3.27.0" setprototypeof@~1.2.0, setprototypeof@1.2.0: @@ -1921,6 +2046,11 @@ toidentifier@~1.0.1: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" @@ -1966,6 +2096,11 @@ typescript@^5.7.2: resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" @@ -2013,6 +2148,11 @@ vary@^1, vary@~1.1.2: optionalDependencies: fsevents "~2.3.3" +web-streams-polyfill@4.0.0-beta.3: + 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== + "web@file:/Users/pradeep.kumar/feature-gap-dashboard/web": version "0.0.1" resolved "file:web" @@ -2020,6 +2160,19 @@ vary@^1, vary@~1.1.2: react "^18.3.1" react-dom "^18.3.1" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"