From fa42c600fddfe1aae38a80c9270fbb81c8e88747 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Mon, 28 Apr 2025 17:22:10 +0200 Subject: [PATCH 01/12] feat: initial update to MCP command --- packages/cli/commands/companies.ts | 2 +- packages/cli/commands/features.ts | 2 +- packages/cli/commands/mcp.ts | 344 ++++++++++++++++++------ packages/cli/commands/rules.ts | 12 +- packages/cli/index.ts | 2 +- packages/cli/mcp/resources.ts | 55 ---- packages/cli/mcp/responses.ts | 73 ----- packages/cli/mcp/tools.ts | 217 --------------- packages/cli/package.json | 5 +- packages/cli/services/mcp.ts | 67 +++++ packages/cli/stores/config.ts | 2 +- packages/cli/utils/auth.ts | 2 +- packages/cli/utils/constants.ts | 8 +- packages/cli/utils/errors.ts | 44 --- packages/cli/utils/file.ts | 25 ++ packages/cli/utils/options.ts | 13 +- packages/cli/utils/schemas.ts | 48 ---- packages/cli/utils/{path.ts => urls.ts} | 0 18 files changed, 367 insertions(+), 554 deletions(-) delete mode 100644 packages/cli/mcp/resources.ts delete mode 100644 packages/cli/mcp/responses.ts delete mode 100644 packages/cli/mcp/tools.ts create mode 100644 packages/cli/services/mcp.ts create mode 100644 packages/cli/utils/file.ts rename packages/cli/utils/{path.ts => urls.ts} (100%) diff --git a/packages/cli/commands/companies.ts b/packages/cli/commands/companies.ts index f1fc7d16..9a31e986 100644 --- a/packages/cli/commands/companies.ts +++ b/packages/cli/commands/companies.ts @@ -11,7 +11,7 @@ import { MissingEnvIdError, } from "../utils/errors.js"; import { appIdOption, companyFilterOption } from "../utils/options.js"; -import { baseUrlSuffix } from "../utils/path.js"; +import { baseUrlSuffix } from "../utils/urls.js"; export const listCompaniesAction = async (options: { filter?: string }) => { const { baseUrl, appId } = configStore.getConfig(); diff --git a/packages/cli/commands/features.ts b/packages/cli/commands/features.ts index 12597996..ee53e44d 100644 --- a/packages/cli/commands/features.ts +++ b/packages/cli/commands/features.ts @@ -36,7 +36,7 @@ import { typesOutOption, userIdsOption, } from "../utils/options.js"; -import { baseUrlSuffix, featureUrl } from "../utils/path.js"; +import { baseUrlSuffix, featureUrl } from "../utils/urls.js"; const lf = new Intl.ListFormat("en"); diff --git a/packages/cli/commands/mcp.ts b/packages/cli/commands/mcp.ts index fe4c18d1..93a37a4a 100644 --- a/packages/cli/commands/mcp.ts +++ b/packages/cli/commands/mcp.ts @@ -1,127 +1,297 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { confirm, input, select } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import express from "express"; -import { findUp } from "find-up"; -import { readFile } from "node:fs/promises"; -import ora, { Ora } from "ora"; +import JSON5 from "json5"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, join, relative } from "node:path"; +import ora, { type Ora } from "ora"; -import { registerMcpTools } from "../mcp/tools.js"; +import { listApps } from "../services/bootstrap.js"; +import { + resolveConfigPath, + SupportedEditor, + SupportedEditors, +} from "../services/mcp.js"; import { configStore } from "../stores/config.js"; import { handleError, MissingAppIdError } from "../utils/errors.js"; -import { appIdOption, mcpSsePortOption } from "../utils/options.js"; +import { fileExists } from "../utils/file.js"; +import { appIdOption, editorOption } from "../utils/options.js"; -type MCPArgs = { - port?: "auto" | number; -}; - -export const mcpAction = async ({ port = 8050 }: MCPArgs) => { - const { appId } = configStore.getConfig(); +export const mcpAction = async (options: { + editor?: SupportedEditor; + appId?: string; +}) => { let spinner: Ora | undefined; - - if (!appId) { - return handleError(new MissingAppIdError(), "MCP"); - } + const config = configStore.getConfig(); + let selectedAppId = options.appId || config.appId; + let selectedAppName = "Default"; // Placeholder, will be fetched try { - const packageJSONPath = await findUp("package.json"); - if (!packageJSONPath) { - throw new Error("Unable to determine version using package.json."); + // 1. Select Editor + let editor = options.editor; + if (!editor) { + editor = await select({ + message: "Which editor do you want to configure?", + choices: SupportedEditors.map((name) => ({ name, value: name })), + }); } - const { version } = JSON.parse( - (await readFile(packageJSONPath, "utf-8")) ?? "{}", - ); - if (!version) { - throw new Error("Unable to determine version using package.json."); + if (!editor || !SupportedEditors.includes(editor)) { + throw new Error(`Unsupported editor: ${editor}`); } - // Create an MCP server - const mcp = new McpServer({ - name: "Bucket", - version: version, - }); + // 2. Select App ID (if not provided/in config) + if (!selectedAppId) { + spinner = ora("Loading apps...").start(); + const apps = await listApps(); + spinner.succeed("Apps loaded."); - setInterval(async () => { + const nonDemoApps = apps.filter((app) => !app.demo); + + if (nonDemoApps.length === 0) { + throw new Error( + "No non-demo apps found. Please create an app in Bucket first.", + ); + } else if (nonDemoApps.length === 1) { + selectedAppId = nonDemoApps[0].id; + selectedAppName = nonDemoApps[0].name; + console.log( + `Automatically selected app ${chalk.cyan(selectedAppName)} (${chalk.cyan(selectedAppId)}).`, + ); + } else { + const longestName = Math.max( + ...nonDemoApps.map((app) => app.name.length), + ); + selectedAppId = await select({ + message: "Select the Bucket app to use:", + choices: nonDemoApps.map((app) => ({ + name: `${app.name.padEnd(longestName, " ")} (${app.id})`, + value: app.id, + })), + }); + const selectedApp = nonDemoApps.find((app) => app.id === selectedAppId); + if (!selectedApp) throw new Error("App selection failed."); // Should not happen + selectedAppName = selectedApp.name; + } + } else { + // If appId was provided or from config, try to find its name try { - await mcp.server.ping(); - } catch (error) { - if (error instanceof Error) { - console.error(`MCP server ping failed: ${error.message}`); - } else { - console.error(`MCP server ping failed: ${error}`); - } + spinner = ora("Fetching app details...").start(); + const apps = await listApps(); // Consider caching or a dedicated getApp(id) service call + const foundApp = apps.find((app) => app.id === selectedAppId); + if (foundApp) selectedAppName = foundApp.name; + else + console.warn( + chalk.yellow(`Could not verify app name for ID: ${selectedAppId}`), + ); + spinner.succeed("App details fetched."); + } catch { + spinner?.fail("Failed to fetch app details."); + // Non-fatal, continue with default name or just ID + console.warn( + chalk.yellow("Could not fetch app details, using default name."), + ); } - }, 10000); - - const app = express(); - const transportMap = new Map(); + } - app.get("/sse", async (_req, res) => { - const transport = new SSEServerTransport("/messages", res); - const sessionId = transport.sessionId; + if (!selectedAppId) { + // This handles the case where appId wasn't provided, wasn't in config, and couldn't be selected + return handleError(new MissingAppIdError(), "MCP Configuration"); + } - // Set the onclose handler to remove the transport from the transportMap - transport.onclose = () => { - transportMap.delete(sessionId); - console.log(`Transport ${sessionId} has been closed.`); - }; + // 3. Determine Config Path + const projectPath = configStore.getProjectPath(); + const globalPath = resolveConfigPath(editor, false); + const localPath = resolveConfigPath(editor, true); + const fullLocalPath = localPath ? join(projectPath, localPath) : undefined; - // Set the onerror handler to log the error - transport.onerror = (error) => { - console.error(`Transport ${sessionId} error:`, error); - }; + if (!globalPath) { + throw new Error(`Unsupported platform for editor: ${editor}`); + } - transportMap.set(sessionId, transport); - await mcp.connect(transport); - spinner?.succeed("Client connected to MCP server."); + const targetConfigPath = await select({ + message: "Configure global or project-local settings?", + choices: [ + { name: `Global (${globalPath})`, value: globalPath }, + ...(fullLocalPath + ? [ + { + name: `Local (${relative(projectPath, fullLocalPath)})`, + value: fullLocalPath, + }, + ] + : []), + ], }); - app.post("/messages", async (req, res) => { - const sessionId = req.query.sessionId?.toString(); - if (!sessionId) { - res.status(400).json({ error: "SessionId is not found." }); - return; + // 4. Read/Parse Config File + spinner = ora( + `Reading configuration file: ${chalk.cyan(targetConfigPath)}...`, + ).start(); + let editorConfig: any = {}; + try { + if (await fileExists(targetConfigPath)) { + const content = await readFile(targetConfigPath, "utf-8"); + // Attempt to parse JSON, handle potential comments if needed (though standard settings.json shouldn't have them) + try { + editorConfig = JSON5.parse(content); + } catch (parseError) { + spinner.fail( + `Failed to parse configuration file ${chalk.cyan(targetConfigPath)}.`, + ); + throw parseError; // Re-throw original error if cleaning fails + } + spinner.succeed("Configuration file read."); + } else { + spinner.info("Configuration file not found, will create a new one."); + editorConfig = {}; // Initialize empty config if file doesn't exist + } + } catch (error: any) { + // Handle file access errors separately from parsing errors + if (error.code !== "ENOENT") { + // ENOENT means file not found, which is handled above + spinner.fail("Failed to read configuration file."); + console.error( + chalk.red(`Error reading ${targetConfigPath}: ${error.message}`), + ); + const proceed = await confirm({ + message: "Reading failed. Attempt to overwrite the file?", + default: false, + }); + if (!proceed) return; + editorConfig = {}; // Start fresh if reading failed and user agreed + } else { + // This case should ideally be caught by the fileExists check, but handle defensively + spinner.info("Configuration file not found, creating a new one."); + editorConfig = {}; } + } + + // Ensure mcpServers object exists + editorConfig.mcpServers = editorConfig.mcpServers || {}; + + // 5. Identify existing Bucket entries + const existingBucketEntries = Object.keys(editorConfig.mcpServers).filter( + (key) => /bucket/i.test(key), + ); - const transport = transportMap.get(sessionId); - if (!transport) { - res.redirect("/sse"); + // 6. Prompt for Add/Update + let targetEntryKey: string; + const defaultNewKey = `Bucket - ${selectedAppName}`; // Add part of ID for uniqueness + + if (existingBucketEntries.length === 0) { + targetEntryKey = defaultNewKey; + console.log(`Adding new MCP server entry: ${chalk.cyan(targetEntryKey)}`); + } else if (existingBucketEntries.length === 1) { + const existingKey = existingBucketEntries[0]; + const shouldUpdate = await confirm({ + message: `Found existing entry: ${chalk.cyan(existingKey)}. Update it? (Choosing 'No' will prompt for a new name)`, + default: true, + }); + if (shouldUpdate) { + targetEntryKey = existingKey; + console.log( + `Updating existing MCP server entry: ${chalk.cyan(targetEntryKey)}`, + ); + } else { + targetEntryKey = await input({ + message: "Enter a name for the new Bucket MCP server entry:", + default: defaultNewKey, + // Basic validation to prevent empty names + validate: (value) => + value.trim().length > 0 ? true : "Name cannot be empty.", + }); + } + } else { + // Multiple existing entries + const choices = [ + ...existingBucketEntries.map((key) => ({ + name: `Update: ${key}`, + value: key, + })), + { name: "Add new entry", value: "add_new" }, // Add a new entry explicitly + { name: "Cancel", value: "cancel" }, // Allow user to back out + ]; + const choice = await select({ + message: "Multiple Bucket MCP entries found. Choose an action:", + choices, + }); + + if (choice === "cancel") { + console.log("Operation cancelled."); return; + } else if (choice === "add_new") { + targetEntryKey = await input({ + message: "Enter a name for the new Bucket MCP server entry:", + default: defaultNewKey, + validate: (value) => + value.trim().length > 0 ? true : "Name cannot be empty.", + }); + } else { + // User chose an existing key to update + targetEntryKey = choice; + console.log( + `Updating existing MCP server entry: ${chalk.cyan(targetEntryKey)}`, + ); } + } - await transport.handlePostMessage(req, res); - }); + // 7. Construct MCP Entry Value + const apiUrl = configStore.getConfig("apiUrl"); // Get configured API URL + if (!apiUrl) { + throw new Error( + "API URL is not configured. Run `bucket init` or set it in the config file.", + ); + } + // Construct the MCP endpoint URL, ensuring no double slashes and adding /mcp + const mcpUrlBase = apiUrl.replace(/\/$/, "") + "/mcp"; + const mcpUrlWithAppId = `${mcpUrlBase}?appId=${selectedAppId}`; + + const newEntryValue = { + type: "stdio", + command: "npx", // Assuming npx is standard + args: [ + "mcp-remote@next", // Use the specified package + mcpUrlWithAppId, // Always include appId explicitly + ], + }; - // Register tools and resources - //registerMcpResources(mcp); - await registerMcpTools(mcp, { appId }); + // 8. Update Config Object + editorConfig.mcpServers[targetEntryKey] = newEntryValue; - const server = app.listen(port !== "auto" ? port : 0, () => { - // Get the port the server is listening on - const address = server.address(); - const assignedPort = - !!address && typeof address === "object" ? address.port : port; + // 9. Write Config File + spinner = ora( + `Writing configuration to ${chalk.cyan(targetConfigPath)}...`, + ).start(); + try { + // Ensure the directory exists before writing + await mkdir(dirname(targetConfigPath), { recursive: true }); + const configString = JSON.stringify(editorConfig, null, 2); // Pretty print JSON + await writeFile(targetConfigPath, configString); + spinner.succeed( + `Configuration updated successfully in ${chalk.cyan(targetConfigPath)}.`, + ); console.log( - `\nMCP server listening at ${chalk.cyan(`http://localhost:${assignedPort}/sse.`)}`, + chalk.grey( + "You may need to restart your editor for changes to take effect.", + ), ); - spinner = ora(`Waiting for connections...`).start(); - }); + } catch (error) { + spinner.fail("Failed to write configuration file."); + void handleError(error, "MCP Configuration"); + } } catch (error) { - spinner?.fail("MCP server failed to start."); - void handleError(error, "MCP"); + spinner?.fail("MCP configuration failed."); + void handleError(error, "MCP Configuration"); } }; export function registerMcpCommand(cli: Command) { cli .command("mcp") - .description( - "Create an model context protocol (MCP) server between your AI assistant and the Bucket API (alpha).", - ) - .action(mcpAction) - .addOption(appIdOption) - .addOption(mcpSsePortOption); + .description("Configure Bucket's remote MCP server for your AI assistant.") + .addOption(appIdOption) // Keep existing appId option + .addOption(editorOption) // Add new editor option + .action(mcpAction); // Update the config with the cli override values cli.hook("preAction", (_, command) => { diff --git a/packages/cli/commands/rules.ts b/packages/cli/commands/rules.ts index bfd9dd6c..21cae592 100644 --- a/packages/cli/commands/rules.ts +++ b/packages/cli/commands/rules.ts @@ -2,8 +2,6 @@ import { confirm } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; import { - access, - constants, mkdir, readFile, writeFile, @@ -14,6 +12,7 @@ import ora from "ora"; import { getCopilotInstructions, getCursorRules } from "../services/rules.js"; import { configStore } from "../stores/config.js"; import { handleError } from "../utils/errors.js"; +import { fileExists } from "../utils/file.js"; import { rulesFormatOption, yesOption } from "../utils/options.js"; type RulesArgs = { @@ -24,15 +23,6 @@ type RulesArgs = { const BUCKET_SECTION_START = ""; const BUCKET_SECTION_END = ""; -async function fileExists(path: string): Promise { - try { - await access(path, constants.F_OK); - return true; - } catch { - return false; - } -} - async function confirmOverwrite( filePath: string, yes: boolean, diff --git a/packages/cli/index.ts b/packages/cli/index.ts index b2fdbe01..17373213 100755 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -17,7 +17,7 @@ import { configStore } from "./stores/config.js"; import { commandName } from "./utils/commander.js"; import { handleError } from "./utils/errors.js"; import { apiUrlOption, baseUrlOption, debugOption } from "./utils/options.js"; -import { stripTrailingSlash } from "./utils/path.js"; +import { stripTrailingSlash } from "./utils/urls.js"; const skipBootstrapCommands = [/^login/, /^logout/, /^rules/]; diff --git a/packages/cli/mcp/resources.ts b/packages/cli/mcp/resources.ts deleted file mode 100644 index 613bb37d..00000000 --- a/packages/cli/mcp/resources.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - McpServer, - ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; - -import { getApp } from "../services/bootstrap.js"; -import { getFeature, listFeatureNames } from "../services/features.js"; -import { configStore } from "../stores/config.js"; -import { MissingAppIdError, MissingEnvIdError } from "../utils/errors.js"; - -export function registerMcpResources(mcp: McpServer) { - const appId = configStore.getConfig("appId"); - if (!appId) { - throw new MissingAppIdError(); - } - const app = getApp(appId); - const production = app.environments.find((e) => e.isProduction); - if (!production) { - throw new MissingEnvIdError(); - } - - mcp.resource( - "feature", - new ResourceTemplate("features://{featureId}", { - list: async () => { - const response = await listFeatureNames(appId); - return { - resources: response.map(({ id, name }) => ({ - name, - uri: `features://${id}`, - description: `Feature ${name} of the app ${app.name}.`, - mimeType: "application/json", - })), - }; - }, - }), - { - description: "Returns a specific feature by ID.", - mimeType: "application/json", - }, - async (uri, { featureId }) => { - const data = await getFeature(appId, featureId.toString(), { - envId: production.id, - }); - return { - contents: [ - { - uri: uri.href, - text: JSON.stringify(data, null, 2), - }, - ], - }; - }, - ); -} diff --git a/packages/cli/mcp/responses.ts b/packages/cli/mcp/responses.ts deleted file mode 100644 index 27c702b1..00000000 --- a/packages/cli/mcp/responses.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { FlagVersion } from "../services/features.js"; -import { JSONPrimitive } from "../utils/json.js"; - -export function textResponse(message: string, data?: JSONPrimitive) { - let response = message.trim(); - if (data) { - response += `"\n\n--- Raw JSON Response ---\n ${JSON.stringify(data, null, 2)}`; - } - return { - content: [ - { - type: "text" as const, - text: response, - }, - ], - }; -} - -export function featuresResponse(data: JSONPrimitive) { - return textResponse("List of features.", data); -} - -export function feedbackResponse(data: JSONPrimitive) { - return textResponse( - "Feedback is returned in a JSON format with pages. Feedback score is between 1 and 5, where higher means better. A score of 0 indicates that no score was provided with the feedback.", - data, - ); -} - -export function featureCreateResponse( - key: string, - link: string, - data: JSONPrimitive, -) { - return textResponse( - ` -Feature created successfully. Show this link of the feature on Bucket website to the user: ${link}. Now run the Bucket CLI in a terminal: -\`shell -$ npx bucket features types -\` -Make sure to feature flag the code with the new feature key: ${key}. -`, - data, - ); -} - -export function companiesResponse(data: JSONPrimitive) { - return textResponse("List of companies.", data); -} - -export function usersResponse(data: JSONPrimitive) { - return textResponse("List of users.", data); -} - -export function companyFeatureAccessResponse( - isEnabled: boolean, - featureKey: string, - companyId: string, -) { - return textResponse( - `${isEnabled ? "Granted" : "Revoked"} access to feature '${featureKey}' for company ID '${companyId}'.`, - ); -} - -export function updateFeatureStageResponse(featureKey: string) { - return textResponse( - `Updated flag targeted audience for feature '${featureKey}'.`, - ); -} - -export function updateFeatureAccessResponse(flagVersions: FlagVersion[]) { - return textResponse(flagVersions.map((v) => v.changeDescription).join("\n")); -} diff --git a/packages/cli/mcp/tools.ts b/packages/cli/mcp/tools.ts deleted file mode 100644 index 17db254f..00000000 --- a/packages/cli/mcp/tools.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import ora from "ora"; -import { z } from "zod"; - -import { getApp, getOrg, listSegments } from "../services/bootstrap.js"; -import { CompaniesQuerySchema, listCompanies } from "../services/companies.js"; -import { - createFeature, - FeatureAccessSchema, - FeatureCreateSchema, - listFeatureNames, - updateFeatureAccess, -} from "../services/features.js"; -import { FeedbackQuerySchema, listFeedback } from "../services/feedback.js"; -import { - listStages, - updateFeatureStage, - UpdateFeatureStageSchema, -} from "../services/stages.js"; -import { listUsers, UsersQuerySchema } from "../services/users.js"; -import { configStore } from "../stores/config.js"; -import { handleMcpError, MissingEnvIdError } from "../utils/errors.js"; -import { KeyFormatPatterns } from "../utils/gen.js"; -import { featureUrl } from "../utils/path.js"; -import { withDefaults, withDescriptions } from "../utils/schemas.js"; - -import { - companiesResponse, - featureCreateResponse, - featuresResponse, - feedbackResponse, - updateFeatureAccessResponse, - updateFeatureStageResponse, - usersResponse, -} from "./responses.js"; - -export async function registerMcpTools( - mcp: McpServer, - { appId }: { appId: string }, -) { - const org = getOrg(); - const app = getApp(appId); - const segments = listSegments(appId); - const production = app.environments.find((e) => e.isProduction); - if (!production) { - throw new MissingEnvIdError(); - } - - // Extend bootstrap spinner for loading stages - const spinner = ora("Bootstrapping...").start(); - const stages = await listStages(appId); - spinner.stop(); - - // Add features tool - mcp.tool( - "features", - "List all feature flags of the Bucket feature management service.", - async () => { - try { - const data = await listFeatureNames(appId); - return featuresResponse(data); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add feedback tool - mcp.tool( - "feedback", - "Get user feedback for the features of the Bucket feature management service.", - withDefaults(FeedbackQuerySchema, { - envId: production.id, - }).shape, - async (args) => { - try { - const data = await listFeedback(appId, args); - return feedbackResponse(data); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add create feature tool - const keyFormatRules = KeyFormatPatterns[org.featureKeyFormat].message; - mcp.tool( - "featureCreate", - "Creates a new feature flag of the Bucket feature management service.", - withDescriptions(FeatureCreateSchema, { - key: `Feature key specified in ${org.featureKeyFormat} format:\n${keyFormatRules}`, - }).shape, - async (args) => { - try { - const feature = await createFeature(appId, args); - const featureLink = featureUrl( - configStore.getConfig("baseUrl"), - production, - feature, - ); - return featureCreateResponse(feature.key, featureLink, feature); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add companies tool - mcp.tool( - "companies", - "List of companies of the Bucket feature management service.", - withDefaults(CompaniesQuerySchema, { - envId: production.id, - }).shape, - async (args) => { - try { - const data = await listCompanies(appId, args); - return companiesResponse(data); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add users tool - mcp.tool( - "users", - "List of users in your application that use the Bucket feature management service.", - withDefaults(UsersQuerySchema, { - envId: production.id, - }).shape, - async (args) => { - try { - const data = await listUsers(appId, args); - return usersResponse(data); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add company feature access tool - const segmentNames = segments.map((s) => s.name); - mcp.tool( - "featureAccess", - "Grant or revoke feature access to specific users, companies, or segments of the Bucket feature management service.", - withDefaults( - FeatureAccessSchema.omit({ segmentIds: true }).extend({ - segmentNames: z - .array(z.enum(segmentNames as [string, ...string[]])) - .optional() - .describe( - `Segment names to target. Must be one of the following: ${segmentNames.join(", ")}`, - ), - }), - { - envId: production.id, - }, - ).shape, - async (args) => { - try { - const { segmentNames: selectedSegmentNames, ...rest } = args; - const segmentIds = - selectedSegmentNames - ?.map((name) => segments.find((s) => s.name === name)?.id) - .filter((id) => id !== undefined) ?? []; - const { flagVersions } = await updateFeatureAccess(appId, { - ...rest, - segmentIds, - }); - return updateFeatureAccessResponse(flagVersions); - } catch (error) { - return await handleMcpError(error); - } - }, - ); - - // Add update feature stage tool - const stageNames = stages.map((s) => s.name); - mcp.tool( - "updateFeatureStage", - "Update the release stage of a feature in the Bucket feature management service.", - withDefaults( - UpdateFeatureStageSchema.omit({ - stageId: true, - }).extend({ - stageName: z - .enum(stageNames as [string, ...string[]]) - .describe( - `The name of the stage. Must be one of the following: ${stageNames.join(", ")}`, - ), - }), - { - envId: production.id, - }, - ).shape, - async (args) => { - const stage = stages.find((s) => s.name === args.stageName); - if (!stage) { - throw new Error(`Stage '${args.stageName}' not found.`); - } - - try { - const { feature } = await updateFeatureStage(appId, { - featureId: args.featureId, - stageId: stage.id, - targetingMode: args.targetingMode, - envId: args.envId, - changeDescription: args.changeDescription, - }); - return updateFeatureStageResponse(feature.key); - } catch (error) { - return await handleMcpError(error); - } - }, - ); -} diff --git a/packages/cli/package.json b/packages/cli/package.json index 00a24414..43adcd1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/cli", - "version": "0.5.1", + "version": "0.6.0", "packageManager": "yarn@4.1.1", "description": "CLI for Bucket service", "main": "./dist/index.js", @@ -31,7 +31,6 @@ "scripts": { "build": "tsc && shx chmod +x dist/index.js", "bucket": "yarn build && ./dist/index.js", - "mcp:inspector": "SERVER_PORT=8051 npx @modelcontextprotocol/inspector", "test": "vitest -c vite.config.js", "test:ci": "vitest run -c vite.config.js --reporter=default --reporter=junit --outputFile=junit.xml", "coverage": "vitest run --coverage", @@ -43,7 +42,6 @@ }, "dependencies": { "@inquirer/prompts": "^5.3.8", - "@modelcontextprotocol/sdk": "^1.7.0", "ajv": "^8.17.1", "chalk": "^5.3.0", "change-case": "^5.4.4", @@ -60,7 +58,6 @@ "devDependencies": { "@bucketco/eslint-config": "workspace:^", "@bucketco/tsconfig": "workspace:^", - "@modelcontextprotocol/inspector": "^0.6.0", "@types/express": "^5.0.0", "@types/node": "^22.5.1", "@types/slug": "^5.0.9", diff --git a/packages/cli/services/mcp.ts b/packages/cli/services/mcp.ts new file mode 100644 index 00000000..3b96350a --- /dev/null +++ b/packages/cli/services/mcp.ts @@ -0,0 +1,67 @@ +import { resolvePath } from "../utils/file.js"; + +export const SupportedEditors = [ + "cursor", + "vscode", + "claude", + "windsurf", +] as const; +export type SupportedEditor = (typeof SupportedEditors)[number]; + +type ConfigPaths = { + global: + | { + mac: string; + windows: string; + linux?: string; + } + | string; + local?: string; +}; + +export const ConfigPaths: Record = { + cursor: { + global: "~/.cursor/mcp.json", + local: ".cursor/mcp.json", + }, + vscode: { + global: { + mac: "~/Library/Application Support/Code/User/settings.json", + linux: "~/.config/Code/User/settings.json", + windows: "@/Code/User/settings.json", + }, + local: ".vscode/mcp.json", + }, + claude: { + global: { + mac: "~/Library/Application Support/Claude/claude_desktop_config.json", + windows: "@/Claude/claude_desktop_config.json", + }, + }, + windsurf: { + global: "~/.codeium/windsurf/mcp_config.json", + }, +}; + +export function resolveConfigPath(editor: SupportedEditor, local = false) { + const editorConfig = ConfigPaths[editor]; + const paths = local ? editorConfig.local : editorConfig.global; + + if (!paths) return undefined; + + if (typeof paths === "string") { + return resolvePath(paths); + } + + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check + switch (process.platform) { + case "darwin": + return resolvePath(paths.mac); + case "win32": + return resolvePath(paths.windows); + case "linux": + return paths.linux ? resolvePath(paths.linux) : undefined; + default: + return undefined; + } +} diff --git a/packages/cli/stores/config.ts b/packages/cli/stores/config.ts index 9bbc1ff2..130cd4a2 100644 --- a/packages/cli/stores/config.ts +++ b/packages/cli/stores/config.ts @@ -14,7 +14,7 @@ import { SCHEMA_URL, } from "../utils/constants.js"; import { ConfigValidationError, handleError } from "../utils/errors.js"; -import { stripTrailingSlash } from "../utils/path.js"; +import { stripTrailingSlash } from "../utils/urls.js"; export const typeFormats = ["react", "node"] as const; export type TypeFormat = (typeof typeFormats)[number]; diff --git a/packages/cli/utils/auth.ts b/packages/cli/utils/auth.ts index ab94c337..fd493c70 100644 --- a/packages/cli/utils/auth.ts +++ b/packages/cli/utils/auth.ts @@ -6,7 +6,7 @@ import open from "open"; import { authStore } from "../stores/auth.js"; import { configStore } from "../stores/config.js"; -import { errorUrl, loginUrl, successUrl } from "./path.js"; +import { errorUrl, loginUrl, successUrl } from "./urls.js"; import { ParamType } from "./types.js"; interface waitForAccessToken { diff --git a/packages/cli/utils/constants.ts b/packages/cli/utils/constants.ts index 844d4174..79785867 100644 --- a/packages/cli/utils/constants.ts +++ b/packages/cli/utils/constants.ts @@ -1,10 +1,8 @@ -import { join } from "path"; +import os from "node:os"; +import { join } from "node:path"; export const CONFIG_FILE_NAME = "bucket.config.json"; -export const AUTH_FILE = join( - process.env.HOME ?? process.env.USERPROFILE ?? "", - ".bucket-auth", -); +export const AUTH_FILE = join(os.homedir(), ".bucket-auth"); export const SCHEMA_URL = `https://unpkg.com/@bucketco/cli@latest/schema.json`; export const DEFAULT_BASE_URL = "https://app.bucket.co"; diff --git a/packages/cli/utils/errors.ts b/packages/cli/utils/errors.ts index 1be1bc44..7e45e79e 100644 --- a/packages/cli/utils/errors.ts +++ b/packages/cli/utils/errors.ts @@ -62,47 +62,3 @@ export async function handleError(error: unknown, tag: string) { } process.exit(1); } - -export async function handleMcpError(error: unknown): Promise<{ - isError: true; - content: Array<{ type: "text"; text: string }>; -}> { - let errorMessage: string; - - if (error instanceof Response) { - try { - const data = await error.json(); - errorMessage = `API Error: ${ - data.error?.message ?? - data.error?.code ?? - `${error.statusText} (${error.status})` - }`; - - if (data.validationErrors) { - const validationDetails = data.validationErrors - .map( - ({ path, message }: { path: string[]; message: string }) => - `- ${path.join(".")}: ${message}`, - ) - .join("\n"); - errorMessage += "\nValidation Errors:\n" + validationDetails; - } - } catch { - errorMessage = `API Error: ${error.statusText} (${error.status})`; - } - } else if (error instanceof Error) { - errorMessage = error.message; - if (error.cause) { - errorMessage += `\nCause: ${JSON.stringify(error.cause)}`; - } - } else if (typeof error === "string") { - errorMessage = error; - } else { - errorMessage = "An unknown error occurred: " + JSON.stringify(error); - } - - return { - isError: true, - content: [{ type: "text", text: errorMessage }], - }; -} diff --git a/packages/cli/utils/file.ts b/packages/cli/utils/file.ts new file mode 100644 index 00000000..0cbdcd8d --- /dev/null +++ b/packages/cli/utils/file.ts @@ -0,0 +1,25 @@ +import { access, constants } from "node:fs/promises"; +import os from "node:os"; +import { join } from "node:path"; +/** + * Checks if a file exists at the given path. + * @param path The path to the file. + * @returns True if the file exists, false otherwise. + */ +export async function fileExists(path: string): Promise { + try { + await access(path, constants.F_OK); + return true; + } catch { + return false; + } +} + +// Helper to resolve home directory +export const resolvePath = (p: string) => + join( + ...p + .replace("~", os.homedir()) + .replace("@", process.env.APPDATA ?? "") + .split("/"), + ); diff --git a/packages/cli/utils/options.ts b/packages/cli/utils/options.ts index 1ec847e1..d41cc178 100644 --- a/packages/cli/utils/options.ts +++ b/packages/cli/utils/options.ts @@ -2,6 +2,9 @@ import { Argument, Option } from "commander"; import { CONFIG_FILE_NAME } from "./constants.js"; +// Define supported editors directly here or import from a central place if needed elsewhere +const SUPPORTED_EDITORS = ["cursor", "vscode"] as const; // Add more later: "claude", "cline", "windsurf" + export const debugOption = new Option("--debug", "Enable debug mode."); export const baseUrlOption = new Option( @@ -44,11 +47,6 @@ export const featureKeyOption = new Option( "Feature key. If not provided, a key is generated from the feature's name.", ); -export const mcpSsePortOption = new Option( - "-p, --port [port]", - "Port for the MCP server to listen on when using SSE transport with the --sse flag.", -).default(8050); - export const companyFilterOption = new Option( "-f, --filter [name]", "Filter companies by name or ID.", @@ -85,6 +83,11 @@ export const segmentIdsOption = new Option( "Segment IDs to target. Can be specified multiple times.", ); +export const editorOption = new Option( + "-e, --editor [editor]", + "Specify the editor to configure for MCP.", +).choices(SUPPORTED_EDITORS); + export const rulesFormatOption = new Option( "-f, --format [format]", "Format to copy rules in", diff --git a/packages/cli/utils/schemas.ts b/packages/cli/utils/schemas.ts index 6cfb815a..0a966a4e 100644 --- a/packages/cli/utils/schemas.ts +++ b/packages/cli/utils/schemas.ts @@ -63,51 +63,3 @@ export const ExternalIdSchema = z .nonempty() .max(256) .describe("External identifier, non-empty string up to 256 characters"); - -export const withDefaults = ( - schema: z.ZodObject, - defaults: Partial>>, -): z.ZodObject => { - const entries = Object.entries(schema.shape); - - const newShape = entries.reduce( - (acc, [key, value]) => { - const defaultValue = defaults[key]; - - if (defaultValue !== undefined && value instanceof z.ZodType) { - acc[key] = value.default(defaultValue); - } else { - acc[key] = value; - } - - return acc; - }, - {} as Record, - ); - - return z.object(newShape as T); -}; - -export const withDescriptions = ( - schema: z.ZodObject, - descriptions: Partial>, -): z.ZodObject => { - const entries = Object.entries(schema.shape); - - const newShape = entries.reduce( - (acc, [key, value]) => { - const description = descriptions[key as keyof T]; - - if (description !== undefined && value instanceof z.ZodType) { - acc[key] = value.describe(description); - } else { - acc[key] = value; - } - - return acc; - }, - {} as Record, - ); - - return z.object(newShape as T); -}; diff --git a/packages/cli/utils/path.ts b/packages/cli/utils/urls.ts similarity index 100% rename from packages/cli/utils/path.ts rename to packages/cli/utils/urls.ts From 9f149cfce670fdf34e6540fe9897469aa1a37948 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 29 Apr 2025 13:31:51 +0200 Subject: [PATCH 02/12] feat: finalize mcp command --- packages/cli/commands/companies.ts | 11 +- packages/cli/commands/features.ts | 40 ++- packages/cli/commands/init.ts | 11 +- packages/cli/commands/mcp.ts | 436 +++++++++++++---------------- packages/cli/services/mcp.ts | 24 ++ packages/cli/utils/file.ts | 18 +- packages/cli/utils/options.ts | 5 + 7 files changed, 273 insertions(+), 272 deletions(-) diff --git a/packages/cli/commands/companies.ts b/packages/cli/commands/companies.ts index 9a31e986..d9ee95ca 100644 --- a/packages/cli/commands/companies.ts +++ b/packages/cli/commands/companies.ts @@ -20,13 +20,14 @@ export const listCompaniesAction = async (options: { filter?: string }) => { if (!appId) { return handleError(new MissingAppIdError(), "Companies List"); } - const app = getApp(appId); - const production = app.environments.find((e) => e.isProduction); - if (!production) { - return handleError(new MissingEnvIdError(), "Companies List"); - } try { + const app = getApp(appId); + const production = app.environments.find((e) => e.isProduction); + if (!production) { + return handleError(new MissingEnvIdError(), "Companies List"); + } + spinner = ora( `Loading companies for app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`, ).start(); diff --git a/packages/cli/commands/features.ts b/packages/cli/commands/features.ts index ee53e44d..08f3c9a5 100644 --- a/packages/cli/commands/features.ts +++ b/packages/cli/commands/features.ts @@ -4,7 +4,7 @@ import { Argument, Command } from "commander"; import { relative } from "node:path"; import ora, { Ora } from "ora"; -import { getApp, getOrg } from "../services/bootstrap.js"; +import { App, getApp, getOrg } from "../services/bootstrap.js"; import { createFeature, Feature, @@ -54,7 +54,14 @@ export const createFeatureAction = async ( if (!appId) { return handleError(new MissingAppIdError(), "Features Create"); } - const app = getApp(appId); + + let app: App; + try { + app = getApp(appId); + } catch (error) { + return handleError(error, "Features Create"); + } + const production = app.environments.find((e) => e.isProduction); try { @@ -102,13 +109,13 @@ export const listFeaturesAction = async () => { if (!appId) { return handleError(new MissingAppIdError(), "Features Create"); } - const app = getApp(appId); - const production = app.environments.find((e) => e.isProduction); - if (!production) { - return handleError(new MissingEnvIdError(), "Features Types"); - } try { + const app = getApp(appId); + const production = app.environments.find((e) => e.isProduction); + if (!production) { + return handleError(new MissingEnvIdError(), "Features Types"); + } spinner = ora( `Loading features of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`, ).start(); @@ -142,7 +149,13 @@ export const generateTypesAction = async () => { return handleError(new MissingAppIdError(), "Features Types"); } - const app = getApp(appId); + let app: App; + try { + app = getApp(appId); + } catch (error) { + return handleError(error, "Features Types"); + } + const production = app.environments.find((e) => e.isProduction); if (!production) { return handleError(new MissingEnvIdError(), "Features Types"); @@ -161,8 +174,7 @@ export const generateTypesAction = async () => { ); } catch (error) { spinner?.fail("Loading features failed."); - void handleError(error, "Features Types"); - return; + return handleError(error, "Features Types"); } try { @@ -202,7 +214,13 @@ export const featureAccessAction = async ( return handleError(new MissingAppIdError(), "Feature Access"); } - const app = getApp(appId); + let app: App; + try { + app = getApp(appId); + } catch (error) { + return handleError(error, "Features Types"); + } + const production = app.environments.find((e) => e.isProduction); if (!production) { return handleError(new MissingEnvIdError(), "Feature Access"); diff --git a/packages/cli/commands/init.ts b/packages/cli/commands/init.ts index 45ebb2af..2b8cf7b1 100644 --- a/packages/cli/commands/init.ts +++ b/packages/cli/commands/init.ts @@ -32,7 +32,7 @@ export const initAction = async (args: InitArgs = {}) => { // Load apps spinner = ora(`Loading apps from ${chalk.cyan(baseUrl)}...`).start(); - apps = await listApps(); + apps = listApps(); spinner.succeed(`Loaded apps from ${chalk.cyan(baseUrl)}.`); } catch (error) { spinner?.fail("Loading apps failed."); @@ -42,20 +42,15 @@ export const initAction = async (args: InitArgs = {}) => { try { let appId: string | undefined; - const nonDemoApps = apps.filter((app) => !app.demo); + const nonDemoApp = apps.find((app) => !app.demo); - // If there is only one non-demo app, select it automatically if (apps.length === 0) { throw new Error("You don't have any apps yet. Please create one."); - } else if (nonDemoApps.length === 1) { - appId = nonDemoApps[0].id; - console.log( - `Automatically selected app ${chalk.cyan(nonDemoApps[0].name)} (${chalk.cyan(appId)}).`, - ); } else { const longestName = Math.max(...apps.map((app) => app.name.length)); appId = await select({ message: "Select an app", + default: nonDemoApp?.id, choices: apps.map((app) => ({ name: `${app.name.padEnd(longestName, " ")}${app.demo ? " [Demo]" : ""}`, value: app.id, diff --git a/packages/cli/commands/mcp.ts b/packages/cli/commands/mcp.ts index 93a37a4a..ef3897db 100644 --- a/packages/cli/commands/mcp.ts +++ b/packages/cli/commands/mcp.ts @@ -1,4 +1,4 @@ -import { confirm, input, select } from "@inquirer/prompts"; +import { confirm, select } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; import JSON5 from "json5"; @@ -6,281 +6,232 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, relative } from "node:path"; import ora, { type Ora } from "ora"; -import { listApps } from "../services/bootstrap.js"; +import { App, listApps } from "../services/bootstrap.js"; import { + ConfigPaths, + getServersConfig, resolveConfigPath, SupportedEditor, SupportedEditors, } from "../services/mcp.js"; import { configStore } from "../stores/config.js"; -import { handleError, MissingAppIdError } from "../utils/errors.js"; +import { handleError } from "../utils/errors.js"; import { fileExists } from "../utils/file.js"; -import { appIdOption, editorOption } from "../utils/options.js"; +import { + appIdOption, + configScopeOption, + editorOption, +} from "../utils/options.js"; export const mcpAction = async (options: { editor?: SupportedEditor; appId?: string; + scope?: "local" | "global"; }) => { - let spinner: Ora | undefined; const config = configStore.getConfig(); - let selectedAppId = options.appId || config.appId; - let selectedAppName = "Default"; // Placeholder, will be fetched + let spinner: Ora | undefined; + let selectedEditor = options.editor; + let selectedApp: App | undefined; + + // Select Editor/Client + if (!selectedEditor) { + selectedEditor = await select({ + message: "Which editor do you want to configure?", + choices: SupportedEditors.map((value) => ({ + value, + name: ConfigPaths[value].name, + })), + }); + } + // Select App ID try { - // 1. Select Editor - let editor = options.editor; - if (!editor) { - editor = await select({ - message: "Which editor do you want to configure?", - choices: SupportedEditors.map((name) => ({ name, value: name })), - }); - } - if (!editor || !SupportedEditors.includes(editor)) { - throw new Error(`Unsupported editor: ${editor}`); - } + spinner = ora(`Loading apps from ${chalk.cyan(config.baseUrl)}...`).start(); + const apps = listApps(); + spinner.succeed(`Loaded apps from ${chalk.cyan(config.baseUrl)}.`); - // 2. Select App ID (if not provided/in config) - if (!selectedAppId) { - spinner = ora("Loading apps...").start(); - const apps = await listApps(); - spinner.succeed("Apps loaded."); - - const nonDemoApps = apps.filter((app) => !app.demo); - - if (nonDemoApps.length === 0) { - throw new Error( - "No non-demo apps found. Please create an app in Bucket first.", - ); - } else if (nonDemoApps.length === 1) { - selectedAppId = nonDemoApps[0].id; - selectedAppName = nonDemoApps[0].name; - console.log( - `Automatically selected app ${chalk.cyan(selectedAppName)} (${chalk.cyan(selectedAppId)}).`, - ); - } else { - const longestName = Math.max( - ...nonDemoApps.map((app) => app.name.length), - ); - selectedAppId = await select({ - message: "Select the Bucket app to use:", - choices: nonDemoApps.map((app) => ({ - name: `${app.name.padEnd(longestName, " ")} (${app.id})`, - value: app.id, - })), - }); - const selectedApp = nonDemoApps.find((app) => app.id === selectedAppId); - if (!selectedApp) throw new Error("App selection failed."); // Should not happen - selectedAppName = selectedApp.name; - } - } else { - // If appId was provided or from config, try to find its name - try { - spinner = ora("Fetching app details...").start(); - const apps = await listApps(); // Consider caching or a dedicated getApp(id) service call - const foundApp = apps.find((app) => app.id === selectedAppId); - if (foundApp) selectedAppName = foundApp.name; - else - console.warn( - chalk.yellow(`Could not verify app name for ID: ${selectedAppId}`), - ); - spinner.succeed("App details fetched."); - } catch { - spinner?.fail("Failed to fetch app details."); - // Non-fatal, continue with default name or just ID - console.warn( - chalk.yellow("Could not fetch app details, using default name."), - ); - } - } - - if (!selectedAppId) { - // This handles the case where appId wasn't provided, wasn't in config, and couldn't be selected - return handleError(new MissingAppIdError(), "MCP Configuration"); + if (apps.length === 0) { + throw new Error("You don't have any apps yet. Please create one."); } - // 3. Determine Config Path - const projectPath = configStore.getProjectPath(); - const globalPath = resolveConfigPath(editor, false); - const localPath = resolveConfigPath(editor, true); - const fullLocalPath = localPath ? join(projectPath, localPath) : undefined; - - if (!globalPath) { - throw new Error(`Unsupported platform for editor: ${editor}`); - } - - const targetConfigPath = await select({ - message: "Configure global or project-local settings?", - choices: [ - { name: `Global (${globalPath})`, value: globalPath }, - ...(fullLocalPath - ? [ - { - name: `Local (${relative(projectPath, fullLocalPath)})`, - value: fullLocalPath, - }, - ] - : []), - ], - }); - - // 4. Read/Parse Config File - spinner = ora( - `Reading configuration file: ${chalk.cyan(targetConfigPath)}...`, - ).start(); - let editorConfig: any = {}; - try { - if (await fileExists(targetConfigPath)) { - const content = await readFile(targetConfigPath, "utf-8"); - // Attempt to parse JSON, handle potential comments if needed (though standard settings.json shouldn't have them) - try { - editorConfig = JSON5.parse(content); - } catch (parseError) { - spinner.fail( - `Failed to parse configuration file ${chalk.cyan(targetConfigPath)}.`, - ); - throw parseError; // Re-throw original error if cleaning fails - } - spinner.succeed("Configuration file read."); - } else { - spinner.info("Configuration file not found, will create a new one."); - editorConfig = {}; // Initialize empty config if file doesn't exist - } - } catch (error: any) { - // Handle file access errors separately from parsing errors - if (error.code !== "ENOENT") { - // ENOENT means file not found, which is handled above - spinner.fail("Failed to read configuration file."); - console.error( - chalk.red(`Error reading ${targetConfigPath}: ${error.message}`), - ); - const proceed = await confirm({ - message: "Reading failed. Attempt to overwrite the file?", - default: false, - }); - if (!proceed) return; - editorConfig = {}; // Start fresh if reading failed and user agreed - } else { - // This case should ideally be caught by the fileExists check, but handle defensively - spinner.info("Configuration file not found, creating a new one."); - editorConfig = {}; + let selectedAppId: string; + if (options.appId) { + // If appId is provided, try to find it directly + const app = apps.find(({ id }) => id === options.appId); + if (!app) { + throw new Error(`Could not find app with ID: ${options.appId}`); } + selectedAppId = app.id; + } else { + // Otherwise, show selection prompt + const nonDemoApp = apps.find((app) => !app.demo); + const longestName = Math.max(...apps.map((app) => app.name.length)); + selectedAppId = await select({ + message: "Select an app", + default: config.appId ?? nonDemoApp?.id, + choices: apps.map((app) => ({ + name: `${app.name.padEnd(longestName, " ")}${app.demo ? " [Demo]" : ""}`, + value: app.id, + })), + }); } + selectedApp = apps.find((app) => app.id === selectedAppId)!; + } catch (error) { + spinner?.fail("Loading apps failed."); + return handleError(error, "MCP Configuration"); + } - // Ensure mcpServers object exists - editorConfig.mcpServers = editorConfig.mcpServers || {}; - - // 5. Identify existing Bucket entries - const existingBucketEntries = Object.keys(editorConfig.mcpServers).filter( - (key) => /bucket/i.test(key), - ); + // Determine Config Path + const projectPath = configStore.getProjectPath(); + const globalPath = resolveConfigPath(selectedEditor, false); + const localPath = resolveConfigPath(selectedEditor, true); + const fullLocalPath = localPath ? join(projectPath, localPath) : undefined; - // 6. Prompt for Add/Update - let targetEntryKey: string; - const defaultNewKey = `Bucket - ${selectedAppName}`; // Add part of ID for uniqueness + if (!globalPath) { + throw new Error(`Unsupported platform for editor: ${selectedEditor}`); + } - if (existingBucketEntries.length === 0) { - targetEntryKey = defaultNewKey; - console.log(`Adding new MCP server entry: ${chalk.cyan(targetEntryKey)}`); - } else if (existingBucketEntries.length === 1) { - const existingKey = existingBucketEntries[0]; - const shouldUpdate = await confirm({ - message: `Found existing entry: ${chalk.cyan(existingKey)}. Update it? (Choosing 'No' will prompt for a new name)`, - default: true, - }); - if (shouldUpdate) { - targetEntryKey = existingKey; - console.log( - `Updating existing MCP server entry: ${chalk.cyan(targetEntryKey)}`, - ); - } else { - targetEntryKey = await input({ - message: "Enter a name for the new Bucket MCP server entry:", - default: defaultNewKey, - // Basic validation to prevent empty names - validate: (value) => - value.trim().length > 0 ? true : "Name cannot be empty.", - }); - } + let configPathType: "global" | "local" = "global"; + if (fullLocalPath) { + if (options.scope) { + configPathType = options.scope; } else { - // Multiple existing entries - const choices = [ - ...existingBucketEntries.map((key) => ({ - name: `Update: ${key}`, - value: key, - })), - { name: "Add new entry", value: "add_new" }, // Add a new entry explicitly - { name: "Cancel", value: "cancel" }, // Allow user to back out - ]; - const choice = await select({ - message: "Multiple Bucket MCP entries found. Choose an action:", - choices, + configPathType = await select<"global" | "local">({ + message: "Configure global or project-local settings?", + choices: [ + { + name: `Local (${relative(projectPath, fullLocalPath)})`, + value: "local", + }, + { name: `Global (${globalPath})`, value: "global" }, + ], }); - - if (choice === "cancel") { - console.log("Operation cancelled."); - return; - } else if (choice === "add_new") { - targetEntryKey = await input({ - message: "Enter a name for the new Bucket MCP server entry:", - default: defaultNewKey, - validate: (value) => - value.trim().length > 0 ? true : "Name cannot be empty.", - }); - } else { - // User chose an existing key to update - targetEntryKey = choice; - console.log( - `Updating existing MCP server entry: ${chalk.cyan(targetEntryKey)}`, + } + } + const configPath = configPathType === "local" ? fullLocalPath! : globalPath; + const displayConfigPath = + configPathType === "local" ? relative(projectPath, configPath) : configPath; + + // Read/Parse Config File + spinner = ora( + `Reading configuration file: ${chalk.cyan(displayConfigPath)}...`, + ).start(); + let editorConfig: any = {}; + try { + if (await fileExists(configPath)) { + const content = await readFile(configPath, "utf-8"); + // Attempt to parse JSON, handle potential comments if needed + try { + editorConfig = JSON5.parse(content); + } catch (parseError) { + spinner.fail( + `Failed to parse configuration file ${chalk.cyan(displayConfigPath)}.`, ); + throw parseError; // Re-throw original error if cleaning fails } + spinner.succeed("Configuration file read."); + } else { + spinner.info("Configuration file not found, will create a new one."); + editorConfig = {}; // Initialize empty config if file doesn't exist } - - // 7. Construct MCP Entry Value - const apiUrl = configStore.getConfig("apiUrl"); // Get configured API URL - if (!apiUrl) { - throw new Error( - "API URL is not configured. Run `bucket init` or set it in the config file.", + } catch (error: any) { + // Handle file access errors separately from parsing errors + if (error.code !== "ENOENT") { + // ENOENT means file not found, which is handled above + spinner.fail("Failed to read configuration file."); + console.error( + chalk.red(`Error reading ${displayConfigPath}: ${error.message}`), ); + const proceed = await confirm({ + message: "Reading failed. Attempt to overwrite the file?", + default: false, + }); + if (!proceed) return; + editorConfig = {}; // Start fresh if reading failed and user agreed + } else { + // This case should ideally be caught by the fileExists check, but handle defensively + spinner.info("Configuration file not found, creating a new one."); + editorConfig = {}; } - // Construct the MCP endpoint URL, ensuring no double slashes and adding /mcp - const mcpUrlBase = apiUrl.replace(/\/$/, "") + "/mcp"; - const mcpUrlWithAppId = `${mcpUrlBase}?appId=${selectedAppId}`; - - const newEntryValue = { - type: "stdio", - command: "npx", // Assuming npx is standard - args: [ - "mcp-remote@next", // Use the specified package - mcpUrlWithAppId, // Always include appId explicitly - ], - }; + } - // 8. Update Config Object - editorConfig.mcpServers[targetEntryKey] = newEntryValue; + // Ensure mcpServers object exists and get existing Bucket entries + const serversConfig = getServersConfig( + editorConfig, + selectedEditor, + configPathType, + ); + const existingBucketEntries = Object.keys(serversConfig).filter((key) => + /bucket/i.test(key), + ); + + // Prompt for Add/Update + let targetEntryKey: string; + const defaultNewKey = `Bucket - ${selectedApp.name}`; + + if (existingBucketEntries.length === 0) { + targetEntryKey = defaultNewKey; + console.log(`Adding new MCP server entry: ${chalk.cyan(targetEntryKey)}`); + } else { + const choices = [ + { name: `Add: ${defaultNewKey}`, value: "add_new" }, + ...existingBucketEntries.map((key) => ({ + name: `Update: ${key}`, + value: key, + })), + ]; + + const choice = await select({ + message: "Add a new MCP server or update an existing one?", + choices, + }); - // 9. Write Config File - spinner = ora( - `Writing configuration to ${chalk.cyan(targetConfigPath)}...`, - ).start(); - try { - // Ensure the directory exists before writing - await mkdir(dirname(targetConfigPath), { recursive: true }); - const configString = JSON.stringify(editorConfig, null, 2); // Pretty print JSON - await writeFile(targetConfigPath, configString); - spinner.succeed( - `Configuration updated successfully in ${chalk.cyan(targetConfigPath)}.`, - ); + if (choice === "add_new") { + targetEntryKey = defaultNewKey; + console.log(`Adding new MCP server entry: ${chalk.cyan(targetEntryKey)}`); + } else { + targetEntryKey = choice; console.log( - chalk.grey( - "You may need to restart your editor for changes to take effect.", - ), + `Updating existing MCP server entry: ${chalk.cyan(targetEntryKey)}`, ); - } catch (error) { - spinner.fail("Failed to write configuration file."); - void handleError(error, "MCP Configuration"); } + } + + // Construct the MCP endpoint URL + const mcpUrlBase = config.apiUrl + "/mcp"; + const mcpUrlWithAppId = `${mcpUrlBase}?appId=${selectedApp.id}`; + + const newEntryValue = { + type: "stdio", + command: "npx", // Assuming npx is standard + args: [ + "mcp-remote@next", // Use the specified package + mcpUrlWithAppId, // Always include appId explicitly + ], + }; + + // Update Config Object + serversConfig[targetEntryKey] = newEntryValue; + + // Write Config File + spinner = ora( + `Writing configuration to ${chalk.cyan(displayConfigPath)}...`, + ).start(); + try { + // Ensure the directory exists before writing + await mkdir(dirname(configPath), { recursive: true }); + const configString = JSON.stringify(editorConfig, null, 2); // Pretty print JSON + await writeFile(configPath, configString); + spinner.succeed( + `Configuration updated successfully in ${chalk.cyan(displayConfigPath)}.`, + ); + console.log( + chalk.grey( + "You may need to restart your editor for changes to take effect.", + ), + ); } catch (error) { - spinner?.fail("MCP configuration failed."); + spinner.fail("Failed to write configuration file."); void handleError(error, "MCP Configuration"); } }; @@ -289,8 +240,9 @@ export function registerMcpCommand(cli: Command) { cli .command("mcp") .description("Configure Bucket's remote MCP server for your AI assistant.") - .addOption(appIdOption) // Keep existing appId option - .addOption(editorOption) // Add new editor option + .addOption(appIdOption) + .addOption(editorOption) + .addOption(configScopeOption) .action(mcpAction); // Update the config with the cli override values diff --git a/packages/cli/services/mcp.ts b/packages/cli/services/mcp.ts index 3b96350a..9bb28136 100644 --- a/packages/cli/services/mcp.ts +++ b/packages/cli/services/mcp.ts @@ -9,6 +9,7 @@ export const SupportedEditors = [ export type SupportedEditor = (typeof SupportedEditors)[number]; type ConfigPaths = { + name: string; global: | { mac: string; @@ -21,10 +22,12 @@ type ConfigPaths = { export const ConfigPaths: Record = { cursor: { + name: "Cursor", global: "~/.cursor/mcp.json", local: ".cursor/mcp.json", }, vscode: { + name: "Visual Studio Code", global: { mac: "~/Library/Application Support/Code/User/settings.json", linux: "~/.config/Code/User/settings.json", @@ -33,12 +36,14 @@ export const ConfigPaths: Record = { local: ".vscode/mcp.json", }, claude: { + name: "Claude Desktop", global: { mac: "~/Library/Application Support/Claude/claude_desktop_config.json", windows: "@/Claude/claude_desktop_config.json", }, }, windsurf: { + name: "Windsurf", global: "~/.codeium/windsurf/mcp_config.json", }, }; @@ -65,3 +70,22 @@ export function resolveConfigPath(editor: SupportedEditor, local = false) { return undefined; } } + +export function getServersConfig( + editorConfig: any, + selectedEditor: SupportedEditor, + configPathType: "global" | "local", +) { + if (selectedEditor === "vscode") { + if (configPathType === "global") { + editorConfig.mcp = editorConfig.mcp || {}; + editorConfig.mcp.servers = editorConfig.mcp.servers || {}; + return editorConfig.mcp.servers; + } else { + editorConfig.servers = editorConfig.servers || {}; + return editorConfig.servers; + } + } + editorConfig.mcpServers = editorConfig.mcpServers || {}; + return editorConfig.mcpServers; +} diff --git a/packages/cli/utils/file.ts b/packages/cli/utils/file.ts index 0cbdcd8d..cef068c4 100644 --- a/packages/cli/utils/file.ts +++ b/packages/cli/utils/file.ts @@ -16,10 +16,16 @@ export async function fileExists(path: string): Promise { } // Helper to resolve home directory -export const resolvePath = (p: string) => - join( - ...p - .replace("~", os.homedir()) - .replace("@", process.env.APPDATA ?? "") - .split("/"), +export const resolvePath = (p: string) => { + return join( + ...p.split("/").map((part) => { + if (part === "~") { + return os.homedir(); + } else if (part === "@") { + return process.env.APPDATA ?? ""; + } else { + return part; + } + }), ); +}; diff --git a/packages/cli/utils/options.ts b/packages/cli/utils/options.ts index d41cc178..c9d10238 100644 --- a/packages/cli/utils/options.ts +++ b/packages/cli/utils/options.ts @@ -88,6 +88,11 @@ export const editorOption = new Option( "Specify the editor to configure for MCP.", ).choices(SUPPORTED_EDITORS); +export const configScopeOption = new Option( + "-s, --scope [scope]", + "Specify whether to use local or global configuration.", +).choices(["local", "global"]); + export const rulesFormatOption = new Option( "-f, --format [format]", "Format to copy rules in", From 6a8664381abf0412ef1bd080f6dfaff09d3d019c Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 29 Apr 2025 15:30:09 +0200 Subject: [PATCH 03/12] feat: replace json5 with comment-json to preserve comments --- packages/cli/commands/mcp.ts | 67 +- packages/cli/package.json | 2 +- packages/cli/stores/config.ts | 18 +- yarn.lock | 1507 ++------------------------------- 4 files changed, 124 insertions(+), 1470 deletions(-) diff --git a/packages/cli/commands/mcp.ts b/packages/cli/commands/mcp.ts index ef3897db..a9001510 100644 --- a/packages/cli/commands/mcp.ts +++ b/packages/cli/commands/mcp.ts @@ -1,7 +1,7 @@ -import { confirm, select } from "@inquirer/prompts"; +import { select } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import JSON5 from "json5"; +import { parse as parseJSON, stringify as stringifyJSON } from "comment-json"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, relative } from "node:path"; import ora, { type Ora } from "ora"; @@ -117,50 +117,31 @@ export const mcpAction = async (options: { `Reading configuration file: ${chalk.cyan(displayConfigPath)}...`, ).start(); let editorConfig: any = {}; - try { - if (await fileExists(configPath)) { - const content = await readFile(configPath, "utf-8"); - // Attempt to parse JSON, handle potential comments if needed - try { - editorConfig = JSON5.parse(content); - } catch (parseError) { - spinner.fail( - `Failed to parse configuration file ${chalk.cyan(displayConfigPath)}.`, - ); - throw parseError; // Re-throw original error if cleaning fails - } - spinner.succeed("Configuration file read."); - } else { - spinner.info("Configuration file not found, will create a new one."); - editorConfig = {}; // Initialize empty config if file doesn't exist - } - } catch (error: any) { - // Handle file access errors separately from parsing errors - if (error.code !== "ENOENT") { - // ENOENT means file not found, which is handled above - spinner.fail("Failed to read configuration file."); - console.error( - chalk.red(`Error reading ${displayConfigPath}: ${error.message}`), + if (await fileExists(configPath)) { + const content = await readFile(configPath, "utf-8"); + // Attempt to parse JSON, handle potential comments if needed + try { + editorConfig = parseJSON(content); + } catch { + spinner.fail( + `Failed to parse configuration file ${chalk.cyan(displayConfigPath)}.`, ); - const proceed = await confirm({ - message: "Reading failed. Attempt to overwrite the file?", - default: false, - }); - if (!proceed) return; - editorConfig = {}; // Start fresh if reading failed and user agreed - } else { - // This case should ideally be caught by the fileExists check, but handle defensively - spinner.info("Configuration file not found, creating a new one."); - editorConfig = {}; } + spinner.succeed( + `Read configuration file ${chalk.cyan(displayConfigPath)}.`, + ); + } else { + spinner.info("Configuration file not found, will create a new one."); + editorConfig = {}; // Initialize empty config if file doesn't exist } - // Ensure mcpServers object exists and get existing Bucket entries + // Ensure MCP servers object exists const serversConfig = getServersConfig( editorConfig, selectedEditor, configPathType, ); + // Check for existing Bucket servers const existingBucketEntries = Object.keys(serversConfig).filter((key) => /bucket/i.test(key), ); @@ -203,10 +184,10 @@ export const mcpAction = async (options: { const newEntryValue = { type: "stdio", - command: "npx", // Assuming npx is standard + command: "npx", args: [ - "mcp-remote@next", // Use the specified package - mcpUrlWithAppId, // Always include appId explicitly + "mcp-remote@next", // todo: remove next once stable + mcpUrlWithAppId, // Always include appId explicitly as that is more stable ], }; @@ -220,7 +201,7 @@ export const mcpAction = async (options: { try { // Ensure the directory exists before writing await mkdir(dirname(configPath), { recursive: true }); - const configString = JSON.stringify(editorConfig, null, 2); // Pretty print JSON + const configString = stringifyJSON(editorConfig, null, 2); await writeFile(configPath, configString); spinner.succeed( `Configuration updated successfully in ${chalk.cyan(displayConfigPath)}.`, @@ -231,7 +212,9 @@ export const mcpAction = async (options: { ), ); } catch (error) { - spinner.fail("Failed to write configuration file."); + spinner.fail( + `Failed to write configuration file ${chalk.cyan(displayConfigPath)}.`, + ); void handleError(error, "MCP Configuration"); } }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 43adcd1e..63eea9f9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -46,10 +46,10 @@ "chalk": "^5.3.0", "change-case": "^5.4.4", "commander": "^12.1.0", + "comment-json": "^4.2.5", "express": "^4.21.2", "fast-deep-equal": "^3.1.3", "find-up": "^7.0.0", - "json5": "^2.2.3", "open": "^10.1.0", "ora": "^8.1.0", "slug": "^10.0.0", diff --git a/packages/cli/stores/config.ts b/packages/cli/stores/config.ts index 130cd4a2..e1013564 100644 --- a/packages/cli/stores/config.ts +++ b/packages/cli/stores/config.ts @@ -1,7 +1,11 @@ import { Ajv, ValidateFunction } from "ajv"; +import { + assign as assignJSON, + parse as parseJSON, + stringify as stringifyJSON, +} from "comment-json"; import equal from "fast-deep-equal"; import { findUp } from "find-up"; -import JSON5 from "json5"; import { readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -71,7 +75,7 @@ class ConfigStore { "schema.json", ); const content = await readFile(schemaPath, "utf-8"); - const parsed = JSON5.parse(content); + const parsed = parseJSON(content) as unknown as Config; const ajv = new Ajv(); this.validateConfig = ajv.compile(parsed); } catch { @@ -95,7 +99,7 @@ class ConfigStore { if (!this.configPath) return; const content = await readFile(this.configPath, "utf-8"); - const parsed = JSON5.parse>(content); + const parsed = parseJSON(content) as unknown as Partial; // Normalize values if (parsed.baseUrl) @@ -112,7 +116,7 @@ class ConfigStore { ); } - this.config = { ...this.config, ...parsed }; + this.config = assignJSON(this.config, parsed); } catch { // No config file found } @@ -123,9 +127,7 @@ class ConfigStore { * @param overwrite If true, overwrites existing config file. Defaults to false */ async saveConfigFile(overwrite = false) { - const configWithoutDefaults: Partial = { - ...this.config, - }; + const configWithoutDefaults: Partial = assignJSON({}, this.config); // Only include non-default values and $schema for (const untypedKey in configWithoutDefaults) { @@ -138,7 +140,7 @@ class ConfigStore { } } - const configJSON = JSON.stringify(configWithoutDefaults, null, 2); + const configJSON = stringifyJSON(configWithoutDefaults, null, 2); if (this.configPath && !overwrite) { throw new Error("Config file already exists"); diff --git a/yarn.lock b/yarn.lock index 50480da4..7227fc2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -498,8 +498,6 @@ __metadata: "@bucketco/eslint-config": "workspace:^" "@bucketco/tsconfig": "workspace:^" "@inquirer/prompts": "npm:^5.3.8" - "@modelcontextprotocol/inspector": "npm:^0.6.0" - "@modelcontextprotocol/sdk": "npm:^1.7.0" "@types/express": "npm:^5.0.0" "@types/node": "npm:^22.5.1" "@types/slug": "npm:^5.0.9" @@ -507,11 +505,11 @@ __metadata: chalk: "npm:^5.3.0" change-case: "npm:^5.4.4" commander: "npm:^12.1.0" + comment-json: "npm:^4.2.5" eslint: "npm:^9.21.0" express: "npm:^4.21.2" fast-deep-equal: "npm:^3.1.3" find-up: "npm:^7.0.0" - json5: "npm:^2.2.3" open: "npm:^10.1.0" ora: "npm:^8.1.0" prettier: "npm:^3.5.2" @@ -1610,16 +1608,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.0": - version: 1.6.13 - resolution: "@floating-ui/dom@npm:1.6.13" - dependencies: - "@floating-ui/core": "npm:^1.6.0" - "@floating-ui/utils": "npm:^0.2.9" - checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9 - languageName: node - linkType: hard - "@floating-ui/dom@npm:^1.6.8": version: 1.6.8 resolution: "@floating-ui/dom@npm:1.6.8" @@ -1630,18 +1618,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.0": - version: 2.1.2 - resolution: "@floating-ui/react-dom@npm:2.1.2" - dependencies: - "@floating-ui/dom": "npm:^1.0.0" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10c0/e855131c74e68cab505f7f44f92cd4e2efab1c125796db3116c54c0859323adae4bf697bf292ee83ac77b9335a41ad67852193d7aeace90aa2e1c4a640cafa60 - languageName: node - linkType: hard - "@floating-ui/utils@npm:^0.2.5": version: 0.2.5 resolution: "@floating-ui/utils@npm:0.2.5" @@ -1649,13 +1625,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.9": - version: 0.2.9 - resolution: "@floating-ui/utils@npm:0.2.9" - checksum: 10c0/48bbed10f91cb7863a796cc0d0e917c78d11aeb89f98d03fc38d79e7eb792224a79f538ed8a2d5d5584511d4ca6354ef35f1712659fd569868e342df4398ad6f - languageName: node - linkType: hard - "@gerrit0/mini-shiki@npm:^1.24.0": version: 1.24.4 resolution: "@gerrit0/mini-shiki@npm:1.24.4" @@ -2242,105 +2211,6 @@ __metadata: languageName: node linkType: hard -"@modelcontextprotocol/inspector-client@npm:^0.6.0": - version: 0.6.0 - resolution: "@modelcontextprotocol/inspector-client@npm:0.6.0" - dependencies: - "@modelcontextprotocol/sdk": "npm:^1.6.1" - "@radix-ui/react-checkbox": "npm:^1.1.4" - "@radix-ui/react-dialog": "npm:^1.1.3" - "@radix-ui/react-icons": "npm:^1.3.0" - "@radix-ui/react-label": "npm:^2.1.0" - "@radix-ui/react-popover": "npm:^1.1.3" - "@radix-ui/react-select": "npm:^2.1.2" - "@radix-ui/react-slot": "npm:^1.1.0" - "@radix-ui/react-tabs": "npm:^1.1.1" - "@types/prismjs": "npm:^1.26.5" - class-variance-authority: "npm:^0.7.0" - clsx: "npm:^2.1.1" - cmdk: "npm:^1.0.4" - lucide-react: "npm:^0.447.0" - pkce-challenge: "npm:^4.1.0" - prismjs: "npm:^1.29.0" - react: "npm:^18.3.1" - react-dom: "npm:^18.3.1" - react-simple-code-editor: "npm:^0.14.1" - react-toastify: "npm:^10.0.6" - serve-handler: "npm:^6.1.6" - tailwind-merge: "npm:^2.5.3" - tailwindcss-animate: "npm:^1.0.7" - zod: "npm:^3.23.8" - bin: - mcp-inspector-client: bin/cli.js - checksum: 10c0/1912cb9e31b1047b1d70122e93497eb3032bbe0e31789993a0fa56df0ad9746208928aa94f692840a23de85627c4704e245de9baf44bf6d549cc1ea4f67ab9f3 - languageName: node - linkType: hard - -"@modelcontextprotocol/inspector-server@npm:^0.6.0": - version: 0.6.0 - resolution: "@modelcontextprotocol/inspector-server@npm:0.6.0" - dependencies: - "@modelcontextprotocol/sdk": "npm:^1.6.1" - cors: "npm:^2.8.5" - express: "npm:^4.21.0" - ws: "npm:^8.18.0" - zod: "npm:^3.23.8" - bin: - mcp-inspector-server: build/index.js - checksum: 10c0/69557e9f65a5b38339c173cae825868c6fdee41c17040cffd2421d1d6517619af4f0def3373285ec38a8a182b355431867a385f5bb0e4aa34a983646b6d1ccbc - languageName: node - linkType: hard - -"@modelcontextprotocol/inspector@npm:^0.6.0": - version: 0.6.0 - resolution: "@modelcontextprotocol/inspector@npm:0.6.0" - dependencies: - "@modelcontextprotocol/inspector-client": "npm:^0.6.0" - "@modelcontextprotocol/inspector-server": "npm:^0.6.0" - concurrently: "npm:^9.0.1" - shell-quote: "npm:^1.8.2" - spawn-rx: "npm:^5.1.2" - ts-node: "npm:^10.9.2" - bin: - mcp-inspector: bin/cli.js - checksum: 10c0/4f44579692164594202cbb4b49bb2cbf56a32d323173be199f943fe5e6b5b9425be6ea6efd2bbaccc08a148b1bd4b6f7c5e042136fdb938cf036981596ba1dbe - languageName: node - linkType: hard - -"@modelcontextprotocol/sdk@npm:^1.6.1": - version: 1.6.1 - resolution: "@modelcontextprotocol/sdk@npm:1.6.1" - dependencies: - content-type: "npm:^1.0.5" - cors: "npm:^2.8.5" - eventsource: "npm:^3.0.2" - express: "npm:^5.0.1" - express-rate-limit: "npm:^7.5.0" - pkce-challenge: "npm:^4.1.0" - raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10c0/767aca8096c06aabfa9432fab6a4e7bafb671833b1bddb2797b8089e102a9d6ac0486e7a353b28df9984eff5c5291bde76cd5ad079b576ae70666cdff10c5b2a - languageName: node - linkType: hard - -"@modelcontextprotocol/sdk@npm:^1.7.0": - version: 1.7.0 - resolution: "@modelcontextprotocol/sdk@npm:1.7.0" - dependencies: - content-type: "npm:^1.0.5" - cors: "npm:^2.8.5" - eventsource: "npm:^3.0.2" - express: "npm:^5.0.1" - express-rate-limit: "npm:^7.5.0" - pkce-challenge: "npm:^4.1.0" - raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10c0/8d2bc586e5d8d9d2ffd927700a7eeb0ae0e24e63cc139283d74dd380e17cde14e252ec2e8f9edcfa4359e4b7b68ca2d4b01d1b341dd715b10d74566f9e34c1ca - languageName: node - linkType: hard - "@mswjs/interceptors@npm:^0.29.0": version: 0.29.1 resolution: "@mswjs/interceptors@npm:0.29.1" @@ -2951,610 +2821,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/number@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/number@npm:1.1.0" - checksum: 10c0/a48e34d5ff1484de1b7cf5d7317fefc831d49e96a2229f300fd37b657bd8cfb59c922830c00ec02838ab21de3b299a523474592e4f30882153412ed47edce6a4 - languageName: node - linkType: hard - -"@radix-ui/primitive@npm:1.1.1": - version: 1.1.1 - resolution: "@radix-ui/primitive@npm:1.1.1" - checksum: 10c0/6457bd8d1aa4ecb948e5d2a2484fc570698b2ab472db6d915a8f1eec04823f80423efa60b5ba840f0693bec2ca380333cc5f3b52586b40f407d9f572f9261f8d - languageName: node - linkType: hard - -"@radix-ui/react-arrow@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-arrow@npm:1.1.2" - dependencies: - "@radix-ui/react-primitive": "npm:2.0.2" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/38e1a338da1131f325e417ac456b1b6c16c76aa9da0635916262b4682d4e648226fd37b23348964a8e909c98b4d2293c7c5789be8f243cfe03856e6f0765cf5d - languageName: node - linkType: hard - -"@radix-ui/react-checkbox@npm:^1.1.4": - version: 1.1.4 - resolution: "@radix-ui/react-checkbox@npm:1.1.4" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-presence": "npm:1.1.2" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - "@radix-ui/react-use-previous": "npm:1.1.0" - "@radix-ui/react-use-size": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/182db383c02affd874c5bd4f81ebd3786ddc5d6525b958984b40673cb1d8ff0336428bea18c19175f20b27a833120c441ec6a97433e9f731284e56ea1a9f13fd - languageName: node - linkType: hard - -"@radix-ui/react-collection@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-collection@npm:1.1.2" - dependencies: - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-slot": "npm:1.1.2" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/8376aa0c0f38efbb45e5c0a2e8724b0ca2ccdab511f5aee4c3eb62a89959b20be0d4dd410b7068bc13d722751cbc88e916e10573784fb26b084c43f930818715 - languageName: node - linkType: hard - -"@radix-ui/react-compose-refs@npm:1.1.1, @radix-ui/react-compose-refs@npm:^1.1.1": - version: 1.1.1 - resolution: "@radix-ui/react-compose-refs@npm:1.1.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/3e84580024e66e3cc5b9ae79355e787815c1d2a3c7d46e7f47900a29c33751ca24cf4ac8903314957ab1f7788aebe1687e2258641c188cf94653f7ddf8f70627 - languageName: node - linkType: hard - -"@radix-ui/react-context@npm:1.1.1": - version: 1.1.1 - resolution: "@radix-ui/react-context@npm:1.1.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/fc4ace9d79d7954c715ade765e06c95d7e1b12a63a536bcbe842fb904f03f88fc5bd6e38d44bd23243d37a270b4c44380fedddaeeae2d274f0b898a20665aba2 - languageName: node - linkType: hard - -"@radix-ui/react-dialog@npm:^1.1.3, @radix-ui/react-dialog@npm:^1.1.6": - version: 1.1.6 - resolution: "@radix-ui/react-dialog@npm:1.1.6" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.5" - "@radix-ui/react-focus-guards": "npm:1.1.1" - "@radix-ui/react-focus-scope": "npm:1.1.2" - "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-portal": "npm:1.1.4" - "@radix-ui/react-presence": "npm:1.1.2" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-slot": "npm:1.1.2" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - aria-hidden: "npm:^1.2.4" - react-remove-scroll: "npm:^2.6.3" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/98e425549573c5d6fb0fee94ecd40427a8b8897bb2d9bb2a44fe64e484754376ff23b64fcf64e061d42fc774b9627a28cb5b1bb5652e567908dac9a8d8618705 - languageName: node - linkType: hard - -"@radix-ui/react-direction@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-direction@npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/eb07d8cc3ae2388b824e0a11ae0e3b71fb0c49972b506e249cec9f27a5b7ef4305ee668c98b674833c92e842163549a83beb0a197dec1ec65774bdeeb61f932c - languageName: node - linkType: hard - -"@radix-ui/react-dismissable-layer@npm:1.1.5": - version: 1.1.5 - resolution: "@radix-ui/react-dismissable-layer@npm:1.1.5" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - "@radix-ui/react-use-escape-keydown": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/05c5adfcd42a736c456f50bdca25bf7f6b25eef7328e4c05de535fea128328666433a89d68cb1445e039c188d7f1397df6a4a02e2da0970762f2a80fd29b48ea - languageName: node - linkType: hard - -"@radix-ui/react-focus-guards@npm:1.1.1": - version: 1.1.1 - resolution: "@radix-ui/react-focus-guards@npm:1.1.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/2e99750ca593083a530542a185d656b45b100752353a7a193a67566e3c256414a76fa9171d152f8c0167b8d6c1fdf62b2e07750d7af2974bf8ef39eb204aa537 - languageName: node - linkType: hard - -"@radix-ui/react-focus-scope@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-focus-scope@npm:1.1.2" - dependencies: - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/7b93866a9980bc938fc3fcfacfc49467c13144931c9b7a3b5423c0c3817685dc421499d73f58335f6c3c1c0f4fea9c9b7c16aa06a1d30571620787086082bea0 - languageName: node - linkType: hard - -"@radix-ui/react-icons@npm:^1.3.0": - version: 1.3.2 - resolution: "@radix-ui/react-icons@npm:1.3.2" - peerDependencies: - react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc - checksum: 10c0/3a380c7ae47e330ebd8ab4846729a543b4a0be5ecb1e2a7a571f4394728ff7d428b01f6620128051b6b69d63138a0ab8de77af78221ec364fbc5d126acf55b4a - languageName: node - linkType: hard - -"@radix-ui/react-id@npm:1.1.0, @radix-ui/react-id@npm:^1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-id@npm:1.1.0" - dependencies: - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/acf13e29e51ee96336837fc0cfecc306328b20b0e0070f6f0f7aa7a621ded4a1ee5537cfad58456f64bae76caa7f8769231e88dc7dc106197347ee433c275a79 - languageName: node - linkType: hard - -"@radix-ui/react-label@npm:^2.1.0": - version: 2.1.2 - resolution: "@radix-ui/react-label@npm:2.1.2" - dependencies: - "@radix-ui/react-primitive": "npm:2.0.2" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/c425ea25a67f60142645e6dd7669aa90bd9017e8d99c347736c9c19c44cea52e33224e4d086fd7e4945a7e9baa49335d42a5801d3bead884305515023e3ab31c - languageName: node - linkType: hard - -"@radix-ui/react-popover@npm:^1.1.3": - version: 1.1.6 - resolution: "@radix-ui/react-popover@npm:1.1.6" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.5" - "@radix-ui/react-focus-guards": "npm:1.1.1" - "@radix-ui/react-focus-scope": "npm:1.1.2" - "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-popper": "npm:1.2.2" - "@radix-ui/react-portal": "npm:1.1.4" - "@radix-ui/react-presence": "npm:1.1.2" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-slot": "npm:1.1.2" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - aria-hidden: "npm:^1.2.4" - react-remove-scroll: "npm:^2.6.3" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/63cc2761693193f8c28c43a25d9eea69e4095ba47da11413dfa19436d6116c814851c388ab78f93a3bda0cc88ec4c234bd31d971ade2fcfbc08a0645ccde1d91 - languageName: node - linkType: hard - -"@radix-ui/react-popper@npm:1.2.2": - version: 1.2.2 - resolution: "@radix-ui/react-popper@npm:1.2.2" - dependencies: - "@floating-ui/react-dom": "npm:^2.0.0" - "@radix-ui/react-arrow": "npm:1.1.2" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - "@radix-ui/react-use-rect": "npm:1.1.0" - "@radix-ui/react-use-size": "npm:1.1.0" - "@radix-ui/rect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/556cef98c0fe50bcfaaa4ae2e85af737755c884b78a04b6bdac3682829051ea0a4cf1163fc8bde782e33280613424e2ebb10b8af507da53e1aea08966c13cc86 - languageName: node - linkType: hard - -"@radix-ui/react-portal@npm:1.1.4": - version: 1.1.4 - resolution: "@radix-ui/react-portal@npm:1.1.4" - dependencies: - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/e4038eb2f20be10d9754d099d00620f429711919d20c4c630946d9c4941f1c83ef1a3f4110c221c70486e65bc565ebba4ada22a0e7e2d179c039f2a014300793 - languageName: node - linkType: hard - -"@radix-ui/react-presence@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-presence@npm:1.1.2" - dependencies: - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/0c6fa281368636308044df3be4c1f02733094b5e35ba04f26e610dd1c4315a245ffc758e0e176c444742a7a46f4328af1a9d8181e860175ec39338d06525a78d - languageName: node - linkType: hard - -"@radix-ui/react-primitive@npm:2.0.2, @radix-ui/react-primitive@npm:^2.0.2": - version: 2.0.2 - resolution: "@radix-ui/react-primitive@npm:2.0.2" - dependencies: - "@radix-ui/react-slot": "npm:1.1.2" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/1af7a33a86f8bd2467f2300b1bb6ca9af67cae3950953ba543d2a625c17f341dff05d19056ece7b03e5ced8b9f8de99c74f806710ce0da6b9a000f2af063fffe - languageName: node - linkType: hard - -"@radix-ui/react-roving-focus@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-roving-focus@npm:1.1.2" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-collection": "npm:1.1.2" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/80e378e1156d5b8af14995e908fe2358c8f4757fbf274e30d2ee3c1cedc3a0c7192524df7e3bb1d5011ee9ab8ab7445b60eff06617370e58abcd1ae97e0e40f6 - languageName: node - linkType: hard - -"@radix-ui/react-select@npm:^2.1.2": - version: 2.1.6 - resolution: "@radix-ui/react-select@npm:2.1.6" - dependencies: - "@radix-ui/number": "npm:1.1.0" - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-collection": "npm:1.1.2" - "@radix-ui/react-compose-refs": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-dismissable-layer": "npm:1.1.5" - "@radix-ui/react-focus-guards": "npm:1.1.1" - "@radix-ui/react-focus-scope": "npm:1.1.2" - "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-popper": "npm:1.2.2" - "@radix-ui/react-portal": "npm:1.1.4" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-slot": "npm:1.1.2" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - "@radix-ui/react-use-previous": "npm:1.1.0" - "@radix-ui/react-visually-hidden": "npm:1.1.2" - aria-hidden: "npm:^1.2.4" - react-remove-scroll: "npm:^2.6.3" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/bc953806b861c66f01371502ac7404e1e3c783290e979f9446e89b643bff7f90164ce91126f719e5aec4d9601b9ad0e4e7b7e747bfc0fbfcf7a31aeeac0a484d - languageName: node - linkType: hard - -"@radix-ui/react-slot@npm:1.1.2, @radix-ui/react-slot@npm:^1.1.0": - version: 1.1.2 - resolution: "@radix-ui/react-slot@npm:1.1.2" - dependencies: - "@radix-ui/react-compose-refs": "npm:1.1.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/81d45091806c52b507cec80b4477e4f31189d76ffcd7845b382eb3a034e6cf1faef71b881612028d5893f7580bf9ab59daa18fbf2792042dccd755c99a18df67 - languageName: node - linkType: hard - -"@radix-ui/react-tabs@npm:^1.1.1": - version: 1.1.3 - resolution: "@radix-ui/react-tabs@npm:1.1.3" - dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-presence": "npm:1.1.2" - "@radix-ui/react-primitive": "npm:2.0.2" - "@radix-ui/react-roving-focus": "npm:1.1.2" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/2f621c43a8e1dd0d54c828f8b4d88414c9114af6b720a650ad9587cc0a7a7536da778f2fe5181a38494cc2956f2b238fbe64790f6daad1d058b34f4acaee520e - languageName: node - linkType: hard - -"@radix-ui/react-use-callback-ref@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-callback-ref@npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/e954863f3baa151faf89ac052a5468b42650efca924417470efd1bd254b411a94c69c30de2fdbb90187b38cb984795978e12e30423dc41e4309d93d53b66d819 - languageName: node - linkType: hard - -"@radix-ui/react-use-controllable-state@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-controllable-state@npm:1.1.0" - dependencies: - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/2af883b5b25822ac226e60a6bfde647c0123a76345052a90219026059b3f7225844b2c13a9a16fba859c1cda5fb3d057f2a04503f71780e607516492db4eb3a1 - languageName: node - linkType: hard - -"@radix-ui/react-use-escape-keydown@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.0" - dependencies: - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/910fd696e5a0994b0e06b9cb68def8a865f47951a013ec240c77db2a9e1e726105602700ef5e5f01af49f2f18fe0e73164f9a9651021f28538ef8a30d91f3fbb - languageName: node - linkType: hard - -"@radix-ui/react-use-layout-effect@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-layout-effect@npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/9bf87ece1845c038ed95863cfccf9d75f557c2400d606343bab0ab3192b9806b9840e6aa0a0333fdf3e83cf9982632852192f3e68d7d8367bc8c788dfdf8e62b - languageName: node - linkType: hard - -"@radix-ui/react-use-previous@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-previous@npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/9787d24790d4e330715127f2f4db56c4cbed9b0a47f97e11a68582c08a356a53c1ec41c7537382f6fb8d0db25de152770f17430e8eaf0fa59705be97760acbad - languageName: node - linkType: hard - -"@radix-ui/react-use-rect@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-rect@npm:1.1.0" - dependencies: - "@radix-ui/rect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/c2e30150ab49e2cec238cda306fd748c3d47fb96dcff69a3b08e1d19108d80bac239d48f1747a25dadca614e3e967267d43b91e60ea59db2befbc7bea913ff84 - languageName: node - linkType: hard - -"@radix-ui/react-use-size@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/react-use-size@npm:1.1.0" - dependencies: - "@radix-ui/react-use-layout-effect": "npm:1.1.0" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/4c8b89037597fdc1824d009e0c941b510c7c6c30f83024cc02c934edd748886786e7d9f36f57323b02ad29833e7fa7e8974d81969b4ab33d8f41661afa4f30a6 - languageName: node - linkType: hard - -"@radix-ui/react-visually-hidden@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-visually-hidden@npm:1.1.2" - dependencies: - "@radix-ui/react-primitive": "npm:2.0.2" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/ea6dc8ec284b32bca6f24809db257394802e14af7c95e4a237af51009fa222c42e3b7a55b3bfc94d753f509086636555058ae8e535be25956c46529abf41b448 - languageName: node - linkType: hard - -"@radix-ui/rect@npm:1.1.0": - version: 1.1.0 - resolution: "@radix-ui/rect@npm:1.1.0" - checksum: 10c0/a26ff7f8708fb5f2f7949baad70a6b2a597d761ee4dd4aadaf1c1a33ea82ea23dfef6ce6366a08310c5d008cdd60b2e626e4ee03fa342bd5f246ddd9d427f6be - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^5.1.0": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" @@ -4534,13 +3800,6 @@ __metadata: languageName: node linkType: hard -"@types/prismjs@npm:^1.26.5": - version: 1.26.5 - resolution: "@types/prismjs@npm:1.26.5" - checksum: 10c0/5619cb449e0d8df098c8759d6f47bf8fdd510abf5dbdfa999e55c6a2545efbd1e209cc85a33d8d9f4ff2898089a1a6d9a70737c9baffaae635c46852c40d384a - languageName: node - linkType: hard - "@types/prop-types@npm:*": version: 15.7.12 resolution: "@types/prop-types@npm:15.7.12" @@ -5528,16 +4787,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^2.0.0": - version: 2.0.0 - resolution: "accepts@npm:2.0.0" - dependencies: - mime-types: "npm:^3.0.0" - negotiator: "npm:^1.0.0" - checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef - languageName: node - linkType: hard - "accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -5889,15 +5138,6 @@ __metadata: languageName: node linkType: hard -"aria-hidden@npm:^1.2.4": - version: 1.2.4 - resolution: "aria-hidden@npm:1.2.4" - dependencies: - tslib: "npm:^2.0.0" - checksum: 10c0/8abcab2e1432efc4db415e97cb3959649ddf52c8fc815d7384f43f3d3abf56f1c12852575d00df9a8927f421d7e0712652dd5f8db244ea57634344e29ecfc74a - languageName: node - linkType: hard - "aria-query@npm:5.3.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -5994,6 +5234,13 @@ __metadata: languageName: node linkType: hard +"array-timsort@npm:^1.0.3": + version: 1.0.3 + resolution: "array-timsort@npm:1.0.3" + checksum: 10c0/bd3a1707b621947265c89867e67c9102b9b9f4c50f5b3974220112290d8b60d26ce60595edec5deed3325207b759d70b758bed3cd310b5ddadb835657ffb6d12 + languageName: node + linkType: hard + "array-union@npm:^2.1.0": version: 2.1.0 resolution: "array-union@npm:2.1.0" @@ -6336,23 +5583,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^2.0.1": - version: 2.1.0 - resolution: "body-parser@npm:2.1.0" - dependencies: - bytes: "npm:^3.1.2" - content-type: "npm:^1.0.5" - debug: "npm:^4.4.0" - http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.5.2" - on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" - raw-body: "npm:^3.0.0" - type-is: "npm:^2.0.0" - checksum: 10c0/aec9bb327ba025808f04f86350ae9152ff9a6a8bc7429b69827cb7721b09f477ffd5e18cc954152a6a259b765322e33ebc4c50ab940f66d8e45ff1466e49ff2e - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -6467,14 +5697,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.0.0": - version: 3.0.0 - resolution: "bytes@npm:3.0.0" - checksum: 10c0/91d42c38601c76460519ffef88371caacaea483a354c8e4b8808e7b027574436a5713337c003ea3de63ee4991c2a9a637884fdfe7f761760d746929d9e8fec60 - languageName: node - linkType: hard - -"bytes@npm:3.1.2, bytes@npm:^3.1.2": +"bytes@npm:3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e @@ -6829,15 +6052,6 @@ __metadata: languageName: node linkType: hard -"class-variance-authority@npm:^0.7.0": - version: 0.7.1 - resolution: "class-variance-authority@npm:0.7.1" - dependencies: - clsx: "npm:^2.1.1" - checksum: 10c0/0f438cea22131808b99272de0fa933c2532d5659773bfec0c583de7b3f038378996d3350683426b8e9c74a6286699382106d71fbec52f0dd5fbb191792cccb5b - languageName: node - linkType: hard - "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -6938,13 +6152,6 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.1.0, clsx@npm:^2.1.1": - version: 2.1.1 - resolution: "clsx@npm:2.1.1" - checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 - languageName: node - linkType: hard - "cmd-shim@npm:6.0.1": version: 6.0.1 resolution: "cmd-shim@npm:6.0.1" @@ -6952,21 +6159,6 @@ __metadata: languageName: node linkType: hard -"cmdk@npm:^1.0.4": - version: 1.1.1 - resolution: "cmdk@npm:1.1.1" - dependencies: - "@radix-ui/react-compose-refs": "npm:^1.1.1" - "@radix-ui/react-dialog": "npm:^1.1.6" - "@radix-ui/react-id": "npm:^1.1.0" - "@radix-ui/react-primitive": "npm:^2.0.2" - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - checksum: 10c0/5605ac4396ec9bc65c82f954da19dd89a0636a54026df72780e2470da1381f9d57434a80a53f2d57eaa4e759660a3ebba9232b74258dc09970576591eae03116 - languageName: node - linkType: hard - "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -7062,6 +6254,19 @@ __metadata: languageName: node linkType: hard +"comment-json@npm:^4.2.5": + version: 4.2.5 + resolution: "comment-json@npm:4.2.5" + dependencies: + array-timsort: "npm:^1.0.3" + core-util-is: "npm:^1.0.3" + esprima: "npm:^4.0.1" + has-own-prop: "npm:^2.0.0" + repeat-string: "npm:^1.6.1" + checksum: 10c0/e22f13f18fcc484ac33c8bc02a3d69c3f9467ae5063fdfb3df7735f83a8d9a2cab6a32b7d4a0c53123413a9577de8e17c8cc88369c433326799558febb34ef9c + languageName: node + linkType: hard + "compare-func@npm:^2.0.0": version: 2.0.0 resolution: "compare-func@npm:2.0.0" @@ -7105,24 +6310,6 @@ __metadata: languageName: node linkType: hard -"concurrently@npm:^9.0.1": - version: 9.1.2 - resolution: "concurrently@npm:9.1.2" - dependencies: - chalk: "npm:^4.1.2" - lodash: "npm:^4.17.21" - rxjs: "npm:^7.8.1" - shell-quote: "npm:^1.8.1" - supports-color: "npm:^8.1.1" - tree-kill: "npm:^1.2.2" - yargs: "npm:^17.7.2" - bin: - conc: dist/bin/concurrently.js - concurrently: dist/bin/concurrently.js - checksum: 10c0/88e00269366aa885ca2b97fd53b04e7af2b0f31774d991bfc0e88c0de61cdebdf115ddacc9c897fbd1f1b90369014637fa77045a171d072a75693332b36dcc70 - languageName: node - linkType: hard - "confbox@npm:^0.1.7": version: 0.1.7 resolution: "confbox@npm:0.1.7" @@ -7137,13 +6324,6 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.2": - version: 0.5.2 - resolution: "content-disposition@npm:0.5.2" - checksum: 10c0/49eebaa0da1f9609b192e99d7fec31d1178cb57baa9d01f5b63b29787ac31e9d18b5a1033e854c68c9b6cce790e700a6f7fa60e43f95e2e416404e114a8f2f49 - languageName: node - linkType: hard - "content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -7153,16 +6333,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:^1.0.0": - version: 1.0.0 - resolution: "content-disposition@npm:1.0.0" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 - languageName: node - linkType: hard - -"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af @@ -7285,13 +6456,6 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:^1.2.1": - version: 1.2.2 - resolution: "cookie-signature@npm:1.2.2" - checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 - languageName: node - linkType: hard - "cookie@npm:0.7.1": version: 0.7.1 resolution: "cookie@npm:0.7.1" @@ -7306,23 +6470,13 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:~1.0.0": +"core-util-is@npm:^1.0.3, core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 languageName: node linkType: hard -"cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 - languageName: node - linkType: hard - "corser@npm:^2.0.1": version: 2.0.1 resolution: "corser@npm:2.0.1" @@ -7555,18 +6709,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.3.6": - version: 4.3.6 - resolution: "debug@npm:4.3.6" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285 - languageName: node - linkType: hard - "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -7810,7 +6952,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0, destroy@npm:^1.2.0": +"destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 @@ -7824,13 +6966,6 @@ __metadata: languageName: node linkType: hard -"detect-node-es@npm:^1.1.0": - version: 1.1.0 - resolution: "detect-node-es@npm:1.1.0" - checksum: 10c0/e562f00de23f10c27d7119e1af0e7388407eb4b06596a25f6d79a360094a109ff285de317f02b090faae093d314cf6e73ac3214f8a5bb3a0def5bece94557fbe - languageName: node - linkType: hard - "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -7994,13 +7129,6 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb - languageName: node - linkType: hard - "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -8008,6 +7136,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -8629,7 +7764,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": +"escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 @@ -9103,7 +8238,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0": +"esprima@npm:^4.0.0, esprima@npm:^4.0.1": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -9177,7 +8312,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:^1.8.1, etag@npm:~1.8.1": +"etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 @@ -9198,22 +8333,6 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.0": - version: 3.0.0 - resolution: "eventsource-parser@npm:3.0.0" - checksum: 10c0/74ded91ff93330bd95243bd5a8fc61c805ea1843dd7ffac8e8ac36287fff419a7eec21b2fadf50d4e554ce0506de72f47928513e3c5b886fa4613fd49ef0024f - languageName: node - linkType: hard - -"eventsource@npm:^3.0.2": - version: 3.0.5 - resolution: "eventsource@npm:3.0.5" - dependencies: - eventsource-parser: "npm:^3.0.0" - checksum: 10c0/0283045a70b7ab7501fc290e60ee5ebd6e24648e0f0a6d6a1c1bbb7a421971e8140b85d6239d08c360e0a0b196205ca86b4ff7f9efe3c06877ba628136856489 - languageName: node - linkType: hard - "execa@npm:5.0.0": version: 5.0.0 resolution: "execa@npm:5.0.0" @@ -9262,91 +8381,42 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.5.0": - version: 7.5.0 - resolution: "express-rate-limit@npm:7.5.0" - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - checksum: 10c0/3e96afa05b4f577395688ede37e0cb19901f20c350b32575fb076f3d25176209fb88d3648151755c232aaf304147c58531f070757978f376e2f08326449299fd - languageName: node - linkType: hard - -"express@npm:^4.21.0, express@npm:^4.21.2": +"express@npm:^4.21.2": version: 4.21.2 - resolution: "express@npm:4.21.2" - dependencies: - accepts: "npm:~1.3.8" - array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" - content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:1.0.6" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.3" - methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.12" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" - range-parser: "npm:~1.2.1" - safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:~1.6.18" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f - languageName: node - linkType: hard - -"express@npm:^5.0.1": - version: 5.0.1 - resolution: "express@npm:5.0.1" + resolution: "express@npm:4.21.2" dependencies: - accepts: "npm:^2.0.0" - body-parser: "npm:^2.0.1" - content-disposition: "npm:^1.0.0" + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" content-type: "npm:~1.0.4" cookie: "npm:0.7.1" - cookie-signature: "npm:^1.2.1" - debug: "npm:4.3.6" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:^2.0.0" - fresh: "npm:2.0.0" + finalhandler: "npm:1.3.1" + fresh: "npm:0.5.2" http-errors: "npm:2.0.0" - merge-descriptors: "npm:^2.0.0" + merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - mime-types: "npm:^3.0.0" on-finished: "npm:2.4.1" - once: "npm:1.4.0" parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.12" proxy-addr: "npm:~2.0.7" qs: "npm:6.13.0" range-parser: "npm:~1.2.1" - router: "npm:^2.0.0" safe-buffer: "npm:5.2.1" - send: "npm:^1.1.0" - serve-static: "npm:^2.1.0" + send: "npm:0.19.0" + serve-static: "npm:1.16.2" setprototypeof: "npm:1.2.0" statuses: "npm:2.0.1" - type-is: "npm:^2.0.0" + type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10c0/6e533f28adb64178c90c3e357cbcdb5d1f233ca1290d023b3c4936c4ac67c6699aaba726edf6c148979220ec7ce6ed2e5e374cff904e3a2bf9ce91620cf8afff + checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f languageName: node linkType: hard @@ -9516,20 +8586,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:^2.0.0": - version: 2.1.0 - resolution: "finalhandler@npm:2.1.0" - dependencies: - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f - languageName: node - linkType: hard - "find-up@npm:^2.0.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -9684,20 +8740,13 @@ __metadata: languageName: node linkType: hard -"fresh@npm:0.5.2, fresh@npm:^0.5.2": +"fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a languageName: node linkType: hard -"fresh@npm:2.0.0": - version: 2.0.0 - resolution: "fresh@npm:2.0.0" - checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc - languageName: node - linkType: hard - "fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" @@ -9936,13 +8985,6 @@ __metadata: languageName: node linkType: hard -"get-nonce@npm:^1.0.0": - version: 1.0.1 - resolution: "get-nonce@npm:1.0.1" - checksum: 10c0/2d7df55279060bf0568549e1ffc9b84bc32a32b7541675ca092dce56317cdd1a59a98dcc4072c9f6a980779440139a3221d7486f52c488e69dc0fd27b1efb162 - languageName: node - linkType: hard - "get-pkg-repo@npm:^4.2.1": version: 4.2.1 resolution: "get-pkg-repo@npm:4.2.1" @@ -10378,6 +9420,13 @@ __metadata: languageName: node linkType: hard +"has-own-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "has-own-prop@npm:2.0.0" + checksum: 10c0/2745497283d80228b5c5fbb8c63ab1029e604bce7db8d4b36255e427b3695b2153dc978b176674d0dd2a23f132809e04d7ef41fefc0ab85870a5caa918c5c0d9 + languageName: node + linkType: hard + "has-property-descriptors@npm:^1.0.0": version: 1.0.0 resolution: "has-property-descriptors@npm:1.0.0" @@ -10576,7 +9625,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": +"http-errors@npm:2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -10715,15 +9764,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.5.2": - version: 0.5.2 - resolution: "iconv-lite@npm:0.5.2" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10c0/6c51c9996fe360b03f501c0f76f122f007c6a9be924cfdf0b007044cfbcdeeb9c9decb5435465934dbd3804f37e67fdc2fb3ed8c9948464299165776541dff25 - languageName: node - linkType: hard - "ieee754@npm:^1.1.13": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -11328,13 +10368,6 @@ __metadata: languageName: node linkType: hard -"is-promise@npm:^4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 - languageName: node - linkType: hard - "is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" @@ -12410,15 +11443,6 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.447.0": - version: 0.447.0 - resolution: "lucide-react@npm:0.447.0" - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc - checksum: 10c0/c8831795a4b4429d0cad28f33baf924d285eb64a0ecbce013a3307e7fab998cd5a48e3dce3607bbfa88fa383b8c603947965453acb60c94e2ef39956afe1eb67 - languageName: node - linkType: hard - "lunr@npm:^2.3.9": version: 2.3.9 resolution: "lunr@npm:2.3.9" @@ -12602,13 +11626,6 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9 - languageName: node - linkType: hard - "meow@npm:^8.1.2": version: 8.1.2 resolution: "meow@npm:8.1.2" @@ -12635,13 +11652,6 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-descriptors@npm:2.0.0" - checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -12687,30 +11697,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:^1.53.0": - version: 1.53.0 - resolution: "mime-db@npm:1.53.0" - checksum: 10c0/1dcc37ba8ed5d1c179f5c6f0837e8db19371d5f2ea3690c3c2f3fa8c3858f976851d3460b172b4dee78ebd606762cbb407aa398545fbacd539e519f858cd7bf4 - languageName: node - linkType: hard - -"mime-db@npm:~1.33.0": - version: 1.33.0 - resolution: "mime-db@npm:1.33.0" - checksum: 10c0/79172ce5468c8503b49dddfdddc18d3f5fe2599f9b5fe1bc321a8cbee14c96730fc6db22f907b23701b05b2936f865795f62ec3a78a7f3c8cb2450bb68c6763e - languageName: node - linkType: hard - -"mime-types@npm:2.1.18": - version: 2.1.18 - resolution: "mime-types@npm:2.1.18" - dependencies: - mime-db: "npm:~1.33.0" - checksum: 10c0/a96a8d12f4bb98bc7bfac6a8ccbd045f40368fc1030d9366050c3613825d3715d1c1f393e10a75a885d2cdc1a26cd6d5e11f3a2a0d5c4d361f00242139430a0f - languageName: node - linkType: hard - -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.35, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -12728,15 +11715,6 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0": - version: 3.0.0 - resolution: "mime-types@npm:3.0.0" - dependencies: - mime-db: "npm:^1.53.0" - checksum: 10c0/8624501c75568f14e53fd9e12327fa18e70d6a85047c5beb9146990178f3adb31f893cb54d25ab58145cabf3e06c61953f2c5427ccd05b6b38fe09db8c5f5e7f - languageName: node - linkType: hard - "mime@npm:1.6.0, mime@npm:^1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -12783,15 +11761,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - "minimatch@npm:9.0.3, minimatch@npm:^9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -12801,6 +11770,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + "minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -13189,13 +12167,6 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b - languageName: node - linkType: hard - "neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" @@ -13718,7 +12689,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": +"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -13899,7 +12870,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": +"on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -13908,7 +12879,7 @@ __metadata: languageName: node linkType: hard -"once@npm:1.4.0, once@npm:^1.3.0, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -14318,7 +13289,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": +"parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 @@ -14360,13 +13331,6 @@ __metadata: languageName: node linkType: hard -"path-is-inside@npm:1.0.2": - version: 1.0.2 - resolution: "path-is-inside@npm:1.0.2" - checksum: 10c0/7fdd4b41672c70461cce734fc222b33e7b447fa489c7c4377c95e7e6852d83d69741f307d88ec0cc3b385b41cb4accc6efac3c7c511cd18512e95424f5fa980c - languageName: node - linkType: hard - "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -14415,13 +13379,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:3.3.0": - version: 3.3.0 - resolution: "path-to-regexp@npm:3.3.0" - checksum: 10c0/ffa0ebe7088d38d435a8d08b0fe6e8c93ceb2a81a65d4dd1d9a538f52e09d5e3474ed5f553cb3b180d894b0caa10698a68737ab599fd1e56b4663d1a64c9f77b - languageName: node - linkType: hard - "path-to-regexp@npm:^6.2.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" @@ -14429,13 +13386,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^8.0.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a - languageName: node - linkType: hard - "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -14557,13 +13507,6 @@ __metadata: languageName: node linkType: hard -"pkce-challenge@npm:^4.1.0": - version: 4.1.0 - resolution: "pkce-challenge@npm:4.1.0" - checksum: 10c0/7cdc45977eb9af6f561a6f48ffcf19bd3e6f0c651727d00feef1c501384b1ed3c32d92ee67636f02011168959aedf099003a7c0bed668e7943444b20558c54e4 - languageName: node - linkType: hard - "pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -15170,13 +14113,6 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:^1.29.0": - version: 1.30.0 - resolution: "prismjs@npm:1.30.0" - checksum: 10c0/f56205bfd58ef71ccfcbcb691fd0eb84adc96c6ff21b0b69fc6fdcf02be42d6ef972ba4aed60466310de3d67733f6a746f89f2fb79c00bf217406d465b3e8f23 - languageName: node - linkType: hard - "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0" @@ -15310,7 +14246,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.14.0, qs@npm:^6.4.0": +"qs@npm:^6.4.0": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -15349,14 +14285,7 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:1.2.0": - version: 1.2.0 - resolution: "range-parser@npm:1.2.0" - checksum: 10c0/c7aef4f6588eb974c475649c157f197d07437d8c6c8ff7e36280a141463fb5ab7a45918417334ebd7b665c6b8321cf31c763f7631dd5f5db9372249261b8b02a - languageName: node - linkType: hard - -"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": +"range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 @@ -15375,19 +14304,7 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" - unpipe: "npm:1.0.0" - checksum: 10c0/f8daf4b724064a4811d118745a781ca0fb4676298b8adadfd6591155549cfea0a067523cf7dd3baeb1265fecc9ce5dfb2fc788c12c66b85202a336593ece0f87 - languageName: node - linkType: hard - -"react-dom@npm:*, react-dom@npm:^18, react-dom@npm:^18.3.1": +"react-dom@npm:*, react-dom@npm:^18": version: 18.3.1 resolution: "react-dom@npm:18.3.1" dependencies: @@ -15420,80 +14337,7 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll-bar@npm:^2.3.7": - version: 2.3.8 - resolution: "react-remove-scroll-bar@npm:2.3.8" - dependencies: - react-style-singleton: "npm:^2.2.2" - tslib: "npm:^2.0.0" - peerDependencies: - "@types/react": "*" - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/9a0675c66cbb52c325bdbfaed80987a829c4504cefd8ff2dd3b6b3afc9a1500b8ec57b212e92c1fb654396d07bbe18830a8146fe77677d2a29ce40b5e1f78654 - languageName: node - linkType: hard - -"react-remove-scroll@npm:^2.6.3": - version: 2.6.3 - resolution: "react-remove-scroll@npm:2.6.3" - dependencies: - react-remove-scroll-bar: "npm:^2.3.7" - react-style-singleton: "npm:^2.2.3" - tslib: "npm:^2.1.0" - use-callback-ref: "npm:^1.3.3" - use-sidecar: "npm:^1.1.3" - peerDependencies: - "@types/react": "*" - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/068e9704ff26816fffc4c8903e2c6c8df7291ee08615d7c1ab0cf8751f7080e2c5a5d78ef5d908b11b9cfc189f176d312e44cb02ea291ca0466d8283b479b438 - languageName: node - linkType: hard - -"react-simple-code-editor@npm:^0.14.1": - version: 0.14.1 - resolution: "react-simple-code-editor@npm:0.14.1" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10c0/63264fce03315c80de4fc65b9e0a30b359cb0a6c269a90ad67ccff36c21d0f3ba1f9ac9bdfbe76477fc9a6d7033182debc64a58345f4914f0bc0b416eb686e61 - languageName: node - linkType: hard - -"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": - version: 2.2.3 - resolution: "react-style-singleton@npm:2.2.3" - dependencies: - get-nonce: "npm:^1.0.0" - tslib: "npm:^2.0.0" - peerDependencies: - "@types/react": "*" - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/841938ff16d16a6b76895f4cb2e1fea957e5fe3b30febbf03a54892dae1c9153f2383e231dea0b3ba41192ad2f2849448fa859caccd288943bce32639e971bee - languageName: node - linkType: hard - -"react-toastify@npm:^10.0.6": - version: 10.0.6 - resolution: "react-toastify@npm:10.0.6" - dependencies: - clsx: "npm:^2.1.0" - peerDependencies: - react: ">=18" - react-dom: ">=18" - checksum: 10c0/4042b716d008295d0feab32488d1e88ec655a1b7a9176fa7d253c70387578a8a0c04aca0ff86d20e1722f3b4baadae8970f50f462940d67a90453c307dd350a9 - languageName: node - linkType: hard - -"react@npm:*, react@npm:^18, react@npm:^18.3.1": +"react@npm:*, react@npm:^18": version: 18.3.1 resolution: "react@npm:18.3.1" dependencies: @@ -15752,6 +14596,13 @@ __metadata: languageName: node linkType: hard +"repeat-string@npm:^1.6.1": + version: 1.6.1 + resolution: "repeat-string@npm:1.6.1" + checksum: 10c0/87fa21bfdb2fbdedc44b9a5b118b7c1239bdd2c2c1e42742ef9119b7d412a5137a1d23f1a83dc6bb686f4f27429ac6f542e3d923090b44181bafa41e8ac0174d + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -16201,17 +15052,6 @@ __metadata: languageName: node linkType: hard -"router@npm:^2.0.0": - version: 2.1.0 - resolution: "router@npm:2.1.0" - dependencies: - is-promise: "npm:^4.0.0" - parseurl: "npm:^1.3.3" - path-to-regexp: "npm:^8.0.0" - checksum: 10c0/df17c5c07210cb72922185ea9378b318676c1e1af9110c52cfa8339bff43004f240cb6660d3b23c932f7867ab6a3ed3fe9d8e8fb0d7ac7402d0b7e7a59f9ba07 - languageName: node - linkType: hard - "rrweb-cssom@npm:^0.6.0": version: 0.6.0 resolution: "rrweb-cssom@npm:0.6.0" @@ -16258,15 +15098,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 - languageName: node - linkType: hard - "safe-array-concat@npm:^1.0.1": version: 1.0.1 resolution: "safe-array-concat@npm:1.0.1" @@ -16483,26 +15314,6 @@ __metadata: languageName: node linkType: hard -"send@npm:^1.0.0, send@npm:^1.1.0": - version: 1.1.0 - resolution: "send@npm:1.1.0" - dependencies: - debug: "npm:^4.3.5" - destroy: "npm:^1.2.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - fresh: "npm:^0.5.2" - http-errors: "npm:^2.0.0" - mime-types: "npm:^2.1.35" - ms: "npm:^2.1.3" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/0d73408bccfd008bb50cb97225434cf565f653b66cb7961befa962a321a936eaefa6c481a1a9c30606f341afb1f08d990bcbf44949f48a68e06d63344eb91105 - languageName: node - linkType: hard - "serialize-javascript@npm:^6.0.1": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -16512,21 +15323,6 @@ __metadata: languageName: node linkType: hard -"serve-handler@npm:^6.1.6": - version: 6.1.6 - resolution: "serve-handler@npm:6.1.6" - dependencies: - bytes: "npm:3.0.0" - content-disposition: "npm:0.5.2" - mime-types: "npm:2.1.18" - minimatch: "npm:3.1.2" - path-is-inside: "npm:1.0.2" - path-to-regexp: "npm:3.3.0" - range-parser: "npm:1.2.0" - checksum: 10c0/1e1cb6bbc51ee32bc1505f2e0605bdc2e96605c522277c977b67f83be9d66bd1eec8604388714a4d728e036d86b629bc9aec02120ea030d3d2c3899d44696503 - languageName: node - linkType: hard - "serve-static@npm:1.16.2": version: 1.16.2 resolution: "serve-static@npm:1.16.2" @@ -16539,18 +15335,6 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:^2.1.0": - version: 2.1.0 - resolution: "serve-static@npm:2.1.0" - dependencies: - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - parseurl: "npm:^1.3.3" - send: "npm:^1.0.0" - checksum: 10c0/f5b36fd3a763cdb4bf367c9be2eff587c0e52685fcf9109aa28428457c4aad0f83d8b6a1bf4929488963270026f3647f2d221c898e6dbdf9f4f5b09ed0b1f51a - languageName: node - linkType: hard - "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -16650,13 +15434,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.8.1, shell-quote@npm:^1.8.2": - version: 1.8.2 - resolution: "shell-quote@npm:1.8.2" - checksum: 10c0/85fdd44f2ad76e723d34eb72c753f04d847ab64e9f1f10677e3f518d0e5b0752a176fd805297b30bb8c3a1556ebe6e77d2288dbd7b7b0110c7e941e9e9c20ce1 - languageName: node - linkType: hard - "shelljs@npm:^0.8.5": version: 0.8.5 resolution: "shelljs@npm:0.8.5" @@ -16905,16 +15682,6 @@ __metadata: languageName: node linkType: hard -"spawn-rx@npm:^5.1.2": - version: 5.1.2 - resolution: "spawn-rx@npm:5.1.2" - dependencies: - debug: "npm:^4.3.7" - rxjs: "npm:^7.8.1" - checksum: 10c0/1a8572da209161063c5ed25b44d5b5bd786ceb51c15d355e499c518feb1da675004fb40e3f20f653025a2ec2feb4c676d1e52cc2ed0fa40ae65e40cc972d60cd - languageName: node - linkType: hard - "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -17413,7 +16180,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1, supports-color@npm:~8.1.1": +"supports-color@npm:^8.0.0, supports-color@npm:~8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -17436,22 +16203,6 @@ __metadata: languageName: node linkType: hard -"tailwind-merge@npm:^2.5.3": - version: 2.6.0 - resolution: "tailwind-merge@npm:2.6.0" - checksum: 10c0/fc8a5535524de9f4dacf1c16ab298581c7bb757d68a95faaf28942b1c555a619bba9d4c6726fe83986e44973b315410c1a5226e5354c30ba82353bd6d2288fa5 - languageName: node - linkType: hard - -"tailwindcss-animate@npm:^1.0.7": - version: 1.0.7 - resolution: "tailwindcss-animate@npm:1.0.7" - peerDependencies: - tailwindcss: "*" - checksum: 10c0/ec7dbd1631076b97d66a1fbaaa06e0725fccfa63119221e8d87a997b02dcede98ad88bb1ef6665b968f5d260fcefb10592e0299ca70208d365b37761edf5e19a - languageName: node - linkType: hard - "tailwindcss@npm:^3.4.1": version: 3.4.6 resolution: "tailwindcss@npm:3.4.6" @@ -17773,15 +16524,6 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:^1.2.2": - version: 1.2.2 - resolution: "tree-kill@npm:1.2.2" - bin: - tree-kill: cli.js - checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 - languageName: node - linkType: hard - "trim-newlines@npm:^3.0.0": version: 3.0.1 resolution: "trim-newlines@npm:3.0.1" @@ -17875,13 +16617,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - "tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -17983,17 +16718,6 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^2.0.0": - version: 2.0.0 - resolution: "type-is@npm:2.0.0" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10c0/c1fa697c8cb77bcb3f4aa3673a3fdbe3f1c2fe51ce8c9736e9773f25c3118562723369b471063c3945722bcda5d8bf969a0693ab7b6e7da012ff667294efe988 - languageName: node - linkType: hard - "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -18487,37 +17211,6 @@ __metadata: languageName: node linkType: hard -"use-callback-ref@npm:^1.3.3": - version: 1.3.3 - resolution: "use-callback-ref@npm:1.3.3" - dependencies: - tslib: "npm:^2.0.0" - peerDependencies: - "@types/react": "*" - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/f887488c6e6075cdad4962979da1714b217bcb1ee009a9e57ce9a844bcfc4c3a99e93983dfc2e5af9e0913824d24e730090ff255e902c516dcb58d2d3837e01c - languageName: node - linkType: hard - -"use-sidecar@npm:^1.1.3": - version: 1.1.3 - resolution: "use-sidecar@npm:1.1.3" - dependencies: - detect-node-es: "npm:^1.1.0" - tslib: "npm:^2.0.0" - peerDependencies: - "@types/react": "*" - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/161599bf921cfaa41c85d2b01c871975ee99260f3e874c2d41c05890d41170297bdcf314bc5185e7a700de2034ac5b888e3efc8e9f35724f4918f53538d717c9 - languageName: node - linkType: hard - "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -18601,7 +17294,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:~1.1.2": +"vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f @@ -19469,21 +18162,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.0": - version: 8.18.1 - resolution: "ws@npm:8.18.1" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/e498965d6938c63058c4310ffb6967f07d4fa06789d3364829028af380d299fe05762961742971c764973dce3d1f6a2633fe8b2d9410c9b52e534b4b882a99fa - languageName: node - linkType: hard - "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -19633,16 +18311,7 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.24.1": - version: 3.24.3 - resolution: "zod-to-json-schema@npm:3.24.3" - peerDependencies: - zod: ^3.24.1 - checksum: 10c0/5d626fa7a51539236962b1348a7b7e7111bd1722f23ad06dead2f76599e6bd918e4067ffba0695e0acac5a60f217b4953d2ad62ad403a482d034d94f025f3a4c - languageName: node - linkType: hard - -"zod@npm:^3.23.8, zod@npm:^3.24.2": +"zod@npm:^3.24.2": version: 3.24.2 resolution: "zod@npm:3.24.2" checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 From aa5af954aea818ec2cb2d8dcac518b2b5e2bd312 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 29 Apr 2025 15:45:33 +0200 Subject: [PATCH 04/12] docs: update readme --- packages/cli/README.md | 75 +++++++++++++----------------------------- 1 file changed, 22 insertions(+), 53 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 2ae99b60..10e6df21 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -99,7 +99,7 @@ All-in-one command to get started quickly. This command combines `init`, feature and type generation in a single step. Use this for the fastest way to get up and running with Bucket. ```bash -npx bucket new "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom] [--out gen/features.ts] [--format react] +npx bucket new "My Feature" [--app-id ap123456789] [--key my-feature] [--key-format custom] [--out gen/features.ts] [--format react] ``` Options: @@ -138,7 +138,7 @@ Create a new feature in your Bucket app. The command guides you through the feature creation process with interactive prompts if options are not provided. ```bash -npx bucket features create "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom] +npx bucket features create "My Feature" [--app-id ap123456789] [--key my-feature] [--key-format custom] ``` Options: @@ -205,7 +205,7 @@ Grant or revoke access to specific features for companies, segments, and users. If no feature key is provided, you'll be prompted to select one from a list. ```bash -npx bucket companies features access [featureKey] [--enable|--disable] [--companies ] [--segments ] [--users ] [--app-id ap123456789] +npx bucket companies features access [--app-id ap123456789] [featureKey] [--enable|--disable] [--companies ] [--segments ] [--users ] ``` Arguments: @@ -252,7 +252,7 @@ Bucket provides powerful AI-assisted development capabilities through rules and The `rules` command helps you set up AI-specific rules for your project. These rules enable AI tools to better understand how to work with Bucket and feature flags and how they should be used in your codebase. ```bash -npx bucket rules [--format cursor|copilot] [--yes] +npx bucket rules [--format ] [--yes] ``` Options: @@ -266,67 +266,36 @@ This command will add rules to your project that provide AI tools with context a ## Model Context Protocol -The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your development environment to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. MCP is in a very early stage of development and changes are frequent, if something isn't working please check out the [Model Context Protocol Website](https://modelcontextprotocol.io/) and open an [issue ticket here](https://github.com/bucketco/bucket-javascript-sdk/issues). +The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your development environment to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. + +_**Note: The Bucket MCP server is now remote hosted and the `mcp` command has been repurposed to help you connect to the new server.**_ ### Setting up MCP -MCP servers currently run locally on your machine. To start the MCP server run the CLI command from your Bucket initialized project directory: +The `mcp` command helps you configure your editor or AI client to connect with Bucket's remote MCP server. This allows your AI tools to understand your feature flags and provide more contextual assistance. ```bash -npx bucket mcp [--port ] [--app-id ap123456789] +npx bucket mcp [--app-id ] [--editor ] [--scope ] ``` Options: -- `--port`: Port to run the SSE server on (defaults to 8050, "auto" for random port). -- `--app-id`: App ID to use. - -This will start an SSE server at `http://localhost:8050/sse` by default which you can connect to using your [client of choice](https://modelcontextprotocol.io/clients). Below are examples that work for [Cursor IDE](https://www.cursor.com/) and [Claude Desktop](https://claude.ai/download). - -#### Server-Side Events (SSE) - -```json -{ - "mcpServers": { - "Bucket": { - "url": "http://localhost:8050/sse" - } - } -} -``` - -#### STDIO Proxy - -Some clients don't support SSE and can instead interface with the MCP server over a STDIO proxy. - -```json -{ - "mcpServers": { - "Bucket": { - "command": "npx", - "args": ["-y", "supergateway", "--sse", "http://localhost:8050/sse"] - } - } -} -``` - -### Cursor IDE - -To enable MCP features in [Cursor IDE](https://www.cursor.com/): - -1. Open Cursor IDE. -2. Go to `Settings > MCP`. -3. Click `Add new global MCP server` and paste the `SSE` config. -4. Save and go back to Cursor. +- `--app-id`: App ID to use for the MCP connection. +- `--editor`: The editor/client to configure: + - `cursor`: [Cursor IDE](https://www.cursor.com/) + - `vscode`: [Visual Studio Code](https://code.visualstudio.com/) + - `claude`: [Claude Desktop](https://claude.ai/download) + - `windsurf`: [Windsurf](https://windsurf.com/editor) +- `--scope`: Whether to configure settings globally or locally for the project. -### Claude Desktop +The command will guide you through: -To enable MCP features in [Claude Desktop](https://claude.ai/download): +1. Selecting which editor/client to configure. +2. Choosing which Bucket app to connect to. +3. Deciding between global or project-local configuration. +4. Setting up the appropriate configuration file for your chosen editor . -1. Open Claude Desktop. -2. Go to `Settings > Developer`. -3. Click `Edit config` and paste the `STDIO` config. -4. Save and restart Claude Desktop. +_**Note: The setup uses [mcp-remote](https://github.com/geelen/mcp-remote) as a compatibility layer allowing the remote hosted Bucket MCP server to work with all editors/clients that support MCP STDIO servers. If your editor/client supports HTTP Streaming with OAuth you can connect to the Bucket MCP server directly.**_ ## Development From 8eac7b3de6b7fcd5ffcd11c43c93017a21a32c4d Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 29 Apr 2025 15:53:25 +0200 Subject: [PATCH 05/12] style: fix formatting --- packages/cli/commands/rules.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/cli/commands/rules.ts b/packages/cli/commands/rules.ts index 21cae592..99b83e59 100644 --- a/packages/cli/commands/rules.ts +++ b/packages/cli/commands/rules.ts @@ -1,11 +1,7 @@ import { confirm } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import { - mkdir, - readFile, - writeFile, -} from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, relative } from "node:path"; import ora from "ora"; From 2c265e6bbcff13eb6210e329174b7cd1521bd64c Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 29 Apr 2025 21:55:20 +0200 Subject: [PATCH 06/12] style: fix order --- packages/cli/utils/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/utils/auth.ts b/packages/cli/utils/auth.ts index fd493c70..052eda79 100644 --- a/packages/cli/utils/auth.ts +++ b/packages/cli/utils/auth.ts @@ -6,8 +6,8 @@ import open from "open"; import { authStore } from "../stores/auth.js"; import { configStore } from "../stores/config.js"; -import { errorUrl, loginUrl, successUrl } from "./urls.js"; import { ParamType } from "./types.js"; +import { errorUrl, loginUrl, successUrl } from "./urls.js"; interface waitForAccessToken { accessToken: string; From 7a6e7a5918cf487eb973e07cb917a82271db042c Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Thu, 1 May 2025 08:35:07 +0200 Subject: [PATCH 07/12] fix: indentation of feature types generate command --- packages/cli/utils/gen.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/utils/gen.ts b/packages/cli/utils/gen.ts index 67dbd015..ca4b15e4 100644 --- a/packages/cli/utils/gen.ts +++ b/packages/cli/utils/gen.ts @@ -68,11 +68,16 @@ export const KeyFormatPatterns: Record = { }, }; -export function indentLines(str: string, indent = 2, lineBreak = "\n"): string { +export function indentLines( + str: string, + indent = 2, + lineBreak = "\n", + trim = false, +): string { const indentStr = " ".repeat(indent); return str .split(lineBreak) - .map((line) => `${indentStr}${line.trim()}`) + .map((line) => `${indentStr}${trim ? line.trim() : line}`) .join(lineBreak); } From a211fd28da0af05d8edadb2ded7cdeca8139d636 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Wed, 7 May 2025 20:44:36 +0200 Subject: [PATCH 08/12] fix: slight mcp command improvements --- packages/cli/commands/mcp.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli/commands/mcp.ts b/packages/cli/commands/mcp.ts index a9001510..e76a5837 100644 --- a/packages/cli/commands/mcp.ts +++ b/packages/cli/commands/mcp.ts @@ -185,10 +185,7 @@ export const mcpAction = async (options: { const newEntryValue = { type: "stdio", command: "npx", - args: [ - "mcp-remote@next", // todo: remove next once stable - mcpUrlWithAppId, // Always include appId explicitly as that is more stable - ], + args: ["mcp-remote@latest", mcpUrlWithAppId], }; // Update Config Object From 753e6004bf37cb2800b328f916baa4e7c8e93ec8 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 13 May 2025 12:31:48 +0200 Subject: [PATCH 09/12] Update packages/cli/README.md Co-authored-by: Ron Cohen --- packages/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 10e6df21..46014fd6 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -266,7 +266,7 @@ This command will add rules to your project that provide AI tools with context a ## Model Context Protocol -The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your development environment to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. +The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your code editor to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. The MCP server is hosted by Bucket, so it's very easy to get started. _**Note: The Bucket MCP server is now remote hosted and the `mcp` command has been repurposed to help you connect to the new server.**_ From 1008b03c58b623fbccb9720a4abbb735ff3f3c12 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 13 May 2025 12:32:02 +0200 Subject: [PATCH 10/12] Update packages/cli/README.md Co-authored-by: Ron Cohen --- packages/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 46014fd6..7e93bcbe 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -268,7 +268,7 @@ This command will add rules to your project that provide AI tools with context a The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your code editor to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. The MCP server is hosted by Bucket, so it's very easy to get started. -_**Note: The Bucket MCP server is now remote hosted and the `mcp` command has been repurposed to help you connect to the new server.**_ +_**Note: The Bucket `mcp` CLI command was previously used for a _local_ server. However, in recent versions of the Bucket CLI, the `mcp` command has been repurposed to help you connect to the new remote MCP server.**_ ### Setting up MCP From d638c729d2fde29714978017d64979919f926f63 Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 13 May 2025 12:40:16 +0200 Subject: [PATCH 11/12] style: fix formatting --- packages/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 7e93bcbe..83784c2e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -268,7 +268,7 @@ This command will add rules to your project that provide AI tools with context a The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your code editor to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. The MCP server is hosted by Bucket, so it's very easy to get started. -_**Note: The Bucket `mcp` CLI command was previously used for a _local_ server. However, in recent versions of the Bucket CLI, the `mcp` command has been repurposed to help you connect to the new remote MCP server.**_ +_\*\*Note: The Bucket `mcp` CLI command was previously used for a \_local_ server. However, in recent versions of the Bucket CLI, the `mcp` command has been repurposed to help you connect to the new remote MCP server.\*\*\_ ### Setting up MCP From 7a7502ef28baa22d314a3e491230f0531b763e1f Mon Sep 17 00:00:00 2001 From: Erik Hughes Date: Tue, 13 May 2025 12:46:16 +0200 Subject: [PATCH 12/12] feat: bumping release to major version --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 63eea9f9..9e208261 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/cli", - "version": "0.6.0", + "version": "1.0.0", "packageManager": "yarn@4.1.1", "description": "CLI for Bucket service", "main": "./dist/index.js",