From 43545c105cb3610cba544b4f53cf0bad4a68f3b1 Mon Sep 17 00:00:00 2001 From: steveluc Date: Fri, 16 Jan 2026 23:19:53 -0800 Subject: [PATCH 1/7] Add command executor MCP server Introduces a new MCP (Model Context Protocol) server that enables Claude Code to execute TypeAgent commands for music playback, list management, calendar operations, and other natural language tasks. Key features: - Connects Claude Code to TypeAgent dispatcher via WebSocket - Automatic reconnection with 5-second retry interval - Comprehensive logging to temp files for debugging - Supports natural language commands (e.g., "play bohemian rhapsody", "add milk to grocery list") - Clean output formatting with HTML image tags stripped Includes example configuration for .mcp.json and detailed installation instructions in README. Co-Authored-By: Claude Sonnet 4.5 --- ts/.mcp.json | 11 + ts/packages/commandExecutor/README.md | 179 ++ .../commandExecutor/dist-test/testClient.d.ts | 2 + .../commandExecutor/dist-test/testClient.js | 61 + .../examples/claude_desktop_config.json | 10 + .../commandExecutor/examples/example_usage.md | 76 + ts/packages/commandExecutor/package-lock.json | 1501 +++++++++++++++++ ts/packages/commandExecutor/package.json | 39 + .../commandExecutor/src/commandServer.ts | 370 ++++ ts/packages/commandExecutor/src/index.ts | 4 + ts/packages/commandExecutor/src/server.ts | 15 + ts/packages/commandExecutor/src/tsconfig.json | 6 + .../commandExecutor/test/testClient.ts | 84 + .../commandExecutor/test/tsconfig.json | 6 + ts/packages/commandExecutor/tsconfig.json | 11 + ts/packages/commandExecutor/view-logs.sh | 29 + ts/pnpm-lock.yaml | 34 + 17 files changed, 2438 insertions(+) create mode 100644 ts/.mcp.json create mode 100644 ts/packages/commandExecutor/README.md create mode 100644 ts/packages/commandExecutor/dist-test/testClient.d.ts create mode 100644 ts/packages/commandExecutor/dist-test/testClient.js create mode 100644 ts/packages/commandExecutor/examples/claude_desktop_config.json create mode 100644 ts/packages/commandExecutor/examples/example_usage.md create mode 100644 ts/packages/commandExecutor/package-lock.json create mode 100644 ts/packages/commandExecutor/package.json create mode 100644 ts/packages/commandExecutor/src/commandServer.ts create mode 100644 ts/packages/commandExecutor/src/index.ts create mode 100644 ts/packages/commandExecutor/src/server.ts create mode 100644 ts/packages/commandExecutor/src/tsconfig.json create mode 100644 ts/packages/commandExecutor/test/testClient.ts create mode 100644 ts/packages/commandExecutor/test/tsconfig.json create mode 100644 ts/packages/commandExecutor/tsconfig.json create mode 100644 ts/packages/commandExecutor/view-logs.sh diff --git a/ts/.mcp.json b/ts/.mcp.json new file mode 100644 index 000000000..ed02dc794 --- /dev/null +++ b/ts/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "command-executor": { + "command": "node", + "args": ["packages/commandExecutor/dist/server.js"], + "env": { + "DEBUG": "*" + } + } + } +} diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md new file mode 100644 index 000000000..9e33477b1 --- /dev/null +++ b/ts/packages/commandExecutor/README.md @@ -0,0 +1,179 @@ +# Command Executor MCP Server + +An MCP (Model Context Protocol) server that connects to the TypeAgent dispatcher to execute user commands like playing music, managing lists, working with calendars, and more. + +## Overview + +This MCP server acts as a bridge between Claude Code (or other MCP clients) and the TypeAgent system. It accepts natural language commands and forwards them to the TypeAgent dispatcher for execution. + +## Prerequisites + +1. **Built Package**: Build this package before using: + ```bash + pnpm run build + ``` + +2. **TypeAgent Server** (optional at startup): The TypeAgent dispatcher server at `ws://localhost:8999`. The MCP server will automatically connect when the TypeAgent server becomes available and reconnect if the connection is lost. + + Start the TypeAgent server with: + ```bash + pnpm run start:agent-server + ``` + +## Configuration + +The server can be configured via environment variables or constructor parameters: + +- **AGENT_SERVER_URL**: WebSocket URL of the TypeAgent dispatcher (default: `ws://localhost:8999`) + +You can set this in the `.env` file at the root of the TypeAgent repository. + +## Installation + +### For Claude Code Users + +1. **Build the package** from the TypeAgent repository root: + ```bash + cd ts + pnpm run build + ``` + +2. **Configure Claude Code** to use the MCP server. Add the following to your `.mcp.json` file in the TypeAgent repository root (create it if it doesn't exist): + ```json + { + "mcpServers": { + "command-executor": { + "command": "node", + "args": [ + "packages/commandExecutor/dist/server.js" + ] + } + } + } + ``` + +3. **Restart Claude Code** to load the MCP server configuration. + +4. **Start the TypeAgent server** (can be done before or after starting Claude Code): + ```bash + pnpm run start:agent-server + ``` + +5. **Test it** by sending commands through Claude Code: + - "play bohemian rhapsody by queen" + - "what's on my grocery list" + - "add milk to my shopping list" + +### For Other MCP Clients + +The server is configured in `.mcp.json`: + +```json +{ + "mcpServers": { + "command-executor": { + "command": "node", + "args": [ + "packages/commandExecutor/dist/server.js" + ] + } + } +} +``` + +### Available Tools + +#### execute_command +Execute user commands such as playing music, managing lists, or working with calendars. + +**Parameters:** +- `request` (string): The natural language command to execute + +**Examples:** +- "play sweet emotion by aerosmith" +- "add jelly beans to my grocery list" +- "schedule a meeting for tomorrow at 2pm" + +#### ping (debug mode) +Test server connectivity. + +**Parameters:** +- `message` (string): Message to echo back + +## Architecture + +``` +Claude Code (MCP Client) + ↓ +Command Executor MCP Server + ↓ +TypeAgent Dispatcher (WebSocket) + ↓ +TypeAgent Agents (Music, Lists, Calendar, etc.) +``` + +The MCP server: +1. Receives commands from the MCP client +2. Connects to the TypeAgent dispatcher via WebSocket +3. Forwards commands to the dispatcher's `processCommand` method +4. Returns results back to the client + +## Connection & Reconnection + +The MCP server includes automatic reconnection capabilities: + +- **Startup**: The server starts immediately, even if the TypeAgent dispatcher is not running +- **Lazy Connection**: When you send the first command, it will attempt to connect if not already connected +- **Auto-Reconnect**: Every 5 seconds, the server checks the connection and reconnects if needed +- **Error Recovery**: If a command fails due to connection loss, the dispatcher is marked as disconnected and will automatically reconnect + +**Recommended workflow:** +1. Start Claude Code (the MCP server starts automatically) +2. Start the TypeAgent server: `pnpm run start:agent-server` +3. Send commands - the MCP server will connect automatically + +You can also start the TypeAgent server first, or restart it at any time without restarting the MCP server. + +## Debugging and Logs + +The MCP server automatically logs all activity to both console and a log file for debugging. + +### Log File Location +Logs are written to: `/tmp/typeagent-mcp/mcp-server-.log` + +### Viewing Logs +Use the provided helper script to view the most recent log file: + +```bash +# View the entire log +./packages/commandExecutor/view-logs.sh + +# Follow the log in real-time +./packages/commandExecutor/view-logs.sh -f +``` + +### What Gets Logged +- Server initialization and configuration +- Connection attempts to TypeAgent dispatcher +- Connection success/failure with error details +- Reconnection attempts +- All incoming user requests +- Command execution results +- Errors with stack traces + +This is particularly useful for debugging connection issues between the MCP server and the TypeAgent dispatcher. + +## Development + +### Building +```bash +pnpm run build +``` + +### Running Standalone +```bash +pnpm run start +``` + +### Testing +Use the MCP client (like Claude Code) to test commands, or use the TypeAgent CLI to verify the dispatcher is working. diff --git a/ts/packages/commandExecutor/dist-test/testClient.d.ts b/ts/packages/commandExecutor/dist-test/testClient.d.ts new file mode 100644 index 000000000..9e721977d --- /dev/null +++ b/ts/packages/commandExecutor/dist-test/testClient.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=testClient.d.ts.map \ No newline at end of file diff --git a/ts/packages/commandExecutor/dist-test/testClient.js b/ts/packages/commandExecutor/dist-test/testClient.js new file mode 100644 index 000000000..b3587ca7a --- /dev/null +++ b/ts/packages/commandExecutor/dist-test/testClient.js @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +async function testCommandServer() { + console.log("Creating MCP client..."); + const client = new Client({ + name: "Test-Client", + version: "1.0.0", + }); + const transport = new StdioClientTransport({ + command: "node", + args: ["../dist/server.js"], + }); + await client.connect(transport); + console.log("Connected to Command Executor Server\n"); + // Test 1: Execute a music command + console.log("Test 1: Play music command"); + const musicResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "play shake it off by taylor swift" }, + })); + console.log("Response:", musicResult.content[0].type === "text" + ? musicResult.content[0].text + : "No text response"); + console.log(); + // Test 2: Execute a list command + console.log("Test 2: Add to list command"); + const listResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "add ham to my grocery list" }, + })); + console.log("Response:", listResult.content[0].type === "text" + ? listResult.content[0].text + : "No text response"); + console.log(); + // Test 3: Execute a calendar command + console.log("Test 3: Calendar command"); + const calendarResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "add meeting tomorrow at 3pm" }, + })); + console.log("Response:", calendarResult.content[0].type === "text" + ? calendarResult.content[0].text + : "No text response"); + console.log(); + // Test 4: Ping diagnostic tool + console.log("Test 4: Ping diagnostic"); + const pingResult = (await client.callTool({ + name: "ping", + arguments: { message: "test connection" }, + })); + console.log("Response:", pingResult.content[0].type === "text" + ? pingResult.content[0].text + : "No text response"); + console.log(); + await client.close(); + console.log("All tests completed successfully!"); +} +testCommandServer().catch(console.error); +//# sourceMappingURL=testClient.js.map \ No newline at end of file diff --git a/ts/packages/commandExecutor/examples/claude_desktop_config.json b/ts/packages/commandExecutor/examples/claude_desktop_config.json new file mode 100644 index 000000000..3be97d654 --- /dev/null +++ b/ts/packages/commandExecutor/examples/claude_desktop_config.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "command-executor": { + "command": "node", + "args": [ + "C:/Users/stevenlucco/src/TypeAgent/ts/packages/commandExecutor/dist/server.js" + ] + } + } +} diff --git a/ts/packages/commandExecutor/examples/example_usage.md b/ts/packages/commandExecutor/examples/example_usage.md new file mode 100644 index 000000000..395028622 --- /dev/null +++ b/ts/packages/commandExecutor/examples/example_usage.md @@ -0,0 +1,76 @@ +# Command Executor MCP Server - Example Usage + +## Configuration + +To use this MCP server with Claude Desktop, add the following to your Claude Desktop configuration: + +### Windows +Edit `%APPDATA%\Claude\claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "command-executor": { + "command": "node", + "args": [ + "C:/Users/YOUR_USERNAME/src/TypeAgent/ts/packages/commandExecutor/dist/server.js" + ] + } + } +} +``` + +### macOS +Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "command-executor": { + "command": "node", + "args": [ + "/Users/YOUR_USERNAME/src/TypeAgent/ts/packages/commandExecutor/dist/server.js" + ] + } + } +} +``` + +## Example Commands + +Once configured, you can ask Claude to execute various commands: + +### Music Commands +- "Play shake it off by taylor swift" +- "Skip to the next song" +- "Pause the music" +- "Set volume to 50%" + +### List Management +- "Add ham to my grocery list" +- "Add milk, eggs, and bread to shopping list" +- "Remove bananas from grocery list" +- "Show me my grocery list" + +### Calendar Operations +- "Add meeting tomorrow at 3pm" +- "Schedule dentist appointment for next Tuesday at 10am" +- "What's on my calendar today?" +- "Cancel my 2pm meeting" + +## Testing + +The server will log all incoming requests to the console. You can verify it's working by: + +1. Restart Claude Desktop after updating the configuration +2. Send a command like "Add ham to my grocery list" +3. Claude will use the `execute_command` tool and receive: `Finished add ham to my grocery list` + +## Current Behavior + +The MCP server currently: +- Accepts command requests via the `execute_command` tool +- Logs the request to the console +- Returns a success message to Claude + +Future versions will integrate with an actual command execution service to perform the requested actions. diff --git a/ts/packages/commandExecutor/package-lock.json b/ts/packages/commandExecutor/package-lock.json new file mode 100644 index 000000000..6461246fd --- /dev/null +++ b/ts/packages/commandExecutor/package-lock.json @@ -0,0 +1,1501 @@ +{ + "name": "command-executor-mcp", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "command-executor-mcp", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "dotenv": "^16.3.1", + "zod": "^4.1.13" + }, + "devDependencies": { + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "typescript": "~5.4.5" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "dependencies": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/prettier": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/ts/packages/commandExecutor/package.json b/ts/packages/commandExecutor/package.json new file mode 100644 index 000000000..71a04e9cc --- /dev/null +++ b/ts/packages/commandExecutor/package.json @@ -0,0 +1,39 @@ +{ + "name": "command-executor-mcp", + "version": "0.0.1", + "private": true, + "description": "MCP server for executing user commands like playing music, managing lists, and working with calendars.", + "homepage": "https://github.com/microsoft/TypeAgent#readme", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/TypeAgent.git", + "directory": "ts/packages/commandExecutor" + }, + "license": "MIT", + "author": "Microsoft", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "npm run tsc", + "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", + "prettier": "prettier --check . --ignore-path ../../.prettierignore", + "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", + "tsc": "tsc -b", + "start": "node dist/server.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "@typeagent/agent-sdk": "workspace:*", + "@typeagent/agent-server-client": "workspace:*", + "@typeagent/dispatcher-types": "workspace:*", + "dotenv": "^16.3.1", + "isomorphic-ws": "^5.0.0", + "zod": "^4.1.13" + }, + "devDependencies": { + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "typescript": "~5.4.5" + } +} diff --git a/ts/packages/commandExecutor/src/commandServer.ts b/ts/packages/commandExecutor/src/commandServer.ts new file mode 100644 index 000000000..93dfc123b --- /dev/null +++ b/ts/packages/commandExecutor/src/commandServer.ts @@ -0,0 +1,370 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod/v4"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { connectDispatcher } from "@typeagent/agent-server-client"; +import type { + ClientIO, + IAgentMessage, + RequestId, + TemplateEditConfig, +} from "@typeagent/dispatcher-types"; +import type { Dispatcher } from "@typeagent/dispatcher-types"; +import { DisplayAppendMode } from "@typeagent/agent-sdk"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; + +function executeCommandRequestSchema() { + return { + request: z.string(), + }; +} +const ExecuteCommandRequestSchema = z.object(executeCommandRequestSchema()); + +export type ExecuteCommandRequest = z.infer< + typeof ExecuteCommandRequestSchema +>; + +function toolResult(result: string): CallToolResult { + return { + content: [{ type: "text", text: result }], + }; +} + +/** + * Logger utility that writes to both console and a log file + */ +class Logger { + private logFilePath: string; + private logStream: fs.WriteStream; + + constructor() { + const logDir = path.join(os.tmpdir(), "typeagent-mcp"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + this.logFilePath = path.join(logDir, `mcp-server-${Date.now()}.log`); + this.logStream = fs.createWriteStream(this.logFilePath, { flags: "a" }); + this.log(`Log file created at: ${this.logFilePath}`); + } + + private formatMessage(level: string, message: string): string { + const timestamp = new Date().toISOString(); + return `[${timestamp}] [${level}] ${message}`; + } + + log(message: string): void { + const formatted = this.formatMessage("INFO", message); + console.log(formatted); + this.logStream.write(formatted + "\n"); + } + + error(message: string, error?: any): void { + const errorDetails = error ? ` - ${error instanceof Error ? error.message : String(error)}` : ""; + const formatted = this.formatMessage("ERROR", message + errorDetails); + console.error(formatted); + this.logStream.write(formatted + "\n"); + if (error?.stack) { + this.logStream.write(error.stack + "\n"); + } + } + + getLogFilePath(): string { + return this.logFilePath; + } + + close(): void { + this.logStream.end(); + } +} + +/** + * Remove ANSI escape codes from a string + */ +function stripAnsi(text: string): string { + // eslint-disable-next-line no-control-regex + return text.replace(/\x1b\[[0-9;]*m/g, ""); +} + +// downloadImage function removed - images are not downloaded or displayed + +/** + * Process HTML content to download images and replace img tags with file references + */ +async function processHtmlImages(content: string): Promise { + // Find all img tags with src attributes + const imgRegex = /]+src="([^"]+)"[^>]*>/gi; + let processed = content; + const matches = [...content.matchAll(imgRegex)]; + + for (const match of matches) { + const fullTag = match[0]; + + // Just remove the img tag entirely - don't download or display artwork + processed = processed.replace(fullTag, ''); + } + + return processed; +} + +/** + * Minimal ClientIO implementation for MCP server + * Most methods are no-ops since we just need to satisfy the interface + */ +function createMcpClientIO(logger: Logger, responseCollector: { messages: string[] }): ClientIO { + return { + clear(): void { + logger.log("ClientIO: clear() called"); + }, + exit(): void { + logger.log("ClientIO: exit() called"); + }, + setDisplayInfo(): void {}, + setDisplay(message: IAgentMessage): void { + logger.log(`ClientIO: setDisplay() - ${JSON.stringify(message)}`); + if (typeof message === 'object' && 'message' in message) { + const msg = message.message; + // Filter out "info" kind messages (technical translation details) + if (typeof msg === 'object' && msg && 'kind' in msg && msg.kind === 'info') { + return; + } + + if (typeof msg === 'string') { + responseCollector.messages.push(stripAnsi(msg)); + } else if (typeof msg === 'object' && msg && 'content' in msg) { + responseCollector.messages.push(stripAnsi(String(msg.content))); + } + } + }, + appendDisplay(message: IAgentMessage, mode: DisplayAppendMode): void { + logger.log(`ClientIO: appendDisplay(mode=${mode}) - ${JSON.stringify(message)}`); + // Only capture block mode messages (final results), not temporary status messages + if (mode === 'block' && typeof message === 'object' && 'message' in message) { + const msg = message.message; + // Filter out "info" kind messages (technical translation details) + if (typeof msg === 'object' && msg && 'kind' in msg && msg.kind === 'info') { + return; + } + + if (typeof msg === 'string') { + responseCollector.messages.push(stripAnsi(msg)); + } else if (typeof msg === 'object' && msg && 'content' in msg) { + responseCollector.messages.push(stripAnsi(String(msg.content))); + } + } + }, + appendDiagnosticData(requestId: RequestId, data: any): void { + logger.log(`ClientIO: appendDiagnosticData(requestId=${requestId}) - ${JSON.stringify(data)}`); + }, + setDynamicDisplay(): void {}, + async askYesNo( + message: string, + requestId: RequestId, + defaultValue?: boolean, + ): Promise { + logger.log(`ClientIO: askYesNo(requestId=${requestId}) - "${message}" (defaulting to ${defaultValue ?? false})`); + return defaultValue ?? false; + }, + async proposeAction( + actionTemplates: TemplateEditConfig, + requestId: RequestId, + source: string, + ): Promise { + logger.log(`ClientIO: proposeAction(requestId=${requestId}, source=${source}) - ${JSON.stringify(actionTemplates)}`); + return undefined; + }, + async popupQuestion( + message: string, + choices: string[], + defaultId: number | undefined, + source: string, + ): Promise { + logger.log(`ClientIO: popupQuestion(source=${source}) - "${message}" choices=[${choices.join(", ")}] (defaulting to ${defaultId ?? 0})`); + return defaultId ?? 0; + }, + notify(event: string, requestId: RequestId, data: any, source: string): void { + logger.log(`ClientIO: notify(event=${event}, requestId=${requestId}, source=${source}) - ${JSON.stringify(data)}`); + }, + openLocalView(port: number): void { + logger.log(`ClientIO: openLocalView(port=${port})`); + }, + closeLocalView(port: number): void { + logger.log(`ClientIO: closeLocalView(port=${port})`); + }, + takeAction(action: string, data: unknown): void { + logger.log(`ClientIO: takeAction(action=${action}) - ${JSON.stringify(data)}`); + }, + }; +} + +export class CommandServer { + public server: McpServer; + private dispatcher: Dispatcher | null = null; + private agentServerUrl: string; + private reconnectInterval: NodeJS.Timeout | null = null; + private isConnecting: boolean = false; + private reconnectDelayMs: number = 5000; // 5 seconds between reconnection attempts + private logger: Logger; + private responseCollector: { messages: string[] } = { messages: [] }; + + /** + * Creates a new CommandServer instance + * @param debugMode Enable debug mode for diagnostic tools + * @param agentServerUrl URL of the TypeAgent dispatcher server (default: ws://localhost:8999) + */ + constructor(debugMode: boolean = true, agentServerUrl?: string) { + this.logger = new Logger(); + this.server = new McpServer({ + name: "Command-Executor-Server", + version: "1.0.0", + }); + this.agentServerUrl = agentServerUrl ?? process.env.AGENT_SERVER_URL ?? "ws://localhost:8999"; + this.logger.log(`CommandServer initializing with TypeAgent server URL: ${this.agentServerUrl}`); + this.addTools(); + if (debugMode) { + this.addDiagnosticTools(); + } + } + + public async start(transport?: StdioServerTransport): Promise { + transport ??= new StdioServerTransport(); + await this.server.connect(transport); + + // Connect to the TypeAgent dispatcher + await this.connectToDispatcher(); + + // Start reconnection monitoring + this.startReconnectionMonitoring(); + } + + private async connectToDispatcher(): Promise { + if (this.isConnecting) { + return; + } + + this.isConnecting = true; + try { + const clientIO = createMcpClientIO(this.logger, this.responseCollector); + this.dispatcher = await connectDispatcher(clientIO, this.agentServerUrl); + this.logger.log(`Connected to TypeAgent dispatcher at ${this.agentServerUrl}`); + } catch (error) { + this.logger.error(`Failed to connect to dispatcher at ${this.agentServerUrl}`, error); + this.logger.error("Will retry connection automatically. Make sure the TypeAgent server is running."); + this.dispatcher = null; + } finally { + this.isConnecting = false; + } + } + + private startReconnectionMonitoring(): void { + // Check connection status periodically and reconnect if needed + this.reconnectInterval = setInterval(async () => { + if (!this.dispatcher && !this.isConnecting) { + this.logger.log("Attempting to reconnect to TypeAgent dispatcher..."); + await this.connectToDispatcher(); + } + }, this.reconnectDelayMs); + } + + private stopReconnectionMonitoring(): void { + if (this.reconnectInterval) { + clearInterval(this.reconnectInterval); + this.reconnectInterval = null; + } + } + + public async close(): Promise { + this.stopReconnectionMonitoring(); + if (this.dispatcher) { + await this.dispatcher.close(); + this.dispatcher = null; + } + this.logger.close(); + } + + private addTools() { + this.server.registerTool( + "execute_command", + { + inputSchema: executeCommandRequestSchema(), + description: + "Execute a user command such as playing music, managing lists, or working with calendars", + }, + async (request: ExecuteCommandRequest) => + this.executeCommand(request), + ); + } + + public async executeCommand( + request: ExecuteCommandRequest, + ): Promise { + this.logger.log(`User request: ${request.request}`); + + // If not connected, try to connect now (lazy connection) + if (!this.dispatcher && !this.isConnecting) { + this.logger.log("Not connected to dispatcher, attempting to connect..."); + await this.connectToDispatcher(); + } + + if (!this.dispatcher) { + const errorMsg = `Cannot execute command: not connected to TypeAgent dispatcher at ${this.agentServerUrl}. Make sure the TypeAgent server is running with: pnpm run start:agent-server`; + this.logger.error(errorMsg); + return toolResult(errorMsg); + } + + try { + // Clear response collector before processing new command + this.responseCollector.messages = []; + + // Process the command through the TypeAgent dispatcher + this.logger.log(`Sending command to dispatcher: ${request.request}`); + const result = await this.dispatcher.processCommand(request.request); + + if (result?.lastError) { + this.logger.error(`Command execution error: ${result.lastError}`); + return toolResult(`Error executing command: ${result.lastError}`); + } + + // Return the collected messages from the dispatcher + this.logger.log(`Successfully executed command: ${request.request}`); + + if (this.responseCollector.messages.length > 0) { + const response = this.responseCollector.messages.join('\n\n'); + // Process any HTML images in the response + const processedResponse = await processHtmlImages(response); + return toolResult(processedResponse); + } + + // Fallback if no messages were collected + return toolResult(`Successfully executed: ${request.request}`); + } catch (error) { + const errorMsg = `Failed to execute command: ${error instanceof Error ? error.message : String(error)}`; + this.logger.error(errorMsg); + + // Mark dispatcher as disconnected so we'll try to reconnect + this.dispatcher = null; + + return toolResult(errorMsg); + } + } + + private addDiagnosticTools() { + this.server.registerTool( + "ping", + { + inputSchema: { message: z.string() }, + description: "Ping the server to test connectivity", + }, + async (request: { message: string }) => { + const response = request.message + ? "PONG: " + request.message + : "pong"; + return toolResult(response); + }, + ); + } +} diff --git a/ts/packages/commandExecutor/src/index.ts b/ts/packages/commandExecutor/src/index.ts new file mode 100644 index 000000000..4ead999f7 --- /dev/null +++ b/ts/packages/commandExecutor/src/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { CommandServer, ExecuteCommandRequest } from "./commandServer.js"; diff --git a/ts/packages/commandExecutor/src/server.ts b/ts/packages/commandExecutor/src/server.ts new file mode 100644 index 000000000..b8584d0f4 --- /dev/null +++ b/ts/packages/commandExecutor/src/server.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { CommandServer } from "./commandServer.js"; +import dotenv from "dotenv"; + +const envPath = new URL("../../../.env", import.meta.url); +dotenv.config({ path: envPath }); + +console.log("Starting Command Executor Server"); + +const commandServer = new CommandServer(); +await commandServer.start(); + +console.log("Exit Command Executor Server"); diff --git a/ts/packages/commandExecutor/src/tsconfig.json b/ts/packages/commandExecutor/src/tsconfig.json new file mode 100644 index 000000000..6e2a4fdd3 --- /dev/null +++ b/ts/packages/commandExecutor/src/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist" + } +} diff --git a/ts/packages/commandExecutor/test/testClient.ts b/ts/packages/commandExecutor/test/testClient.ts new file mode 100644 index 000000000..26446c416 --- /dev/null +++ b/ts/packages/commandExecutor/test/testClient.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +async function testCommandServer() { + console.log("Creating MCP client..."); + + const client = new Client({ + name: "Test-Client", + version: "1.0.0", + }); + + const transport = new StdioClientTransport({ + command: "node", + args: ["../dist/server.js"], + }); + + await client.connect(transport); + console.log("Connected to Command Executor Server\n"); + + // Test 1: Execute a music command + console.log("Test 1: Play music command"); + const musicResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "play shake it off by taylor swift" }, + })) as CallToolResult; + console.log( + "Response:", + musicResult.content[0].type === "text" + ? musicResult.content[0].text + : "No text response", + ); + console.log(); + + // Test 2: Execute a list command + console.log("Test 2: Add to list command"); + const listResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "add ham to my grocery list" }, + })) as CallToolResult; + console.log( + "Response:", + listResult.content[0].type === "text" + ? listResult.content[0].text + : "No text response", + ); + console.log(); + + // Test 3: Execute a calendar command + console.log("Test 3: Calendar command"); + const calendarResult = (await client.callTool({ + name: "execute_command", + arguments: { request: "add meeting tomorrow at 3pm" }, + })) as CallToolResult; + console.log( + "Response:", + calendarResult.content[0].type === "text" + ? calendarResult.content[0].text + : "No text response", + ); + console.log(); + + // Test 4: Ping diagnostic tool + console.log("Test 4: Ping diagnostic"); + const pingResult = (await client.callTool({ + name: "ping", + arguments: { message: "test connection" }, + })) as CallToolResult; + console.log( + "Response:", + pingResult.content[0].type === "text" + ? pingResult.content[0].text + : "No text response", + ); + console.log(); + + await client.close(); + console.log("All tests completed successfully!"); +} + +testCommandServer().catch(console.error); diff --git a/ts/packages/commandExecutor/test/tsconfig.json b/ts/packages/commandExecutor/test/tsconfig.json new file mode 100644 index 000000000..2881fdbb9 --- /dev/null +++ b/ts/packages/commandExecutor/test/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist-test" + } +} diff --git a/ts/packages/commandExecutor/tsconfig.json b/ts/packages/commandExecutor/tsconfig.json new file mode 100644 index 000000000..b6e1577e4 --- /dev/null +++ b/ts/packages/commandExecutor/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true + }, + "include": [], + "references": [{ "path": "./src" }], + "ts-node": { + "esm": true + } +} diff --git a/ts/packages/commandExecutor/view-logs.sh b/ts/packages/commandExecutor/view-logs.sh new file mode 100644 index 000000000..5c1e76a0e --- /dev/null +++ b/ts/packages/commandExecutor/view-logs.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Helper script to view MCP server logs + +LOG_DIR="/tmp/typeagent-mcp" + +if [ ! -d "$LOG_DIR" ]; then + echo "No logs found yet. The log directory will be created when the MCP server starts." + echo "Expected location: $LOG_DIR" + exit 0 +fi + +# Find the most recent log file +LATEST_LOG=$(ls -t "$LOG_DIR"/mcp-server-*.log 2>/dev/null | head -1) + +if [ -z "$LATEST_LOG" ]; then + echo "No log files found in $LOG_DIR" + exit 0 +fi + +echo "Viewing log file: $LATEST_LOG" +echo "==================================================================================" +echo "" + +# Check if we should tail (follow) the log +if [ "$1" == "-f" ] || [ "$1" == "--follow" ]; then + tail -f "$LATEST_LOG" +else + cat "$LATEST_LOG" +fi diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 5e4f62a49..66fdcac77 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -2813,6 +2813,40 @@ importers: specifier: ^6.0.1 version: 6.0.1 + packages/commandExecutor: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.25.2 + version: 1.25.2(hono@4.11.3)(zod@4.1.13) + '@typeagent/agent-sdk': + specifier: workspace:* + version: link:../agentSdk + '@typeagent/agent-server-client': + specifier: workspace:* + version: link:../agentServer/client + '@typeagent/dispatcher-types': + specifier: workspace:* + version: link:../dispatcher/types + dotenv: + specifier: ^16.3.1 + version: 16.5.0 + isomorphic-ws: + specifier: ^5.0.0 + version: 5.0.0(ws@8.18.2) + zod: + specifier: ^4.1.13 + version: 4.1.13 + devDependencies: + prettier: + specifier: ^3.2.5 + version: 3.5.3 + rimraf: + specifier: ^5.0.5 + version: 5.0.10 + typescript: + specifier: ~5.4.5 + version: 5.4.5 + packages/defaultAgentProvider: dependencies: '@modelcontextprotocol/sdk': From bf20673511a2149b33274bf768986b2a961c24c8 Mon Sep 17 00:00:00 2001 From: steveluc Date: Fri, 16 Jan 2026 23:22:43 -0800 Subject: [PATCH 2/7] Add copyright header to view-logs.sh Co-Authored-By: Claude Sonnet 4.5 --- ts/packages/commandExecutor/view-logs.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/packages/commandExecutor/view-logs.sh b/ts/packages/commandExecutor/view-logs.sh index 5c1e76a0e..775d9b7a0 100644 --- a/ts/packages/commandExecutor/view-logs.sh +++ b/ts/packages/commandExecutor/view-logs.sh @@ -1,4 +1,7 @@ #!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + # Helper script to view MCP server logs LOG_DIR="/tmp/typeagent-mcp" From 7c2a81a579f09e165ce6a697a40c3025245aad7d Mon Sep 17 00:00:00 2001 From: steveluc Date: Fri, 16 Jan 2026 23:29:37 -0800 Subject: [PATCH 3/7] Fix repo policy issues - Add trademark section to README - Remove dist-test build outputs from git - Fix package.json field ordering (use exports instead of main) Co-Authored-By: Claude Sonnet 4.5 --- ts/packages/commandExecutor/README.md | 4 ++ .../commandExecutor/dist-test/testClient.d.ts | 2 - .../commandExecutor/dist-test/testClient.js | 61 ------------------- ts/packages/commandExecutor/package.json | 6 +- 4 files changed, 8 insertions(+), 65 deletions(-) delete mode 100644 ts/packages/commandExecutor/dist-test/testClient.d.ts delete mode 100644 ts/packages/commandExecutor/dist-test/testClient.js diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md index 9e33477b1..b7ff2173e 100644 --- a/ts/packages/commandExecutor/README.md +++ b/ts/packages/commandExecutor/README.md @@ -177,3 +177,7 @@ pnpm run start ### Testing Use the MCP client (like Claude Code) to test commands, or use the TypeAgent CLI to verify the dispatcher is working. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/ts/packages/commandExecutor/dist-test/testClient.d.ts b/ts/packages/commandExecutor/dist-test/testClient.d.ts deleted file mode 100644 index 9e721977d..000000000 --- a/ts/packages/commandExecutor/dist-test/testClient.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=testClient.d.ts.map \ No newline at end of file diff --git a/ts/packages/commandExecutor/dist-test/testClient.js b/ts/packages/commandExecutor/dist-test/testClient.js deleted file mode 100644 index b3587ca7a..000000000 --- a/ts/packages/commandExecutor/dist-test/testClient.js +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -async function testCommandServer() { - console.log("Creating MCP client..."); - const client = new Client({ - name: "Test-Client", - version: "1.0.0", - }); - const transport = new StdioClientTransport({ - command: "node", - args: ["../dist/server.js"], - }); - await client.connect(transport); - console.log("Connected to Command Executor Server\n"); - // Test 1: Execute a music command - console.log("Test 1: Play music command"); - const musicResult = (await client.callTool({ - name: "execute_command", - arguments: { request: "play shake it off by taylor swift" }, - })); - console.log("Response:", musicResult.content[0].type === "text" - ? musicResult.content[0].text - : "No text response"); - console.log(); - // Test 2: Execute a list command - console.log("Test 2: Add to list command"); - const listResult = (await client.callTool({ - name: "execute_command", - arguments: { request: "add ham to my grocery list" }, - })); - console.log("Response:", listResult.content[0].type === "text" - ? listResult.content[0].text - : "No text response"); - console.log(); - // Test 3: Execute a calendar command - console.log("Test 3: Calendar command"); - const calendarResult = (await client.callTool({ - name: "execute_command", - arguments: { request: "add meeting tomorrow at 3pm" }, - })); - console.log("Response:", calendarResult.content[0].type === "text" - ? calendarResult.content[0].text - : "No text response"); - console.log(); - // Test 4: Ping diagnostic tool - console.log("Test 4: Ping diagnostic"); - const pingResult = (await client.callTool({ - name: "ping", - arguments: { message: "test connection" }, - })); - console.log("Response:", pingResult.content[0].type === "text" - ? pingResult.content[0].text - : "No text response"); - console.log(); - await client.close(); - console.log("All tests completed successfully!"); -} -testCommandServer().catch(console.error); -//# sourceMappingURL=testClient.js.map \ No newline at end of file diff --git a/ts/packages/commandExecutor/package.json b/ts/packages/commandExecutor/package.json index 71a04e9cc..25eb2f9e7 100644 --- a/ts/packages/commandExecutor/package.json +++ b/ts/packages/commandExecutor/package.json @@ -12,8 +12,10 @@ "license": "MIT", "author": "Microsoft", "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "types": "./dist/index.d.ts", "scripts": { "build": "npm run tsc", "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", From 82a19109894364a6ed998925ab7b2c5e9f2b32ad Mon Sep 17 00:00:00 2001 From: steveluc Date: Fri, 16 Jan 2026 23:30:52 -0800 Subject: [PATCH 4/7] Run prettier to format files Co-Authored-By: Claude Sonnet 4.5 --- ts/packages/commandExecutor/README.md | 26 ++- .../commandExecutor/examples/example_usage.md | 6 + .../commandExecutor/src/commandServer.ts | 150 +++++++++++++----- 3 files changed, 137 insertions(+), 45 deletions(-) diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md index b7ff2173e..be504ede3 100644 --- a/ts/packages/commandExecutor/README.md +++ b/ts/packages/commandExecutor/README.md @@ -9,6 +9,7 @@ This MCP server acts as a bridge between Claude Code (or other MCP clients) and ## Prerequisites 1. **Built Package**: Build this package before using: + ```bash pnpm run build ``` @@ -16,6 +17,7 @@ This MCP server acts as a bridge between Claude Code (or other MCP clients) and 2. **TypeAgent Server** (optional at startup): The TypeAgent dispatcher server at `ws://localhost:8999`. The MCP server will automatically connect when the TypeAgent server becomes available and reconnect if the connection is lost. Start the TypeAgent server with: + ```bash pnpm run start:agent-server ``` @@ -33,20 +35,20 @@ You can set this in the `.env` file at the root of the TypeAgent repository. ### For Claude Code Users 1. **Build the package** from the TypeAgent repository root: + ```bash cd ts pnpm run build ``` 2. **Configure Claude Code** to use the MCP server. Add the following to your `.mcp.json` file in the TypeAgent repository root (create it if it doesn't exist): + ```json { "mcpServers": { "command-executor": { "command": "node", - "args": [ - "packages/commandExecutor/dist/server.js" - ] + "args": ["packages/commandExecutor/dist/server.js"] } } } @@ -55,6 +57,7 @@ You can set this in the `.env` file at the root of the TypeAgent repository. 3. **Restart Claude Code** to load the MCP server configuration. 4. **Start the TypeAgent server** (can be done before or after starting Claude Code): + ```bash pnpm run start:agent-server ``` @@ -73,9 +76,7 @@ The server is configured in `.mcp.json`: "mcpServers": { "command-executor": { "command": "node", - "args": [ - "packages/commandExecutor/dist/server.js" - ] + "args": ["packages/commandExecutor/dist/server.js"] } } } @@ -84,20 +85,25 @@ The server is configured in `.mcp.json`: ### Available Tools #### execute_command + Execute user commands such as playing music, managing lists, or working with calendars. **Parameters:** + - `request` (string): The natural language command to execute **Examples:** + - "play sweet emotion by aerosmith" - "add jelly beans to my grocery list" - "schedule a meeting for tomorrow at 2pm" #### ping (debug mode) + Test server connectivity. **Parameters:** + - `message` (string): Message to echo back ## Architecture @@ -113,6 +119,7 @@ TypeAgent Agents (Music, Lists, Calendar, etc.) ``` The MCP server: + 1. Receives commands from the MCP client 2. Connects to the TypeAgent dispatcher via WebSocket 3. Forwards commands to the dispatcher's `processCommand` method @@ -128,6 +135,7 @@ The MCP server includes automatic reconnection capabilities: - **Error Recovery**: If a command fails due to connection loss, the dispatcher is marked as disconnected and will automatically reconnect **Recommended workflow:** + 1. Start Claude Code (the MCP server starts automatically) 2. Start the TypeAgent server: `pnpm run start:agent-server` 3. Send commands - the MCP server will connect automatically @@ -139,9 +147,11 @@ You can also start the TypeAgent server first, or restart it at any time without The MCP server automatically logs all activity to both console and a log file for debugging. ### Log File Location + Logs are written to: `/tmp/typeagent-mcp/mcp-server-.log` ### Viewing Logs + Use the provided helper script to view the most recent log file: ```bash @@ -153,6 +163,7 @@ Use the provided helper script to view the most recent log file: ``` ### What Gets Logged + - Server initialization and configuration - Connection attempts to TypeAgent dispatcher - Connection success/failure with error details @@ -166,16 +177,19 @@ This is particularly useful for debugging connection issues between the MCP serv ## Development ### Building + ```bash pnpm run build ``` ### Running Standalone + ```bash pnpm run start ``` ### Testing + Use the MCP client (like Claude Code) to test commands, or use the TypeAgent CLI to verify the dispatcher is working. ## Trademarks diff --git a/ts/packages/commandExecutor/examples/example_usage.md b/ts/packages/commandExecutor/examples/example_usage.md index 395028622..a94f4b2bc 100644 --- a/ts/packages/commandExecutor/examples/example_usage.md +++ b/ts/packages/commandExecutor/examples/example_usage.md @@ -5,6 +5,7 @@ To use this MCP server with Claude Desktop, add the following to your Claude Desktop configuration: ### Windows + Edit `%APPDATA%\Claude\claude_desktop_config.json`: ```json @@ -21,6 +22,7 @@ Edit `%APPDATA%\Claude\claude_desktop_config.json`: ``` ### macOS + Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json @@ -41,18 +43,21 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: Once configured, you can ask Claude to execute various commands: ### Music Commands + - "Play shake it off by taylor swift" - "Skip to the next song" - "Pause the music" - "Set volume to 50%" ### List Management + - "Add ham to my grocery list" - "Add milk, eggs, and bread to shopping list" - "Remove bananas from grocery list" - "Show me my grocery list" ### Calendar Operations + - "Add meeting tomorrow at 3pm" - "Schedule dentist appointment for next Tuesday at 10am" - "What's on my calendar today?" @@ -69,6 +74,7 @@ The server will log all incoming requests to the console. You can verify it's wo ## Current Behavior The MCP server currently: + - Accepts command requests via the `execute_command` tool - Logs the request to the console - Returns a success message to Claude diff --git a/ts/packages/commandExecutor/src/commandServer.ts b/ts/packages/commandExecutor/src/commandServer.ts index 93dfc123b..4e0dca2c4 100644 --- a/ts/packages/commandExecutor/src/commandServer.ts +++ b/ts/packages/commandExecutor/src/commandServer.ts @@ -25,9 +25,7 @@ function executeCommandRequestSchema() { } const ExecuteCommandRequestSchema = z.object(executeCommandRequestSchema()); -export type ExecuteCommandRequest = z.infer< - typeof ExecuteCommandRequestSchema ->; +export type ExecuteCommandRequest = z.infer; function toolResult(result: string): CallToolResult { return { @@ -64,7 +62,9 @@ class Logger { } error(message: string, error?: any): void { - const errorDetails = error ? ` - ${error instanceof Error ? error.message : String(error)}` : ""; + const errorDetails = error + ? ` - ${error instanceof Error ? error.message : String(error)}` + : ""; const formatted = this.formatMessage("ERROR", message + errorDetails); console.error(formatted); this.logStream.write(formatted + "\n"); @@ -105,7 +105,7 @@ async function processHtmlImages(content: string): Promise { const fullTag = match[0]; // Just remove the img tag entirely - don't download or display artwork - processed = processed.replace(fullTag, ''); + processed = processed.replace(fullTag, ""); } return processed; @@ -115,7 +115,10 @@ async function processHtmlImages(content: string): Promise { * Minimal ClientIO implementation for MCP server * Most methods are no-ops since we just need to satisfy the interface */ -function createMcpClientIO(logger: Logger, responseCollector: { messages: string[] }): ClientIO { +function createMcpClientIO( + logger: Logger, + responseCollector: { messages: string[] }, +): ClientIO { return { clear(): void { logger.log("ClientIO: clear() called"); @@ -126,39 +129,61 @@ function createMcpClientIO(logger: Logger, responseCollector: { messages: string setDisplayInfo(): void {}, setDisplay(message: IAgentMessage): void { logger.log(`ClientIO: setDisplay() - ${JSON.stringify(message)}`); - if (typeof message === 'object' && 'message' in message) { + if (typeof message === "object" && "message" in message) { const msg = message.message; // Filter out "info" kind messages (technical translation details) - if (typeof msg === 'object' && msg && 'kind' in msg && msg.kind === 'info') { + if ( + typeof msg === "object" && + msg && + "kind" in msg && + msg.kind === "info" + ) { return; } - if (typeof msg === 'string') { + if (typeof msg === "string") { responseCollector.messages.push(stripAnsi(msg)); - } else if (typeof msg === 'object' && msg && 'content' in msg) { - responseCollector.messages.push(stripAnsi(String(msg.content))); + } else if (typeof msg === "object" && msg && "content" in msg) { + responseCollector.messages.push( + stripAnsi(String(msg.content)), + ); } } }, appendDisplay(message: IAgentMessage, mode: DisplayAppendMode): void { - logger.log(`ClientIO: appendDisplay(mode=${mode}) - ${JSON.stringify(message)}`); + logger.log( + `ClientIO: appendDisplay(mode=${mode}) - ${JSON.stringify(message)}`, + ); // Only capture block mode messages (final results), not temporary status messages - if (mode === 'block' && typeof message === 'object' && 'message' in message) { + if ( + mode === "block" && + typeof message === "object" && + "message" in message + ) { const msg = message.message; // Filter out "info" kind messages (technical translation details) - if (typeof msg === 'object' && msg && 'kind' in msg && msg.kind === 'info') { + if ( + typeof msg === "object" && + msg && + "kind" in msg && + msg.kind === "info" + ) { return; } - if (typeof msg === 'string') { + if (typeof msg === "string") { responseCollector.messages.push(stripAnsi(msg)); - } else if (typeof msg === 'object' && msg && 'content' in msg) { - responseCollector.messages.push(stripAnsi(String(msg.content))); + } else if (typeof msg === "object" && msg && "content" in msg) { + responseCollector.messages.push( + stripAnsi(String(msg.content)), + ); } } }, appendDiagnosticData(requestId: RequestId, data: any): void { - logger.log(`ClientIO: appendDiagnosticData(requestId=${requestId}) - ${JSON.stringify(data)}`); + logger.log( + `ClientIO: appendDiagnosticData(requestId=${requestId}) - ${JSON.stringify(data)}`, + ); }, setDynamicDisplay(): void {}, async askYesNo( @@ -166,7 +191,9 @@ function createMcpClientIO(logger: Logger, responseCollector: { messages: string requestId: RequestId, defaultValue?: boolean, ): Promise { - logger.log(`ClientIO: askYesNo(requestId=${requestId}) - "${message}" (defaulting to ${defaultValue ?? false})`); + logger.log( + `ClientIO: askYesNo(requestId=${requestId}) - "${message}" (defaulting to ${defaultValue ?? false})`, + ); return defaultValue ?? false; }, async proposeAction( @@ -174,7 +201,9 @@ function createMcpClientIO(logger: Logger, responseCollector: { messages: string requestId: RequestId, source: string, ): Promise { - logger.log(`ClientIO: proposeAction(requestId=${requestId}, source=${source}) - ${JSON.stringify(actionTemplates)}`); + logger.log( + `ClientIO: proposeAction(requestId=${requestId}, source=${source}) - ${JSON.stringify(actionTemplates)}`, + ); return undefined; }, async popupQuestion( @@ -183,11 +212,20 @@ function createMcpClientIO(logger: Logger, responseCollector: { messages: string defaultId: number | undefined, source: string, ): Promise { - logger.log(`ClientIO: popupQuestion(source=${source}) - "${message}" choices=[${choices.join(", ")}] (defaulting to ${defaultId ?? 0})`); + logger.log( + `ClientIO: popupQuestion(source=${source}) - "${message}" choices=[${choices.join(", ")}] (defaulting to ${defaultId ?? 0})`, + ); return defaultId ?? 0; }, - notify(event: string, requestId: RequestId, data: any, source: string): void { - logger.log(`ClientIO: notify(event=${event}, requestId=${requestId}, source=${source}) - ${JSON.stringify(data)}`); + notify( + event: string, + requestId: RequestId, + data: any, + source: string, + ): void { + logger.log( + `ClientIO: notify(event=${event}, requestId=${requestId}, source=${source}) - ${JSON.stringify(data)}`, + ); }, openLocalView(port: number): void { logger.log(`ClientIO: openLocalView(port=${port})`); @@ -196,7 +234,9 @@ function createMcpClientIO(logger: Logger, responseCollector: { messages: string logger.log(`ClientIO: closeLocalView(port=${port})`); }, takeAction(action: string, data: unknown): void { - logger.log(`ClientIO: takeAction(action=${action}) - ${JSON.stringify(data)}`); + logger.log( + `ClientIO: takeAction(action=${action}) - ${JSON.stringify(data)}`, + ); }, }; } @@ -222,8 +262,13 @@ export class CommandServer { name: "Command-Executor-Server", version: "1.0.0", }); - this.agentServerUrl = agentServerUrl ?? process.env.AGENT_SERVER_URL ?? "ws://localhost:8999"; - this.logger.log(`CommandServer initializing with TypeAgent server URL: ${this.agentServerUrl}`); + this.agentServerUrl = + agentServerUrl ?? + process.env.AGENT_SERVER_URL ?? + "ws://localhost:8999"; + this.logger.log( + `CommandServer initializing with TypeAgent server URL: ${this.agentServerUrl}`, + ); this.addTools(); if (debugMode) { this.addDiagnosticTools(); @@ -248,12 +293,25 @@ export class CommandServer { this.isConnecting = true; try { - const clientIO = createMcpClientIO(this.logger, this.responseCollector); - this.dispatcher = await connectDispatcher(clientIO, this.agentServerUrl); - this.logger.log(`Connected to TypeAgent dispatcher at ${this.agentServerUrl}`); + const clientIO = createMcpClientIO( + this.logger, + this.responseCollector, + ); + this.dispatcher = await connectDispatcher( + clientIO, + this.agentServerUrl, + ); + this.logger.log( + `Connected to TypeAgent dispatcher at ${this.agentServerUrl}`, + ); } catch (error) { - this.logger.error(`Failed to connect to dispatcher at ${this.agentServerUrl}`, error); - this.logger.error("Will retry connection automatically. Make sure the TypeAgent server is running."); + this.logger.error( + `Failed to connect to dispatcher at ${this.agentServerUrl}`, + error, + ); + this.logger.error( + "Will retry connection automatically. Make sure the TypeAgent server is running.", + ); this.dispatcher = null; } finally { this.isConnecting = false; @@ -264,7 +322,9 @@ export class CommandServer { // Check connection status periodically and reconnect if needed this.reconnectInterval = setInterval(async () => { if (!this.dispatcher && !this.isConnecting) { - this.logger.log("Attempting to reconnect to TypeAgent dispatcher..."); + this.logger.log( + "Attempting to reconnect to TypeAgent dispatcher...", + ); await this.connectToDispatcher(); } }, this.reconnectDelayMs); @@ -306,7 +366,9 @@ export class CommandServer { // If not connected, try to connect now (lazy connection) if (!this.dispatcher && !this.isConnecting) { - this.logger.log("Not connected to dispatcher, attempting to connect..."); + this.logger.log( + "Not connected to dispatcher, attempting to connect...", + ); await this.connectToDispatcher(); } @@ -321,19 +383,29 @@ export class CommandServer { this.responseCollector.messages = []; // Process the command through the TypeAgent dispatcher - this.logger.log(`Sending command to dispatcher: ${request.request}`); - const result = await this.dispatcher.processCommand(request.request); + this.logger.log( + `Sending command to dispatcher: ${request.request}`, + ); + const result = await this.dispatcher.processCommand( + request.request, + ); if (result?.lastError) { - this.logger.error(`Command execution error: ${result.lastError}`); - return toolResult(`Error executing command: ${result.lastError}`); + this.logger.error( + `Command execution error: ${result.lastError}`, + ); + return toolResult( + `Error executing command: ${result.lastError}`, + ); } // Return the collected messages from the dispatcher - this.logger.log(`Successfully executed command: ${request.request}`); + this.logger.log( + `Successfully executed command: ${request.request}`, + ); if (this.responseCollector.messages.length > 0) { - const response = this.responseCollector.messages.join('\n\n'); + const response = this.responseCollector.messages.join("\n\n"); // Process any HTML images in the response const processedResponse = await processHtmlImages(response); return toolResult(processedResponse); From ea66752a7ec5d899a81309bd9cfcfadf757a507d Mon Sep 17 00:00:00 2001 From: steveluc Date: Fri, 16 Jan 2026 23:33:40 -0800 Subject: [PATCH 5/7] Fix repo policy issues - trademark format and package.json script order - Use exact trademark text format with proper line breaks - Sort package.json scripts alphabetically Co-Authored-By: Claude Sonnet 4.5 --- ts/packages/commandExecutor/README.md | 6 +++++- ts/packages/commandExecutor/package.json | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md index be504ede3..39adee334 100644 --- a/ts/packages/commandExecutor/README.md +++ b/ts/packages/commandExecutor/README.md @@ -194,4 +194,8 @@ Use the MCP client (like Claude Code) to test commands, or use the TypeAgent CLI ## Trademarks -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/ts/packages/commandExecutor/package.json b/ts/packages/commandExecutor/package.json index 25eb2f9e7..9778f8854 100644 --- a/ts/packages/commandExecutor/package.json +++ b/ts/packages/commandExecutor/package.json @@ -21,8 +21,8 @@ "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", "prettier": "prettier --check . --ignore-path ../../.prettierignore", "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", - "tsc": "tsc -b", - "start": "node dist/server.js" + "start": "node dist/server.js", + "tsc": "tsc -b" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", From e4672de7382895676781c1cbaa90331819908a5d Mon Sep 17 00:00:00 2001 From: steveluc Date: Sun, 18 Jan 2026 21:24:52 -0800 Subject: [PATCH 6/7] Enhance VSCode split editor functionality This commit enhances the split editor command in the Coda extension to support more flexible editor selection and improves TypeAgent schema disambiguation. **Bug Fixes:** - Fix off-by-one error when selecting "first" or "last" editor by sorting by viewColumn - Fix multiple split issue by adding conditionals to focus changes - Remove unnecessary focus restoration logic for voice command scenarios **Features:** - Add support for splitting editors by position: "first", "last", "active", or numeric index - Add support for splitting editors by file name: "split app.tsx to the right" - Search all open tabs using tabGroups API, not just visible editors - Automatically open and focus files found in background tabs before splitting **Schema Improvements:** - Add clear disambiguation between splitEditor and moveCursorInFile actions - Add "USE THIS for" and "DO NOT USE for" guidance in schema comments - Add concrete examples to help LLM choose correct action - Remove moveCursorInFile reference from main code schema (not useful for voice) **Documentation:** - Add VSCODE_CAPABILITIES.md documenting all VSCode automation features - Update split editor examples to show new position and file-based splitting Co-Authored-By: Claude Sonnet 4.5 --- .../agents/code/src/codeActionsSchema.ts | 20 +- .../src/vscode/editorCodeActionsSchema.ts | 13 +- ts/packages/coda/src/handleVSCodeActions.ts | 166 ++++++++- ts/packages/commandExecutor/README.md | 24 +- .../commandExecutor/VSCODE_CAPABILITIES.md | 318 ++++++++++++++++++ .../commandExecutor/src/commandServer.ts | 6 +- 6 files changed, 531 insertions(+), 16 deletions(-) create mode 100644 ts/packages/commandExecutor/VSCODE_CAPABILITIES.md diff --git a/ts/packages/agents/code/src/codeActionsSchema.ts b/ts/packages/agents/code/src/codeActionsSchema.ts index 176655ff2..b38f25ce4 100644 --- a/ts/packages/agents/code/src/codeActionsSchema.ts +++ b/ts/packages/agents/code/src/codeActionsSchema.ts @@ -41,13 +41,29 @@ export type ChangeColorThemeAction = { }; export type SplitDirection = "right" | "left" | "up" | "down"; +export type EditorPosition = "first" | "last" | "active"; -// Split to update the current editor window into a new editor pane to the left, right, above, or below +// ACTION: Split an editor window into multiple panes showing the same file or different files side-by-side. +// This creates a new editor pane (split view) for working with multiple files simultaneously. +// USE THIS for: "split editor", "split the editor with X", "duplicate this editor to the right", "split X" +// +// Examples: +// - "split editor to the right" → splits active editor +// - "split the first editor" → splits leftmost editor +// - "split app.tsx to the left" → finds editor showing app.tsx and splits it +// - "split the last editor down" → splits rightmost editor downward +// - "split the editor with utils.ts" → finds editor showing utils.ts and splits it export type SplitEditorAction = { actionName: "splitEditor"; parameters: { - // e.g., "right", "left", "up", "down", only if specified by the user + // Direction to split: "right", "left", "up", "down". Only include if user specifies direction. direction?: SplitDirection; + // Which editor to split by position. Use "first" for leftmost editor, "last" for rightmost, "active" for current editor, or a number (0-based index). + editorPosition?: EditorPosition | number; + // Which editor to split by file name. Extract the file name or pattern from user request. + // Examples: "app.tsx", "main.py", "utils", "codeActionHandler" + // Use this when user says "split X" or "split the editor with X" where X is a file name. + fileName?: string; }; }; diff --git a/ts/packages/agents/code/src/vscode/editorCodeActionsSchema.ts b/ts/packages/agents/code/src/vscode/editorCodeActionsSchema.ts index 28e1c55fa..38126e23a 100644 --- a/ts/packages/agents/code/src/vscode/editorCodeActionsSchema.ts +++ b/ts/packages/agents/code/src/vscode/editorCodeActionsSchema.ts @@ -160,11 +160,20 @@ export type EditorActionFixProblem = { }; }; -// Action to move the cursor in a file to a specified position. +// ACTION: Move the cursor to a specific position within a file (for navigation or editing preparation). +// This moves the cursor position, NOT split/duplicate the editor view. +// USE THIS for: "go to file X", "jump to line 50", "go to function foo", "move cursor to X" +// DO NOT USE for: "split editor", "split X", "duplicate editor" (use splitEditor action instead) +// +// Examples: +// - "go to line 50" → { target: { type: "onLine", line: 50 } } +// - "jump to function main" → { target: { type: "insideFunction", name: "main" } } +// - "go to app.tsx" → { target: { type: "inFile", filePath: "app.tsx" } } +// - "move cursor to the end of file" → { target: { type: "atEndOfFile" } } export type EditorActionMoveCursor = { actionName: "moveCursorInFile"; parameters: { - //Target position for the cursor. Supports symbolic locations, line-based positions, or file-relative positions. + // Target position for the cursor. Supports symbolic locations, line-based positions, or file-relative positions. target: CursorTarget; // Optional file where the cursor should be moved. Defaults to the active editor if not provided. file?: FileTarget; diff --git a/ts/packages/coda/src/handleVSCodeActions.ts b/ts/packages/coda/src/handleVSCodeActions.ts index 564063351..f8a6217f4 100644 --- a/ts/packages/coda/src/handleVSCodeActions.ts +++ b/ts/packages/coda/src/handleVSCodeActions.ts @@ -298,38 +298,186 @@ export async function handleBaseEditorActions( } case "splitEditor": { - if (actionData && actionData.direction) { - switch (actionData.direction) { + console.log( + `[splitEditor] Starting with actionData:`, + JSON.stringify(actionData), + ); + // Find the target editor to split + let targetEditor: vscode.TextEditor | undefined; + const editorPosition = actionData?.editorPosition; + const fileName = actionData?.fileName; + console.log( + `[splitEditor] editorPosition=${editorPosition}, fileName=${fileName}`, + ); + + if (fileName || editorPosition !== undefined) { + // Find target editor by fileName or editorPosition + // Use visibleTextEditors to get all currently visible editors + const allEditors = vscode.window.visibleTextEditors; + console.log( + `[splitEditor] Found ${allEditors.length} visible editors:`, + allEditors.map((e) => e.document.fileName), + ); + + if (fileName) { + // Search by file name (case-insensitive, partial match) + const pattern = fileName.toLowerCase(); + console.log( + `[splitEditor] Searching for pattern: ${pattern}`, + ); + + // First try visible editors + targetEditor = allEditors.find((editor) => + editor.document.fileName + .toLowerCase() + .includes(pattern), + ); + + // If not found in visible editors, search all open tabs + if (!targetEditor) { + console.log( + `[splitEditor] Not found in visible editors, searching all tabs...`, + ); + for (const tabGroup of vscode.window.tabGroups.all) { + for (const tab of tabGroup.tabs) { + const input = tab.input as any; + if (input?.uri) { + const filePath = + input.uri.fsPath || input.uri.path; + if ( + filePath.toLowerCase().includes(pattern) + ) { + console.log( + `[splitEditor] Found tab with matching file: ${filePath}`, + ); + // Open the document to make it an editor + const document = + await vscode.workspace.openTextDocument( + input.uri, + ); + targetEditor = + await vscode.window.showTextDocument( + document, + { + viewColumn: + tabGroup.viewColumn, + preserveFocus: false, + }, + ); + break; + } + } + } + if (targetEditor) break; + } + } + + if (!targetEditor) { + console.log( + `[splitEditor] No editor or tab found with pattern: ${pattern}`, + ); + actionResult.handled = false; + actionResult.message = `No editor found with file: ${fileName}`; + break; + } + console.log( + `[splitEditor] Found target editor: ${targetEditor.document.fileName}`, + ); + } else if (editorPosition !== undefined) { + // Search by position + if (typeof editorPosition === "number") { + targetEditor = allEditors[editorPosition]; + if (!targetEditor) { + actionResult.handled = false; + actionResult.message = `No editor at position: ${editorPosition}`; + break; + } + } else if (editorPosition === "first") { + // Sort by viewColumn to get leftmost editor + const sortedEditors = [...allEditors].sort( + (a, b) => (a.viewColumn || 0) - (b.viewColumn || 0), + ); + targetEditor = sortedEditors[0]; + } else if (editorPosition === "last") { + // Sort by viewColumn to get rightmost editor + const sortedEditors = [...allEditors].sort( + (a, b) => (a.viewColumn || 0) - (b.viewColumn || 0), + ); + targetEditor = sortedEditors[sortedEditors.length - 1]; + } else if (editorPosition === "active") { + targetEditor = vscode.window.activeTextEditor; + } + + if (!targetEditor) { + actionResult.handled = false; + actionResult.message = `No editor found at position: ${editorPosition}`; + break; + } + } + + // Focus the target editor temporarily (only if it's not already active) + if (targetEditor !== vscode.window.activeTextEditor) { + console.log( + `[splitEditor] Focusing target editor: ${targetEditor!.document.fileName}`, + ); + await vscode.window.showTextDocument( + targetEditor!.document, + { + viewColumn: + targetEditor!.viewColumn ?? + vscode.ViewColumn.One, + preserveFocus: false, + }, + ); + } + } + + // Execute the split command + const direction = actionData?.direction; + if (direction) { + switch (direction) { case "right": { - vscode.commands.executeCommand( + await vscode.commands.executeCommand( "workbench.action.splitEditorRight", ); break; } case "left": { - vscode.commands.executeCommand( + await vscode.commands.executeCommand( "workbench.action.splitEditorLeft", ); break; } case "up": { - vscode.commands.executeCommand( + await vscode.commands.executeCommand( "workbench.action.splitEditorUp", ); break; } case "down": { - vscode.commands.executeCommand( + await vscode.commands.executeCommand( "workbench.action.splitEditorDown", ); break; } } - actionResult.message = `Split editor ${actionData.direction}`; } else { - vscode.commands.executeCommand("workbench.action.splitEditor"); - actionResult.message = "Split editor"; + await vscode.commands.executeCommand( + "workbench.action.splitEditor", + ); } + + // Build result message + const targetInfo = fileName + ? ` (${fileName})` + : editorPosition !== undefined + ? ` (${editorPosition})` + : ""; + actionResult.message = + `Split editor${targetInfo} ${direction || ""}`.trim(); + console.log( + `[splitEditor] Completed successfully: ${actionResult.message}`, + ); break; } diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md index 39adee334..f02106f28 100644 --- a/ts/packages/commandExecutor/README.md +++ b/ts/packages/commandExecutor/README.md @@ -86,7 +86,7 @@ The server is configured in `.mcp.json`: #### execute_command -Execute user commands such as playing music, managing lists, or working with calendars. +Execute user commands including music playback, list management, calendar operations, and VSCode automation. **Parameters:** @@ -94,10 +94,28 @@ Execute user commands such as playing music, managing lists, or working with cal **Examples:** +**Music & Media:** - "play sweet emotion by aerosmith" +- "play bohemian rhapsody by queen" + +**Lists & Tasks:** - "add jelly beans to my grocery list" +- "what's on my shopping list" + +**Calendar:** - "schedule a meeting for tomorrow at 2pm" +**VSCode Automation:** +- "switch to monokai theme" +- "change theme to dark+" +- "open the explorer view" +- "create a new folder called components" +- "open file app.ts" +- "split editor to the right" +- "toggle zen mode" +- "open integrated terminal" +- "show output panel" + #### ping (debug mode) Test server connectivity. @@ -115,7 +133,9 @@ Command Executor MCP Server ↓ TypeAgent Dispatcher (WebSocket) ↓ -TypeAgent Agents (Music, Lists, Calendar, etc.) + ├─ TypeAgent Agents (Music, Lists, Calendar, etc.) + └─ Coda VSCode Extension (via WebSocket on port 8082) + └─ VSCode APIs (theme, editor, files, terminal, etc.) ``` The MCP server: diff --git a/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md b/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md new file mode 100644 index 000000000..c99ca05f6 --- /dev/null +++ b/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md @@ -0,0 +1,318 @@ + + +# VSCode Capabilities Available Through Command Executor + +The Command Executor MCP server can control VSCode through the Coda extension. Below are the available capabilities organized by category. + +## How It Works + +``` +User → Claude Code → execute_command MCP tool → + → TypeAgent Dispatcher → + → Coda Extension (WebSocket on port 8082) → + → VSCode APIs +``` + +The Coda VSCode extension connects to TypeAgent's dispatcher and can execute various VSCode commands. Simply use natural language with the `execute_command` tool. + +## Available Commands + +### Theme & Appearance + +**Change Color Theme:** +- "switch to monokai theme" +- "change theme to dark+" +- "change to light theme" +- "set theme to solarized dark" + +**Display Controls:** +- "toggle full screen" +- "toggle zen mode" +- "zoom in" (zooms in 5 levels) +- "zoom out" (zooms out 5 levels) +- "reset zoom" + +### Editor Layout + +**Split Editor:** +- "split editor to the right" (splits currently focused editor) +- "split editor to the left" +- "split editor up" +- "split editor down" +- "split the first editor to the right" (splits leftmost editor) +- "split the last editor" (splits rightmost editor) +- "split app.tsx to the right" (splits editor with app.tsx file) +- "split the typescript file" (splits editor with a .ts file) + +**Column Layout:** +- "change editor to single column" +- "change editor to double columns" +- "change editor to three columns" +- "toggle editor layout" + +**Editor Management:** +- "close editor" + +### File & Folder Operations + +**Open Files:** +- "open file app.ts" +- "open main.py" +- "goto file index.html" + +**Navigate:** +- "goto line 42" +- "goto file package.json" + +**Create Files:** +- "create new file" (untitled) +- "create file hello.ts in src folder" + +**Create Folders:** +- "create folder called components" +- "create folder utils in src" + +**Open Folders:** +- "open folder src in explorer" +- "reveal packages folder" + +### Views & Panels + +**Show Views:** +- "show explorer" +- "open explorer view" +- "show search" +- "show source control" +- "show output panel" + +**Special Views:** +- "toggle search details" +- "replace in files" +- "open markdown preview" +- "open markdown preview to side" + +### Navigation & Commands + +**Command Palette:** +- "show command palette" + +**Quick Open:** +- "quick open file" + +**Settings:** +- "open settings" +- "show user settings" +- "show keyboard shortcuts" + +### Terminal + +**Open Terminal:** +- "open integrated terminal" +- "open terminal in src folder" +- "open terminal and run npm install" + +### Tasks & Build + +**Run Tasks:** +- "build the project" +- "clean the project" +- "rebuild the project" +- "run build task in packages folder" + +### Window Management + +**New Windows:** +- "open new window" + +## Usage Examples + +### Example 1: Change Theme + +**User to Claude Code:** +``` +switch to monokai theme +``` + +**Claude Code calls:** +```json +{ + "tool": "execute_command", + "arguments": { + "request": "switch to monokai theme" + } +} +``` + +**Result:** +``` +Changed theme to Monokai +``` + +### Example 2: Open File and Split Editor + +**User to Claude Code:** +``` +open app.ts and split the editor to the right +``` + +**Claude Code can:** +1. Call execute_command with "open app.ts" +2. Call execute_command with "split editor to the right" + +Or TypeAgent might handle both in one call. + +### Example 3: Create Project Structure + +**User to Claude Code:** +``` +create folders called src, tests, and docs +``` + +**Claude Code calls:** +```json +{ + "tool": "execute_command", + "arguments": { + "request": "create folders called src, tests, and docs" + } +} +``` + +## Implementation Details + +### How Coda Extension Handles Commands + +The Coda extension listens for WebSocket messages from TypeAgent's Code Agent: + +```typescript +// Message format from TypeAgent +{ + id: "123", + method: "code/changeColorScheme", + params: { + theme: "Monokai" + } +} + +// Response from Coda +{ + id: "123", + result: "Changed theme to Monokai" +} +``` + +### Action Handlers + +Commands are routed through several handlers: + +1. **handleBaseEditorActions**: Theme, split, layout, new file +2. **handleGeneralKBActions**: Command palette, goto, settings +3. **handleDisplayKBActions**: Views, panels, zoom, full screen +4. **handleWorkbenchActions**: Files, folders, tasks, terminal +5. **handleDebugActions**: Debugging operations +6. **handleExtensionActions**: Extension management +7. **handleEditorCodeActions**: Code editing, refactoring + +See source files in `packages/coda/src/handle*.ts` for full details. + +### Available Action Names + +Here are the internal action names (useful for understanding the code): + +**Base Editor:** +- `changeColorScheme` +- `splitEditor` +- `changeEditorLayout` +- `newFile` + +**Display:** +- `toggleFullScreen` +- `toggleEditorLayout` +- `zoomIn`, `zoomOut`, `fontZoomReset` +- `showExplorer`, `showSearch`, `showSourceControl` +- `showOutputPanel` +- `toggleSearchDetails` +- `replaceInFiles` +- `openMarkdownPreview`, `openMarkdownPreviewToSide` +- `zenMode` +- `closeEditor` +- `openSettings` + +**General:** +- `showCommandPalette` +- `gotoFileOrLineOrSymbol` +- `newWindowFromApp` +- `showUserSettings` +- `showKeyboardShortcuts` + +**Workbench:** +- `workbenchOpenFile` +- `workbenchOpenFolder` +- `workbenchCreateFolderFromExplorer` +- `workbenchBuildRelatedTask` +- `openInIntegratedTerminal` + +## Prerequisites + +To use these VSCode capabilities: + +1. **TypeAgent Dispatcher** must be running: + ```bash + pnpm run start:agent-server + ``` + +2. **Coda Extension** must be installed and activated in VSCode: + - Published as `aisystems.copilot-coda` + - Auto-connects to dispatcher on port 8082 + +3. **Command Executor MCP Server** configured in `.mcp.json`: + ```json + { + "mcpServers": { + "command-executor": { + "command": "node", + "args": ["packages/commandExecutor/dist/server.js"] + } + } + } + ``` + +## Limitations + +1. **Natural Language Translation**: Commands must be clear enough for TypeAgent to translate to the correct action +2. **File/Folder Matching**: File and folder names are matched via search, so ambiguous names might require user selection +3. **Terminal Commands**: High-risk terminal commands are blocked for security +4. **Theme Names**: Theme names must match installed themes exactly + +## Testing Commands + +You can test these commands directly in Claude Code: + +``` +// Test theme changing +switch to monokai theme + +// Test editor layout +split editor to the right + +// Test file operations +open package.json + +// Test views +show explorer + +// Test terminal +open integrated terminal +``` + +## Extending Capabilities + +To add new VSCode capabilities: + +1. Add handler in `packages/coda/src/handle*.ts` +2. Register in `handleVSCodeActions` function +3. Update this documentation +4. No changes needed to MCP server (it forwards all commands to dispatcher) + +The beauty of this architecture is that new capabilities added to Coda are automatically available through the MCP server without any code changes to the command executor. diff --git a/ts/packages/commandExecutor/src/commandServer.ts b/ts/packages/commandExecutor/src/commandServer.ts index 4e0dca2c4..be2966bdf 100644 --- a/ts/packages/commandExecutor/src/commandServer.ts +++ b/ts/packages/commandExecutor/src/commandServer.ts @@ -352,7 +352,11 @@ export class CommandServer { { inputSchema: executeCommandRequestSchema(), description: - "Execute a user command such as playing music, managing lists, or working with calendars", + "Execute user commands including:\n" + + "- Music & media: play songs, control playback\n" + + "- Lists & tasks: manage shopping lists, todo lists\n" + + "- Calendar: schedule events, view calendar\n" + + "- VSCode automation: change theme (e.g. 'switch to monokai theme'), open files, create folders, run tasks, manage editor layout, open terminals, toggle settings", }, async (request: ExecuteCommandRequest) => this.executeCommand(request), From 7cf1b8089b0806bb60e8c43624ee8be6093c21b3 Mon Sep 17 00:00:00 2001 From: steveluc Date: Sun, 18 Jan 2026 21:36:02 -0800 Subject: [PATCH 7/7] Fix prettier formatting for README and VSCODE_CAPABILITIES --- ts/packages/commandExecutor/README.md | 5 ++- .../commandExecutor/VSCODE_CAPABILITIES.md | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ts/packages/commandExecutor/README.md b/ts/packages/commandExecutor/README.md index bde1c9839..a510b40f3 100644 --- a/ts/packages/commandExecutor/README.md +++ b/ts/packages/commandExecutor/README.md @@ -95,17 +95,21 @@ Execute user commands including music playback, list management, calendar operat **Examples:** **Music & Media:** + - "play sweet emotion by aerosmith" - "play bohemian rhapsody by queen" **Lists & Tasks:** + - "add jelly beans to my grocery list" - "what's on my shopping list" **Calendar:** + - "schedule a meeting for tomorrow at 2pm" **VSCode Automation:** + - "switch to monokai theme" - "change theme to dark+" - "open the explorer view" @@ -116,7 +120,6 @@ Execute user commands including music playback, list management, calendar operat - "open integrated terminal" - "show output panel" - #### ping (debug mode) Test server connectivity. diff --git a/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md b/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md index c99ca05f6..ecb97378c 100644 --- a/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md +++ b/ts/packages/commandExecutor/VSCODE_CAPABILITIES.md @@ -21,12 +21,14 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Theme & Appearance **Change Color Theme:** + - "switch to monokai theme" - "change theme to dark+" - "change to light theme" - "set theme to solarized dark" **Display Controls:** + - "toggle full screen" - "toggle zen mode" - "zoom in" (zooms in 5 levels) @@ -36,6 +38,7 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Editor Layout **Split Editor:** + - "split editor to the right" (splits currently focused editor) - "split editor to the left" - "split editor up" @@ -46,40 +49,48 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var - "split the typescript file" (splits editor with a .ts file) **Column Layout:** + - "change editor to single column" - "change editor to double columns" - "change editor to three columns" - "toggle editor layout" **Editor Management:** + - "close editor" ### File & Folder Operations **Open Files:** + - "open file app.ts" - "open main.py" - "goto file index.html" **Navigate:** + - "goto line 42" - "goto file package.json" **Create Files:** + - "create new file" (untitled) - "create file hello.ts in src folder" **Create Folders:** + - "create folder called components" - "create folder utils in src" **Open Folders:** + - "open folder src in explorer" - "reveal packages folder" ### Views & Panels **Show Views:** + - "show explorer" - "open explorer view" - "show search" @@ -87,6 +98,7 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var - "show output panel" **Special Views:** + - "toggle search details" - "replace in files" - "open markdown preview" @@ -95,12 +107,15 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Navigation & Commands **Command Palette:** + - "show command palette" **Quick Open:** + - "quick open file" **Settings:** + - "open settings" - "show user settings" - "show keyboard shortcuts" @@ -108,6 +123,7 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Terminal **Open Terminal:** + - "open integrated terminal" - "open terminal in src folder" - "open terminal and run npm install" @@ -115,6 +131,7 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Tasks & Build **Run Tasks:** + - "build the project" - "clean the project" - "rebuild the project" @@ -123,6 +140,7 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Window Management **New Windows:** + - "open new window" ## Usage Examples @@ -130,11 +148,13 @@ The Coda VSCode extension connects to TypeAgent's dispatcher and can execute var ### Example 1: Change Theme **User to Claude Code:** + ``` switch to monokai theme ``` **Claude Code calls:** + ```json { "tool": "execute_command", @@ -145,6 +165,7 @@ switch to monokai theme ``` **Result:** + ``` Changed theme to Monokai ``` @@ -152,11 +173,13 @@ Changed theme to Monokai ### Example 2: Open File and Split Editor **User to Claude Code:** + ``` open app.ts and split the editor to the right ``` **Claude Code can:** + 1. Call execute_command with "open app.ts" 2. Call execute_command with "split editor to the right" @@ -165,11 +188,13 @@ Or TypeAgent might handle both in one call. ### Example 3: Create Project Structure **User to Claude Code:** + ``` create folders called src, tests, and docs ``` **Claude Code calls:** + ```json { "tool": "execute_command", @@ -221,12 +246,14 @@ See source files in `packages/coda/src/handle*.ts` for full details. Here are the internal action names (useful for understanding the code): **Base Editor:** + - `changeColorScheme` - `splitEditor` - `changeEditorLayout` - `newFile` **Display:** + - `toggleFullScreen` - `toggleEditorLayout` - `zoomIn`, `zoomOut`, `fontZoomReset` @@ -240,6 +267,7 @@ Here are the internal action names (useful for understanding the code): - `openSettings` **General:** + - `showCommandPalette` - `gotoFileOrLineOrSymbol` - `newWindowFromApp` @@ -247,6 +275,7 @@ Here are the internal action names (useful for understanding the code): - `showKeyboardShortcuts` **Workbench:** + - `workbenchOpenFile` - `workbenchOpenFolder` - `workbenchCreateFolderFromExplorer` @@ -258,11 +287,13 @@ Here are the internal action names (useful for understanding the code): To use these VSCode capabilities: 1. **TypeAgent Dispatcher** must be running: + ```bash pnpm run start:agent-server ``` 2. **Coda Extension** must be installed and activated in VSCode: + - Published as `aisystems.copilot-coda` - Auto-connects to dispatcher on port 8082