#
tokens: 36279/50000 26/26 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── npm-publish.yml
├── .gitignore
├── .idea
│   ├── AugmentWebviewStateStore.xml
│   ├── codeStyles
│   │   ├── codeStyleConfig.xml
│   │   └── Project.xml
│   ├── git_toolbox_blame.xml
│   ├── git_toolbox_prj.xml
│   ├── inspectionProfiles
│   │   └── Project_Default.xml
│   ├── jsLinters
│   │   └── eslint.xml
│   ├── modules.xml
│   ├── prettier.xml
│   ├── sf-mcp.iml
│   ├── shelf
│   │   ├── Uncommitted_changes_before_Checkout_at_4_8_25__22_47__Changes_.xml
│   │   └── Uncommitted_changes_before_Checkout_at_4_8_25,_22_47_[Changes]
│   │       └── shelved.patch
│   ├── vcs.xml
│   └── workspace.xml
├── .prettierrc
├── build
│   ├── index.js
│   ├── resources.js
│   ├── sfCommands.js
│   └── utils.js
├── CHANGELOG.md
├── CLAUDE.md
├── eslint.config.js
├── package-lock.json
├── package.json
├── README.md
├── run.sh
├── src
│   ├── index.ts
│   ├── resources.ts
│   ├── sfCommands.ts
│   └── utils.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 120,
  "tabWidth": 4,
  "useTabs": false,
  "endOfLine":"lf"
}
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*


```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Salesforce CLI MCP Server

Model Context Protocol (MCP) server for providing Salesforce CLI functionality to LLM tools like Claude Desktop.

## Overview

This MCP server wraps the Salesforce CLI (`sf`) command-line tool and exposes its commands as MCP tools and resources, allowing LLM-powered agents to:

- View help information about Salesforce CLI topics and commands
- Execute Salesforce CLI commands with appropriate parameters
- Leverage Salesforce CLI capabilities in AI workflows

## Requirements

- Node.js 18+ and npm
- Salesforce CLI (`sf`) installed and configured
- Your Salesforce org credentials configured in the CLI

## Installation

```bash
# Clone the repository
git clone <repository-url>
cd sfMcp

# Install dependencies
npm install
```

## Usage

### Starting the server

```bash
# Basic usage
npm start

# With project roots
npm start /path/to/project1 /path/to/project2
# or using the convenience script
npm run with-roots /path/to/project1 /path/to/project2

# As an npx package with roots
npx -y codefriar/sf-mcp /path/to/project1 /path/to/project2
```

The MCP server uses stdio transport, which can be used with MCP clients 
like the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) or Claude Desktop.

### Configuring in Claude Desktop

To configure this MCP in Claude Desktop's `.claude.json` configuration:

```json
{
  "tools": {
    "salesforce": {
      "command": "/path/to/node",
      "args": [
        "/path/to/sf-mcp/build/index.js",
        "/path/to/project1",
        "/path/to/project2"
      ]
    }
  }
}
```

Using the npm package directly:

```json
{
  "tools": {
    "salesforce": {
      "command": "/path/to/npx", 
      "args": [
        "-y",
        "codefriar/sf-mcp",
        "/path/to/project1",
        "/path/to/project2"
      ]
    }
  }
}
```

### Development

```bash
# Watch mode (recompiles on file changes)
npm run dev

# In another terminal
npm start [optional project roots...]
```

## Available Tools and Resources

This MCP server provides Salesforce CLI commands as MCP tools. It automatically discovers and registers all available commands from the Salesforce CLI, and also specifically implements the most commonly used commands.

### Core Tools

- `sf_version` - Get the Salesforce CLI version information
- `sf_help` - Get help information for Salesforce CLI commands
- `sf_cache_clear` - Clear the command discovery cache
- `sf_cache_refresh` - Refresh the command discovery cache

### Project Directory Management (Roots)

For commands that require a Salesforce project context (like deployments), you must specify the project directory.
The MCP supports multiple project directories (roots) similar to the filesystem MCP.

#### Configuration Methods

**Method 1: Via Command Line Arguments**
```bash
# Start the MCP with project roots
npm start /path/to/project1 /path/to/project2
# or
npx -y codefriar/sf-mcp /path/to/project1 /path/to/project2
```

When configured this way, the roots will be automatically named `root1`, `root2`, etc., 
with the first one set as default.

**Method 2: Using MCP Tools**
- `sf_set_project_directory` - Set a Salesforce project directory to use for commands
  - Parameters: 
    - `directory` - Path to a directory containing a sfdx-project.json file
    - `name` - (Optional) Name for this project root
    - `description` - (Optional) Description for this project root
    - `isDefault` - (Optional) Set this root as the default for command execution
- `sf_list_roots` - List all configured project roots
- `sf_detect_project_directory` - Attempt to detect project directory from user messages

Example usage:
```
# Set project directory with a name
sf_set_project_directory --directory=/path/to/your/sfdx/project --name=project1 --isDefault=true

# List all configured roots
sf_list_roots

# Or include in your message:
"Please deploy the apex code from the project in /path/to/your/sfdx/project to my scratch org"
```

**Method 3: Claude Desktop Configuration**
Configure project roots in `.claude.json` as described below.

#### Using Project Roots

You can execute commands in specific project roots:
```
# Using resource URI
sf://roots/project1/commands/project deploy start --sourcedir=force-app

# Using rootName parameter
sf_project_deploy_start --sourcedir=force-app --rootName=project1
```

Project directory must be specified for commands such as deployments,
source retrieval, and other project-specific operations. 
If multiple roots are configured, the default root will be used unless otherwise specified.

### Key Implemented Tools

The following commands are specifically implemented and guaranteed to work:

#### Organization Management

- `sf_org_list` - List Salesforce orgs
    - Parameters: `json`, `verbose`
- `sf_auth_list_orgs` - List authenticated Salesforce orgs
    - Parameters: `json`, `verbose`
- `sf_org_display` - Display details about an org
    - Parameters: `targetusername`, `json`
- `sf_org_open` - Open an org in the browser
    - Parameters: `targetusername`, `path`, `urlonly`

#### Apex Code

- `sf_apex_run` - Run anonymous Apex code
    - Parameters: `targetusername`, `file`, `apexcode`, `json`
- `sf_apex_test_run` - Run Apex tests
    - Parameters: `targetusername`, `testnames`, `suitenames`, `classnames`, `json`

#### Data Management

- `sf_data_query` - Execute a SOQL query
    - Parameters: `targetusername`, `query`, `json`
- `sf_schema_list_objects` - List sObjects in the org
    - Parameters: `targetusername`, `json`
- `sf_schema_describe` - Describe a Salesforce object
    - Parameters: `targetusername`, `sobject`, `json`

#### Deployment

- `sf_project_deploy_start` - Deploy the source to an org
    - Parameters: `targetusername`, `sourcedir`, `json`, `wait`

### Dynamically Discovered Tools

The server discovers all available Salesforce CLI commands and registers them as tools with format: `sf_<topic>_<command>`.

For example:

- `sf_apex_run` - Run anonymous Apex code
- `sf_data_query` - Execute a SOQL query

For nested topic commands, the tool name includes the full path with underscores:

- `sf_apex_log_get` - Get apex logs
- `sf_org_login_web` - Login to an org using web flow

The server also creates simplified aliases for common nested commands where possible:

- `sf_get` as an alias for `sf_apex_log_get`
- `sf_web` as an alias for `sf_org_login_web`

The available commands vary depending on the installed Salesforce CLI plugins.

> **Note:** Command discovery is cached to improve startup performance. If you install new SF CLI plugins, use the `sf_cache_refresh` tool to update the cache, then restart the server.

### Resources

The following resources provide documentation about Salesforce CLI:

- `sf://help` - Main CLI documentation
- `sf://topics/{topic}/help` - Topic help documentation
- `sf://commands/{command}/help` - Command help documentation
- `sf://topics/{topic}/commands/{command}/help` - Topic-command help documentation
- `sf://version` - Version information
- `sf://roots` - List all configured project roots
- `sf://roots/{root}/commands/{command}` - Execute a command in a specific project root

## How It Works

1. At startup, the server checks for a cached list of commands (stored in `~/.sf-mcp/command-cache.json`)
2. If a valid cache exists, it's used to register commands; otherwise, commands are discovered dynamically
3. During discovery, the server queries `sf commands --json` to get a complete list of available commands
4. Command metadata (including parameters and descriptions) is extracted directly from the JSON output
5. All commands are registered as MCP tools with appropriate parameter schemas
6. Resources are registered for help documentation
7. When a tool is called, the corresponding Salesforce CLI command is executed

### Project Roots Management

For commands that require a Salesforce project context:

1. The server checks if any project roots have been configured via `sf_set_project_directory`
2. If multiple roots are configured, it uses the default root unless a specific root is specified
3. If no roots are set, the server will prompt the user to specify a project directory
4. Commands are executed within the appropriate project directory, ensuring proper context
5. The user can add or switch between multiple project roots as needed

Project-specific commands (like deployments, retrievals, etc.) 
will automatically run in the appropriate project directory. 
For commands that don't require a project context, the working directory doesn't matter.

You can execute commands in specific project roots by:
- Using the resource URI: `sf://roots/{rootName}/commands/{command}`
- Providing a `rootName` parameter to command tools (internal implementation details)
- Setting a specific root as the default with `sf_set_project_directory --isDefault=true`

### Command Caching

To improve startup performance, the MCP server caches discovered commands:

- The cache is stored in `~/.sf-mcp/command-cache.json`
- It includes all topics, commands, parameters, and descriptions
- The cache has a validation timestamp and SF CLI version check
- By default, the cache expires after 7 days
- When you install new Salesforce CLI plugins, use `sf_cache_refresh` to update the cache

#### Troubleshooting Cache Issues

The first run of the server performs a full command discovery which can take some time. If you encounter any issues with missing commands or cache problems:

1. Stop the MCP server (if running)
2. Manually delete the cache file: `rm ~/.sf-mcp/command-cache.json`
3. Start the server again: `npm start`

This will force a complete rediscovery of all commands using the official CLI metadata.

If specific commands are still missing, or you've installed new SF CLI plugins:

1. Use the `sf_cache_refresh` tool from Claude Desktop
2. Stop and restart the MCP server

### Handling Nested Topics

The Salesforce CLI has a hierarchical command structure that can be several levels deep. This MCP server handles these nested commands by:

- Converting colon-separated paths to underscore format (`apex:log:get` → `sf_apex_log_get`)
- Providing aliases for common deep commands when possible (`sf_get` for `sf_apex_log_get`)
- Preserving the full command hierarchy in the tool names
- Using the official command structure from `sf commands --json`

Nested topic commands are registered twice when possible—once with the full hierarchy name and once with a simplified alias,
making them easier to discover and use.

## License

ISC

```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
# MCP Development Guide

## Build Commands

- Build project: `npm run build`
- Run the MCP server: `node build/index.js`

## Lint & Formatting

- Format with Prettier: `npx prettier --write 'src/**/*.ts'`
- Lint: `npx eslint 'src/**/*.ts'`
- Type check: `npx tsc --noEmit`

## Testing

- Run tests: `npm test`
- Run a single test: `npm test -- -t 'test name'`

## Code Style Guidelines

- Use ES modules (import/export) syntax
- TypeScript strict mode enabled
- Types: Use strong typing with TypeScript interfaces/types
- Naming: camelCase for variables/functions, PascalCase for classes/interfaces
- Error handling: Use try/catch with typed errors
- Imports: Group by 3rd party, then local, alphabetized within groups
- Async: Prefer async/await over raw Promises
- Documentation: JSDoc for public APIs
- Endpoint API naming following MCP conventions for resources/tools/prompts

## Project Description

- This project seeks to create a Model Context Protocol Server that tools like Claude code, and Claude desktop can use to directly and intelligently interface with the Salesforce Command Line Interface. (CLI)

## Model Context Protocol (MCP) Architecture

### Core Components
- **Hosts**: LLM applications (Claude Desktop, Claude Code) that initiate connections
- **Clients**: Maintain connections with MCP servers
- **Servers**: Provide context, tools, and prompts (this Salesforce CLI MCP server)

### MCP Primitives for Salesforce Integration

#### Tools
- Executable functions for Salesforce operations
- Dynamic tool discovery and invocation
- Tool annotations (read-only, destructive operations)
- Key Salesforce tools to implement:
  - SOQL query execution
  - Record CRUD operations
  - Metadata deployment/retrieval
  - Org inspection and configuration
  - Apex execution and testing

#### Resources
- Expose Salesforce data and metadata
- Unique URI identification for resources
- Support for text and binary content
- Salesforce resources to expose:
  - Object schemas and field definitions
  - Org configuration and limits
  - Deployment metadata
  - Code coverage reports
  - Flow definitions

#### Prompts
- Reusable prompt templates for Salesforce workflows
- Dynamic arguments for context-aware interactions
- Common Salesforce prompt patterns:
  - Data analysis and reporting
  - Code generation and review
  - Deployment guidance
  - Best practices recommendations

#### Sampling
- Allow server to request LLM completions
- Human-in-the-loop approval for destructive operations
- Fine-grained control over Salesforce operations

### Security Considerations
- Input validation for all Salesforce CLI commands
- Proper authentication with Salesforce orgs
- Rate limiting to respect Salesforce API limits
- Sanitization of external interactions
- Secure handling of sensitive org data

### Transport
- Primary: Stdio (standard input/output)
- Alternative: HTTP with Server-Sent Events (SSE)

### Implementation Strategy
1. Start with core Salesforce CLI tools (query, describe, deploy)
2. Use TypeScript MCP SDK for type safety
3. Implement robust error handling for CLI failures
4. Provide clear tool descriptions and examples
5. Add progressive enhancement for advanced features
```

--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------

```
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>
```

--------------------------------------------------------------------------------
/.idea/git_toolbox_blame.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GitToolBoxBlameSettings">
    <option name="version" value="2" />
  </component>
</project>
```

--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="PrettierConfiguration">
    <option name="myConfigurationMode" value="AUTOMATIC" />
    <option name="myRunOnSave" value="true" />
  </component>
</project>
```

--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------

```
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
  </profile>
</component>
```

--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/sf-mcp.iml" filepath="$PROJECT_DIR$/.idea/sf-mcp.iml" />
    </modules>
  </component>
</project>
```

--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="EslintConfiguration">
    <work-dir-patterns value="$PROJECT_DIR$" />
    <custom-configuration-file used="true" path="$PROJECT_DIR$/eslint.config.js" />
    <option name="fix-on-save" value="true" />
  </component>
</project>
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
    "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/.idea/shelf/Uncommitted_changes_before_Checkout_at_4_8_25__22_47__Changes_.xml:
--------------------------------------------------------------------------------

```
<changelist name="Uncommitted_changes_before_Checkout_at_4_8_25,_22_47_[Changes]" date="1744177621660" recycled="true" deleted="true">
  <option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Checkout_at_4_8_25,_22_47_[Changes]/shelved.patch" />
  <option name="DESCRIPTION" value="Uncommitted changes before Checkout at 4/8/25, 22:47 [Changes]" />
</changelist>
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
    eslint.configs.recommended,
    tseslint.configs.recommendedTypeChecked,
    // tseslint.configs.strictTypeChecked,
    // tseslint.configs.stylisticTypeChecked,
    {
        languageOptions: {
            parserOptions: {
                projectService: true,
                tsconfigRootDir: import.meta.dirname,
            },
        },
    },
);
```

--------------------------------------------------------------------------------
/.idea/git_toolbox_prj.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GitToolBoxProjectSettings">
    <option name="commitMessageIssueKeyValidationOverride">
      <BoolValueOverride>
        <option name="enabled" value="true" />
      </BoolValueOverride>
    </option>
    <option name="commitMessageValidationEnabledOverride">
      <BoolValueOverride>
        <option name="enabled" value="true" />
      </BoolValueOverride>
    </option>
  </component>
</project>
```

--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CommitMessageInspectionProfile">
    <profile version="1.0">
      <inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
      <inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
    </profile>
  </component>
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>
```

--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Check if sf command is available
if ! command -v sf &> /dev/null; then
    echo "Error: Salesforce CLI (sf) is not installed or not in your PATH"
    echo "Please install it from: https://developer.salesforce.com/tools/sfdxcli"
    exit 1
fi

# Print current directory and sf version
echo "Current directory: $(pwd)"
echo "Salesforce CLI version:"
sf --version

# Build and run the server
echo "Building and starting MCP server..."
npm run build

# Pass all command-line arguments to the server (for project roots)
node build/index.js "$@"
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package

on:
  release:
    types: [created]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

  publish-npm:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org/
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPMJS_TOKEN}}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
    "name": "sf-mcp",
    "version": "1.3.2",
    "main": "build/index.js",
    "type": "module",
    "bin": {
        "sfmcp": "./build/index.js"
    },
    "scripts": {
        "build": "tsc && chmod 755 build/index.js",
        "start": "node build/index.js",
        "dev": "tsc -w",
        "lint": "eslint src",
        "prepare": "npm run build",
        "format": "prettier --write \"**/*.{ts,json,md}\"",
        "test": "echo \"No tests configured\" && exit 0",
        "release": "standard-version && git push --follow-tags origin main && npm publish",
        "with-roots": "./run.sh"
    },
    "files": [
        "build",
        "run.sh"
    ],
    "keywords": [
        "mcp",
        "modelcontextprotocol",
        "salesforce",
        "sf",
        "cli",
        "llm"
    ],
    "author": "Kevin Poorman",
    "license": "ISC",
    "description": "Model Context Protocol (MCP) server for the Salesforce CLI, making Salesforce CLI commands available to LLM tools like Claude Desktop.",
    "repository": {
        "type": "git",
        "url": "https://github.com/codefriar/sf-mcp"
    },
    "dependencies": {
        "@modelcontextprotocol/sdk": "^1.8.0",
        "zod": "^3.24.2"
    },
    "devDependencies": {
        "@eslint/js": "^9.23.0",
        "@types/node": "^22.13.14",
        "eslint": "^9.23.0",
        "prettier": "^3.5.3",
        "standard-release": "^0.2.0",
        "standard-version": "^9.5.0",
        "typescript": "^5.8.2",
        "typescript-eslint": "^8.28.0"
    }
}

```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# Changelog

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [1.3.2](https://github.com/codefriar/sf-mcp/compare/v1.3.1...v1.3.2) (2025-05-27)

### [1.3.1](https://github.com/codefriar/sf-mcp/compare/v1.3.0...v1.3.1) (2025-04-09)

## [1.3.0](https://github.com/codefriar/sf-mcp/compare/v1.1.1...v1.3.0) (2025-04-09)


### Features

* add contextual execution with project directory detection ([0c02e01](https://github.com/codefriar/sf-mcp/commit/0c02e0100da6906ea0ece9e26e0fd75ec0886044))
* add Salesforce project directory handling for contextual command execution ([5c1ddc3](https://github.com/codefriar/sf-mcp/commit/5c1ddc3783a0e8e80f357dfb0c9c082e8710b36d))
* **context directories:** All commands require a directory ([4b1d76b](https://github.com/codefriar/sf-mcp/commit/4b1d76b0b38c9b5b01b12efbed2ad107320af3c2))
* **roots:** Now with Roots ([da38db3](https://github.com/codefriar/sf-mcp/commit/da38db3187809b42c47604d9d078238d2d02705a))

## [1.2.0](https://github.com/codefriar/sf-mcp/compare/v1.1.1...v1.2.0) (2025-04-09)


### Features

* add contextual execution with project directory detection ([0c02e01](https://github.com/codefriar/sf-mcp/commit/0c02e0100da6906ea0ece9e26e0fd75ec0886044))
* add Salesforce project directory handling for contextual command execution ([5c1ddc3](https://github.com/codefriar/sf-mcp/commit/5c1ddc3783a0e8e80f357dfb0c9c082e8710b36d))
* **context directories:** All commands require a directory ([4b1d76b](https://github.com/codefriar/sf-mcp/commit/4b1d76b0b38c9b5b01b12efbed2ad107320af3c2))
* **roots:** Now with Roots ([da38db3](https://github.com/codefriar/sf-mcp/commit/da38db3187809b42c47604d9d078238d2d02705a))

### [1.1.1](https://github.com/codefriar/sf-mcp/compare/v1.1.0...v1.1.1) (2025-04-02)

## 1.1.0 (2025-03-28)


### Features

* **tools:** autodiscovery of tools ([532c685](https://github.com/codefriar/sf-mcp/commit/532c685aa8b22f01e81b4bfa69024c14a05d932d))

```

--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { executeSfCommand, getProjectRoots } from './sfCommands.js';

/**
 * Register all resources for the SF CLI MCP Server
 */
export function registerResources(server: McpServer): void {
    // Main CLI documentation
    server.resource('sf-help', 'sf://help', async (uri) => ({
        contents: [
            {
                uri: uri.href,
                text: executeSfCommand('-h'),
            },
        ],
    }));

    // Project roots information
    server.resource('sf-roots', 'sf://roots', async (uri) => {
        const roots = getProjectRoots();
        const rootsText = roots.length > 0 
            ? roots.map(root => `${root.name}${root.isDefault ? ' (default)' : ''}: ${root.path}${root.description ? ` - ${root.description}` : ''}`).join('\n')
            : 'No project roots configured. Use sf_set_project_directory to add a project root.';
        
        return {
            contents: [
                {
                    uri: uri.href,
                    text: rootsText,
                },
            ],
        };
    });

    // Topic help documentation
    server.resource(
        'sf-topic-help',
        new ResourceTemplate('sf://topics/{topic}/help', { list: undefined }),
        async (uri, { topic }) => ({
            contents: [
                {
                    uri: uri.href,
                    text: executeSfCommand(`${topic} -h`),
                },
            ],
        })
    );

    // Command help documentation
    server.resource(
        'sf-command-help',
        new ResourceTemplate('sf://commands/{command}/help', { list: undefined }),
        async (uri, { command }) => ({
            contents: [
                {
                    uri: uri.href,
                    text: executeSfCommand(`${command} -h`),
                },
            ],
        })
    );

    // Topic-command help documentation
    server.resource(
        'sf-topic-command-help',
        new ResourceTemplate('sf://topics/{topic}/commands/{command}/help', {
            list: undefined,
        }),
        async (uri, { topic, command }) => ({
            contents: [
                {
                    uri: uri.href,
                    text: executeSfCommand(`${topic} ${command} -h`),
                },
            ],
        })
    );

    // Root-specific command help (execute in a specific root)
    server.resource(
        'sf-root-command',
        new ResourceTemplate('sf://roots/{root}/commands/{command}', { list: undefined }),
        async (uri, { root, command }) => ({
            contents: [
                {
                    uri: uri.href,
                    // Ensure command is treated as string
                    text: executeSfCommand(String(command), String(root)),
                },
            ],
        })
    );

    // Version information
    server.resource('sf-version', 'sf://version', async (uri) => ({
        contents: [
            {
                uri: uri.href,
                text: executeSfCommand('--version'),
            },
        ],
    }));
}

```

--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------

```
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <option name="LINE_SEPARATOR" value="&#10;" />
    <ApexCodeStyleSettings>
      <option name="ALIGN_MULTILINE_COMPOSITE_EXPRESSIONS" value="true" />
      <option name="USAGE_CASE_APEX_CHANGE_CASE_WHEN_FORMATTING" value="true" />
      <option name="KEYWORD_CASE_APEX_CHANGE_CASE_WHEN_FORMATTING" value="true" />
      <option name="KEYWORD_CASE_SOQL_SOSL_CHANGE_CASE_WHEN_FORMATTING" value="true" />
      <option name="KEYWORD_CASE_VISUALFORCE_CHANGE_CASE_WHEN_FORMATTING" value="true" />
      <option name="KEYWORD_CASE_AURA_CHANGE_CASE_WHEN_FORMATTING" value="true" />
      <option name="APEX_DOC_REFORMAT_APEX_DOC" value="true" />
      <option name="APEX_DOC_REQUIRE_DESCRIPTION_TAG" value="true" />
    </ApexCodeStyleSettings>
    <HTMLCodeStyleSettings>
      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
    </HTMLCodeStyleSettings>
    <JSCodeStyleSettings version="0">
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
      <option name="USE_DOUBLE_QUOTES" value="false" />
      <option name="FORCE_QUOTE_STYlE" value="true" />
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
    </JSCodeStyleSettings>
    <TypeScriptCodeStyleSettings version="0">
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
      <option name="USE_DOUBLE_QUOTES" value="false" />
      <option name="FORCE_QUOTE_STYlE" value="true" />
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
    </TypeScriptCodeStyleSettings>
    <VueCodeStyleSettings>
      <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
      <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
    </VueCodeStyleSettings>
    <codeStyleSettings language="Apex">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
        <option name="SMART_TABS" value="true" />
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="HTML">
      <option name="SOFT_MARGINS" value="120" />
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="JavaScript">
      <option name="SOFT_MARGINS" value="120" />
    </codeStyleSettings>
    <codeStyleSettings language="TypeScript">
      <option name="SOFT_MARGINS" value="120" />
    </codeStyleSettings>
    <codeStyleSettings language="Vue">
      <option name="SOFT_MARGINS" value="120" />
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="TAB_SIZE" value="4" />
      </indentOptions>
    </codeStyleSettings>
  </code_scheme>
</component>
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { registerSfCommands, clearCommandCache, refreshCommandCache, setProjectDirectory, getProjectRoots } from './sfCommands.js';
import path from 'path';
import { registerResources } from './resources.js';
import { extractProjectDirectoryFromMessage } from './utils.js';

// Create an MCP server
const server = new McpServer({
    name: 'Salesforce CLI MCP',
    version: '1.1.0',
    description: 'MCP server for Salesforce CLI integration',
});

// Only register utility tools that aren't SF CLI commands
// These are utility functions that extend or manage the MCP server itself
server.tool('sf_cache_clear', 'Clear the cached SF command metadata to force a refresh', {}, async () => {
    const result = clearCommandCache();
    return {
        content: [
            {
                type: 'text',
                text: result
                    ? 'Command cache cleared successfully.'
                    : 'Failed to clear command cache or cache did not exist.',
            },
        ]
    };
});

server.tool('sf_cache_refresh', 'Refresh the SF command cache by re-scanning all available commands', {}, async () => {
    const result = refreshCommandCache();
    return {
        content: [
            {
                type: 'text',
                text: result
                    ? 'Command cache refreshed successfully. Restart the server to use the new cache.'
                    : 'Failed to refresh command cache.',
            },
        ],
    };
});

// Tools for managing Salesforce project directories (roots)

// Tool for automatically detecting project directories from messages
server.tool('sf_detect_project_directory', 'Get instructions for setting up Salesforce project directories for command execution', {}, async () => {
    // Since we can't access the message in this version of MCP,
    // we need to rely on the LLM to extract the directory and use sf_set_project_directory
    
    return {
        content: [
            {
                type: 'text',
                text: 'To set a project directory, please use sf_set_project_directory with the path to your Salesforce project, or include the project path in your message using formats like "Execute in /path/to/project" or "Use project in /path/to/project".',
            },
        ],
    };
});

// Tool for explicitly setting a project directory (root)
server.tool('sf_set_project_directory', 'Set a Salesforce project directory for command execution context', {
    directory: z.string().describe('The absolute path to a directory containing an sfdx-project.json file'),
    name: z.string().optional().describe('Optional name for this project root'),
    description: z.string().optional().describe('Optional description for this project root'),
    isDefault: z.boolean().optional().describe('Set this root as the default for command execution')
}, async (params) => {
    
    // Set the project directory with optional metadata
    const result = setProjectDirectory(params.directory, {
        name: params.name,
        description: params.description,
        isDefault: params.isDefault
    });
    
    return {
        content: [
            {
                type: 'text',
                text: result
                    ? `Successfully set Salesforce project root: ${params.directory}${params.name ? ` with name "${params.name}"` : ''}${params.isDefault ? ' (default)' : ''}`
                    : `Failed to set project directory. Make sure the path exists and contains an sfdx-project.json file.`,
            },
        ],
    };
});

// Tool for listing configured project roots
server.tool('sf_list_roots', 'List all configured Salesforce project directories and their metadata', {}, async () => {
    const roots = getProjectRoots();
    
    if (roots.length === 0) {
        return {
            content: [
                {
                    type: 'text',
                    text: 'No project roots configured. Use sf_set_project_directory to add a project root.'
                }
            ]
        };
    }
    
    // Format roots list for display
    const rootsList = roots.map(root => (
        `- ${root.name || path.basename(root.path)}${root.isDefault ? ' (default)' : ''}: ${root.path}${root.description ? `\n  Description: ${root.description}` : ''}`
    )).join('\n\n');
    
    return {
        content: [
            {
                type: 'text',
                text: `Configured Salesforce project roots:\n\n${rootsList}`
            }
        ]
    };
});

// Start the server with stdio transport
// We can't use middleware, so we'll rely on explicit tool use
// The LLM will need to be instructed to look for project directory references
// and call the sf_set_project_directory tool

/**
 * Process command line arguments to detect and set project roots
 * All arguments that look like filesystem paths are treated as potential roots
 */
function processRootPaths(): void {
    // Skip the first two arguments (node executable and script path)
    const args = process.argv.slice(2);
    
    if (!args || args.length === 0) {
        console.error('No arguments provided');
        return;
    }

    // Filter arguments that appear to be filesystem paths
    // A path typically starts with / or ./ or ../ or ~/ or contains a directory separator
    const rootPaths = args.filter(arg => 
        arg.startsWith('/') || 
        arg.startsWith('./') || 
        arg.startsWith('../') || 
        arg.startsWith('~/') ||
        arg.includes('/') ||
        arg.includes('\\')
    );
    
    if (rootPaths.length === 0) {
        console.error('No project roots identified in CLI arguments');
        return;
    }

    console.error(`Configuring ${rootPaths.length} project roots from CLI arguments...`);
    
    // Process each provided path
    for (let i = 0; i < rootPaths.length; i++) {
        const rootPath = rootPaths[i];
        const isDefault = i === 0; // Make the first root the default
        const rootName = `root${i + 1}`;
        
        // Set up this root
        const result = setProjectDirectory(rootPath, {
            name: rootName,
            isDefault,
            description: `CLI-configured root #${i + 1}`
        });
        
        if (result) {
            console.error(`Configured project root #${i + 1}: ${rootPath}`);
        } else {
            console.error(`Failed to configure project root #${i + 1}: ${rootPath}`);
        }
    }
}

async function main() {
    try {
        // Process any command line arguments for project roots
        processRootPaths();
        
        // Register documentation resources
        registerResources(server);

        // Register all SF CLI commands as tools (dynamic discovery)
        const dynamicToolCount = await registerSfCommands(server);

        // Add the utility tools we registered manually
        const totalTools = dynamicToolCount + 5; // sf_cache_clear, sf_cache_refresh, sf_set_project_directory, sf_detect_project_directory, sf_list_roots
        console.error(`Total registered tools: ${totalTools} (${dynamicToolCount} SF CLI tools + 5 utility tools)`);

        console.error('Starting Salesforce CLI MCP Server...');
        const transport = new StdioServerTransport();
        await server.connect(transport);
    } catch (err) {
        console.error('Error starting server:', err);
        process.exit(1);
    }
}

main();

```

--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="AutoImportSettings">
    <option name="autoReloadType" value="SELECTIVE" />
  </component>
  <component name="ChangeListManager">
    <list default="true" id="f109ddcd-c921-4a27-aea6-657a0d3b75de" name="Changes" comment="feat(context directories): All commands require a directory&#10;&#10;to execute from, so that the child_process can CWD there, and the sf cli understand it's project context">
      <change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
    </list>
    <option name="SHOW_DIALOG" value="false" />
    <option name="HIGHLIGHT_CONFLICTS" value="true" />
    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
    <option name="LAST_RESOLUTION" value="IGNORE" />
  </component>
  <component name="Git.Settings">
    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
  </component>
  <component name="GitHubPullRequestSearchHistory">{
  &quot;lastFilter&quot;: {
    &quot;state&quot;: &quot;OPEN&quot;,
    &quot;assignee&quot;: &quot;codefriar&quot;
  }
}</component>
  <component name="GithubPullRequestsUISettings">{
  &quot;selectedUrlAndAccountId&quot;: {
    &quot;url&quot;: &quot;[email protected]:codefriar/sf-mcp.git&quot;,
    &quot;accountId&quot;: &quot;cd1051c7-86d1-42aa-9984-58b0635d53d5&quot;
  },
  &quot;recentNewPullRequestHead&quot;: {
    &quot;server&quot;: {
      &quot;useHttp&quot;: false,
      &quot;host&quot;: &quot;github.com&quot;,
      &quot;port&quot;: null,
      &quot;suffix&quot;: null
    },
    &quot;owner&quot;: &quot;codefriar&quot;,
    &quot;repository&quot;: &quot;sf-mcp&quot;
  }
}</component>
  <component name="ProjectColorInfo">{
  &quot;associatedIndex&quot;: 4
}</component>
  <component name="ProjectId" id="2urn5n86K5zpk5YZjhjYXG5kRcZ" />
  <component name="ProjectViewState">
    <option name="hideEmptyMiddlePackages" value="true" />
    <option name="showLibraryContents" value="true" />
  </component>
  <component name="PropertiesComponent">{
  &quot;keyToString&quot;: {
    &quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
    &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
    &quot;git-widget-placeholder&quot;: &quot;#2 on feat/contextualExecution&quot;,
    &quot;js.linters.configure.manually.selectedeslint&quot;: &quot;true&quot;,
    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
    &quot;node.js.detected.package.standard&quot;: &quot;true&quot;,
    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
    &quot;node.js.selected.package.eslint&quot;: &quot;/Users/kpoorman/src/sfMcp/node_modules/@eslint/eslintrc&quot;,
    &quot;node.js.selected.package.standard&quot;: &quot;&quot;,
    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
    &quot;settings.editor.selected.configurable&quot;: &quot;settings.javascript.linters.eslint&quot;,
    &quot;ts.external.directory.path&quot;: &quot;/Applications/IntelliJ IDEA.app/Contents/plugins/javascript-plugin/jsLanguageServicesImpl/external&quot;,
    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
  }
}</component>
  <component name="RubyModuleManagerSettings">
    <option name="blackListedRootsPaths">
      <list>
        <option value="$PROJECT_DIR$" />
      </list>
    </option>
  </component>
  <component name="SharedIndexes">
    <attachedChunks>
      <set>
        <option value="bundled-jdk-9823dce3aa75-a94e463ab2e7-intellij.indexing.shared.core-IU-243.26053.27" />
        <option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-IU-243.26053.27" />
      </set>
    </attachedChunks>
  </component>
  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
  <component name="TaskManager">
    <task active="true" id="Default" summary="Default task">
      <changelist id="f109ddcd-c921-4a27-aea6-657a0d3b75de" name="Changes" comment="" />
      <created>1743015380248</created>
      <option name="number" value="Default" />
      <option name="presentableId" value="Default" />
      <updated>1743015380248</updated>
      <workItem from="1743015381427" duration="2834000" />
      <workItem from="1743030208472" duration="5343000" />
      <workItem from="1743138461831" duration="925000" />
      <workItem from="1743139755619" duration="9045000" />
      <workItem from="1743222211272" duration="953000" />
      <workItem from="1743606226833" duration="2474000" />
      <workItem from="1743709358220" duration="3768000" />
    </task>
    <task id="LOCAL-00001" summary="feat(tools): autodiscovery of tools&#10;&#10;based on `sf commands --json`">
      <option name="closed" value="true" />
      <created>1743142354973</created>
      <option name="number" value="00001" />
      <option name="presentableId" value="LOCAL-00001" />
      <option name="project" value="LOCAL" />
      <updated>1743142354973</updated>
    </task>
    <task id="LOCAL-00002" summary="chore(lint): prettier and eslint&#10;&#10;rules applied">
      <option name="closed" value="true" />
      <created>1743143924176</created>
      <option name="number" value="00002" />
      <option name="presentableId" value="LOCAL-00002" />
      <option name="project" value="LOCAL" />
      <updated>1743143924176</updated>
    </task>
    <task id="LOCAL-00003" summary="chore(packaging): added standard-release">
      <option name="closed" value="true" />
      <created>1743144159287</created>
      <option name="number" value="00003" />
      <option name="presentableId" value="LOCAL-00003" />
      <option name="project" value="LOCAL" />
      <updated>1743144159287</updated>
    </task>
    <task id="LOCAL-00004" summary="chore(build): build version">
      <option name="closed" value="true" />
      <created>1743144468869</created>
      <option name="number" value="00004" />
      <option name="presentableId" value="LOCAL-00004" />
      <option name="project" value="LOCAL" />
      <updated>1743144468869</updated>
    </task>
    <task id="LOCAL-00005" summary="chore(cleanup): cleanup of code in prep&#10;&#10;for release, and the use of npx">
      <option name="closed" value="true" />
      <created>1743613695726</created>
      <option name="number" value="00005" />
      <option name="presentableId" value="LOCAL-00005" />
      <option name="project" value="LOCAL" />
      <updated>1743613695726</updated>
    </task>
    <task id="LOCAL-00006" summary="feat(context directories): All commands require a directory&#10;&#10;to execute from, so that the child_process can CWD there, and the sf cli understand it's project context">
      <option name="closed" value="true" />
      <created>1743826235821</created>
      <option name="number" value="00006" />
      <option name="presentableId" value="LOCAL-00006" />
      <option name="project" value="LOCAL" />
      <updated>1743826235821</updated>
    </task>
    <option name="localTasksCounter" value="7" />
    <servers />
  </component>
  <component name="TypeScriptGeneratedFilesManager">
    <option name="version" value="3" />
  </component>
  <component name="VcsManagerConfiguration">
    <MESSAGE value="feat(tools): autodiscovery of tools&#10;&#10;based on `sf commands --json`" />
    <MESSAGE value="chore(lint): prettier and eslint&#10;&#10;rules applied" />
    <MESSAGE value="chore(packaging): added standard-release" />
    <MESSAGE value="chore(build): build version" />
    <MESSAGE value="chore(cleanup): cleanup of code in prep&#10;&#10;for release, and the use of npx" />
    <MESSAGE value="feat(context directories): All commands require a directory&#10;&#10;to execute from, so that the child_process can CWD there, and the sf cli understand it's project context" />
    <option name="LAST_COMMIT_MESSAGE" value="feat(context directories): All commands require a directory&#10;&#10;to execute from, so that the child_process can CWD there, and the sf cli understand it's project context" />
  </component>
  <component name="XSLT-Support.FileAssociations.UIState">
    <expand />
    <select />
  </component>
</project>
```

--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utility functions for working with the Salesforce CLI
 */

/**
 * Parse a user message to look for project directory specification
 * @param message A message from the user that might contain project directory specification
 * @returns The extracted directory path, or null if none found
 */
export function extractProjectDirectoryFromMessage(message: string): string | null {
    if (!message) return null;
    
    // Common patterns for specifying project directories
    const patterns = [
        // "Execute in /path/to/project"
        /[Ee]xecute\s+(?:in|from)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "Run in /path/to/project"
        /[Rr]un\s+(?:in|from)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "Use project in /path/to/project"
        /[Uu]se\s+project\s+(?:in|from|at)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "Set project directory to /path/to/project"
        /[Ss]et\s+project\s+directory\s+(?:to|as)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "Project is at /path/to/project"
        /[Pp]roject\s+(?:is|located)\s+(?:at|in)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "My project is in /path/to/project"
        /[Mm]y\s+project\s+is\s+(?:at|in)\s+(['"]?)([\/~][^\n'"]+)\1/,
        // "/path/to/project is my project"
        /(['"]?)([\/~][^\n'"]+)\1\s+is\s+my\s+(?:project|directory)/,
    ];
    
    for (const pattern of patterns) {
        const match = message.match(pattern);
        if (match) {
            return match[2];
        }
    }
    
    return null;
}

/**
 * Formats an object as a string representation of CLI flags
 * @param flags Key-value pairs of flag names and values
 * @returns Formatted flags string suitable for command line
 */
export function formatFlags(flags: Record<string, any>): string {
    if (!flags) return '';

    return Object.entries(flags)
        .map(([key, value]) => {
            // Skip undefined/null values
            if (value === undefined || value === null) return '';

            // Handle boolean flags
            if (typeof value === 'boolean') {
                return value ? `--${key}` : '';
            }

            // Handle arrays (space-separated multi-values)
            if (Array.isArray(value)) {
                return value.map((v) => `--${key}=${escapeValue(v)}`).join(' ');
            }

            // Handle objects (JSON stringify)
            if (typeof value === 'object') {
                return `--${key}=${escapeValue(JSON.stringify(value))}`;
            }

            // Regular values
            return `--${key}=${escapeValue(value)}`;
        })
        .filter(Boolean)
        .join(' ');
}

/**
 * Escapes values for command line usage
 */
function escapeValue(value: any): string {
    const stringValue = String(value);

    // If value contains spaces, wrap in quotes
    if (stringValue.includes(' ')) {
        // Escape any existing quotes
        return `"${stringValue.replace(/"/g, '\\"')}"`;
    }

    return stringValue;
}

/**
 * Parses help text to extract structured information about commands or flags
 * @param helpText Help text from Salesforce CLI
 * @returns Structured information extracted from help text
 */
export function parseHelpText(helpText: string): {
    description: string;
    examples: string[];
    flags: Record<
        string,
        {
            name: string;
            description: string;
            required: boolean;
            type: string;
            char?: string;
        }
    >;
} {
    const description: string[] = [];
    const examples: string[] = [];
    const flags: Record<string, any> = {};

    // Split by sections
    const sections = helpText.split(/\n\s*\n/);

    // Extract description (usually the first section, skipping DESCRIPTION header if present)
    if (sections.length > 0) {
        let firstSection = sections[0].trim();
        if (firstSection.toUpperCase().startsWith('DESCRIPTION')) {
            firstSection = firstSection.substring(firstSection.indexOf('\n') + 1).trim();
        }
        description.push(firstSection);
    }

    // Look for a description section if the first section wasn't clear
    if (description[0]?.length < 10 || description[0]?.toUpperCase().includes('USAGE')) {
        const descSection = sections.find(
            (section) =>
                section.toUpperCase().startsWith('DESCRIPTION') || section.toUpperCase().includes('\nDESCRIPTION\n')
        );

        if (descSection) {
            const descContent = descSection.replace(/DESCRIPTION/i, '').trim();
            if (descContent) {
                description.push(descContent);
            }
        }
    }

    // Look for examples section with improved pattern matching
    const examplePatterns = [/EXAMPLES?/i, /USAGE/i];

    for (const pattern of examplePatterns) {
        const exampleSection = sections.find((section) => pattern.test(section));
        if (exampleSection) {
            // Extract examples - look for command lines that start with $ or sf
            const exampleLines = exampleSection
                .split('\n')
                .filter((line) => {
                    const trimmed = line.trim();
                    return trimmed.startsWith('$') || trimmed.startsWith('sf ') || /^\s*\d+\.\s+sf\s+/.test(line); // Numbered examples: "1. sf ..."
                })
                .map((line) => line.trim().replace(/^\d+\.\s+/, '')); // Remove numbering if present

            examples.push(...exampleLines);
        }
    }

    // Look for flags section with improved pattern matching
    const flagPatterns = [/FLAGS/i, /OPTIONS/i, /PARAMETERS/i, /ARGUMENTS/i];

    for (const pattern of flagPatterns) {
        const flagSections = sections.filter((section) => pattern.test(section));

        for (const flagSection of flagSections) {
            // Skip the section header line
            const sectionLines = flagSection.split('\n').slice(1);

            // Different patterns for flag lines
            const flagPatterns = [
                // Pattern 1: Classic -c, --char=<value> Description
                /^\s*(?:-([a-zA-Z]),\s+)?--([a-zA-Z][a-zA-Z0-9-]+)(?:=<?([a-zA-Z0-9_\-\[\]|]+)>?)?\s+(.+)$/,

                // Pattern 2: Indented flag with details (common in newer SF CLI)
                /^\s+(?:-([a-zA-Z]),\s+)?--([a-zA-Z][a-zA-Z0-9-]+)(?:\s+|\=)(?:<([a-zA-Z0-9_\-\[\]|]+)>)?\s*\n\s+(.+)/,

                // Pattern 3: Simple flag with no/minimal formatting
                /^\s*(?:-([a-zA-Z]),\s*)?--([a-zA-Z][a-zA-Z0-9-]+)(?:\s+|\=)?(?:\s*<([a-zA-Z0-9_\-\[\]|]+)>)?\s+(.+)$/,
            ];

            // Process the flag section
            let i = 0;
            while (i < sectionLines.length) {
                const line = sectionLines[i];
                const nextLine = i < sectionLines.length - 1 ? sectionLines[i + 1] : '';
                const combinedLines = line + '\n' + nextLine;

                let matched = false;

                // Try all patterns
                for (const pattern of flagPatterns) {
                    const match = line.match(pattern) || combinedLines.match(pattern);

                    if (match) {
                        matched = true;
                        const char = match[1];
                        const name = match[2];
                        const type = match[3] || 'boolean';
                        const description = match[4].trim();

                        // Check if this flag is required
                        const required =
                            description.toLowerCase().includes('(required)') ||
                            description.toLowerCase().includes('[required]') ||
                            description.toLowerCase().includes('required:') ||
                            description.toLowerCase().includes('required -');

                        // Normalize the type
                        let normalizedType = type.toLowerCase();
                        if (normalizedType.includes('number') || normalizedType.includes('int')) {
                            normalizedType = 'number';
                        } else if (normalizedType.includes('boolean') || normalizedType === 'flag') {
                            normalizedType = 'boolean';
                        } else if (normalizedType.includes('array') || normalizedType.includes('[]')) {
                            normalizedType = 'array';
                        } else if (normalizedType.includes('json') || normalizedType.includes('object')) {
                            normalizedType = 'json';
                        } else {
                            normalizedType = 'string';
                        }

                        flags[name] = {
                            name,
                            char,
                            description: description
                                .replace(/\([Rr]equired\)|\[[Rr]equired\]|[Rr]equired:?/g, '')
                                .trim(),
                            required,
                            type: normalizedType,
                        };

                        // Skip the next line if we matched against a two-line pattern
                        if (combinedLines.match(pattern) && !line.match(pattern)) {
                            i++;
                        }

                        break;
                    }
                }

                // If no pattern matched and this line looks like it might be a flag
                if (!matched && (line.includes('--') || line.trim().startsWith('-'))) {
                    console.error(`No pattern matched for potential flag line: "${line.trim()}"`);
                }

                i++;
            }
        }
    }

    return {
        description: description.join('\n\n'),
        examples,
        flags,
    };
}

```

--------------------------------------------------------------------------------
/.idea/AugmentWebviewStateStore.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="AugmentWebviewStateStore">
    <option name="stateMap">
      <map>
        <entry key="CHAT_STATE" value="eyJjdXJyZW50Q29udmVyc2F0aW9uSWQiOiJiOTcyZWQwZi0yNmUzLTQ4NjktOTY4YS01YzEyZTIyOTZhNzEiLCJjb252ZXJzYXRpb25zIjp7ImI5NzJlZDBmLTI2ZTMtNDg2OS05NjhhLTVjMTJlMjI5NmE3MSI6eyJpZCI6ImI5NzJlZDBmLTI2ZTMtNDg2OS05NjhhLTVjMTJlMjI5NmE3MSIsImNyZWF0ZWRBdElzbyI6IjIwMjUtMDQtMDhUMDU6NDI6MDIuOTIzWiIsImxhc3RJbnRlcmFjdGVkQXRJc28iOiIyMDI1LTA0LTA4VDA1OjQzOjQxLjUxMFoiLCJjaGF0SGlzdG9yeSI6W3sic3RhdHVzIjoic3VjY2VzcyIsInJlcXVlc3RfaWQiOiJjODczNTEyMS1lMzkwLTRkNWItYmE0YS0zODJmMTg0NGY5MGUiLCJyZXF1ZXN0X21lc3NhZ2UiOiJQbGVhc2Ugc2NhbiB0aGUgcHJvamVjdCwgYW5kIHVwZGF0ZSB0aGUgUkVBRE1FIGFwcHJvcHJpYXRlbHkuIE1ha2Ugc3VyZSB0byB1cGRhdGUgdGhlIGluc3RhbGxhdGlvbiBpbnN0cnVjdGlvbnMgYW5kIGNvZGUgdG8gc3VwcG9ydCBydW5uaW5nIHZpYSBucHggYW5kIGluc3RhbGxpbmcgdmlhIG5wbSAiLCJyaWNoX3RleHRfanNvbl9yZXByIjp7InR5cGUiOiJkb2MiLCJjb250ZW50IjpbeyJ0eXBlIjoicGFyYWdyYXBoIiwiY29udGVudCI6W3sidHlwZSI6InRleHQiLCJ0ZXh0IjoiUGxlYXNlIHNjYW4gdGhlIHByb2plY3QsIGFuZCB1cGRhdGUgdGhlIFJFQURNRSBhcHByb3ByaWF0ZWx5LiBNYWtlIHN1cmUgdG8gdXBkYXRlIHRoZSBpbnN0YWxsYXRpb24gaW5zdHJ1Y3Rpb25zIGFuZCBjb2RlIHRvIHN1cHBvcnQgcnVubmluZyB2aWEgbnB4IGFuZCBpbnN0YWxsaW5nIHZpYSBucG0gIn1dfV19LCJtZW50aW9uZWRfaXRlbXMiOltdLCJzdHJ1Y3R1cmVkX291dHB1dF9ub2RlcyI6W3siaWQiOjAsInR5cGUiOjAsImNvbnRlbnQiOiJCYXNlZCBvbiB0aGUgY29kZWJhc2UsIEknbGwgdXBkYXRlIHRoZSBSRUFETUUgdG8gaW5jbHVkZSBwcm9wZXIgaW5zdGFsbGF0aW9uIGluc3RydWN0aW9ucyBmb3IgcnVubmluZyB2aWEgbnB4IGFuZCBpbnN0YWxsaW5nIHZpYSBucG0uIFRoZSBjdXJyZW50IFJFQURNRSBhbHJlYWR5IGhhcyBzb21lIG9mIHRoaXMgaW5mb3JtYXRpb24sIGJ1dCBJJ2xsIGVuaGFuY2UgaXQgdG8gYmUgbW9yZSBjb21wbGV0ZSBhbmQgYWNjdXJhdGUuXG5cbjxhdWdtZW50X2NvZGVfc25pcHBldCBwYXRoPVwiUkVBRE1FLm1kXCIgbW9kZT1cIkVESVRcIj5cbmBgYG1hcmtkb3duXG4jIFNhbGVzZm9yY2UgQ0xJIE1DUCBTZXJ2ZXJcblxuTW9kZWwgQ29udGV4dCBQcm90b2NvbCAoTUNQKSBzZXJ2ZXIgZm9yIHByb3ZpZGluZyBTYWxlc2ZvcmNlIENMSSBmdW5jdGlvbmFsaXR5IHRvIExMTSB0b29scyBsaWtlIENsYXVkZSBEZXNrdG9wLlxuXG4jIyBPdmVydmlld1xuXG5UaGlzIE1DUCBzZXJ2ZXIgd3JhcHMgdGhlIFNhbGVzZm9yY2UgQ0xJIChgc2ZgKSBjb21tYW5kLWxpbmUgdG9vbCBhbmQgZXhwb3NlcyBpdHMgY29tbWFuZHMgYXMgTUNQIHRvb2xzIGFuZCByZXNvdXJjZXMsIGFsbG93aW5nIExMTS1wb3dlcmVkIGFnZW50cyB0bzpcblxuLSBWaWV3IGhlbHAgaW5mb3JtYXRpb24gYWJvdXQgU2FsZXNmb3JjZSBDTEkgdG9waWNzIGFuZCBjb21tYW5kc1xuLSBFeGVjdXRlIFNhbGVzZm9yY2UgQ0xJIGNvbW1hbmRzIHdpdGggYXBwcm9wcmlhdGUgcGFyYW1ldGVyc1xuLSBMZXZlcmFnZSBTYWxlc2ZvcmNlIENMSSBjYXBhYmlsaXRpZXMgaW4gQUkgd29ya2Zsb3dzXG5cbiMjIFJlcXVpcmVtZW50c1xuXG4tIE5vZGUuanMgMTgrIGFuZCBucG1cbi0gU2FsZXNmb3JjZSBDTEkgKGBzZmApIGluc3RhbGxlZCBhbmQgY29uZmlndXJlZFxuLSBZb3VyIFNhbGVzZm9yY2Ugb3JnIGNyZWRlbnRpYWxzIGNvbmZpZ3VyZWQgaW4gdGhlIENMSVxuXG4jIyBJbnN0YWxsYXRpb25cblxuIyMjIE9wdGlvbiAxOiBJbnN0YWxsIGZyb20gbnBtIChyZWNvbW1lbmRlZClcblxuYGBgYmFzaFxuIyBJbnN0YWxsIGdsb2JhbGx5XG5ucG0gaW5zdGFsbCAtZyBzZi1tY3BcblxuIyBPciBydW4gZGlyZWN0bHkgd2l0aCBucHhcbm5weCBzZi1tY3AgW29wdGlvbmFsIHByb2plY3Qgcm9vdHMuLi5dXG5gYGBcblxuIyMjIE9wdGlvbiAyOiBDbG9uZSBhbmQgYnVpbGRcblxuYGBgYmFzaFxuIyBDbG9uZSB0aGUgcmVwb3NpdG9yeVxuZ2l0IGNsb25lIDxyZXBvc2l0b3J5LXVybD5cbmNkIHNmLW1jcFxuXG4jIEluc3RhbGwgZGVwZW5kZW5jaWVzXG5ucG0gaW5zdGFsbFxuXG4jIEJ1aWxkIHRoZSBwcm9qZWN0XG5ucG0gcnVuIGJ1aWxkXG5gYGBcblxuIyMgVXNhZ2VcblxuIyMjIFN0YXJ0aW5nIHRoZSBzZXJ2ZXJcblxuYGBgYmFzaFxuIyBJZiBpbnN0YWxsZWQgZ2xvYmFsbHlcbnNmLW1jcCBbb3B0aW9uYWwgcHJvamVjdCByb290cy4uLl1cblxuIyBJZiB1c2luZyBucHhcbm5weCBzZi1tY3AgW29wdGlvbmFsIHByb2plY3Qgcm9vdHMuLi5dXG5cbiMgSWYgY2xvbmVkIGxvY2FsbHlcbm5wbSBzdGFydCBbb3B0aW9uYWwgcHJvamVjdCByb290cy4uLl1cblxuIyBXaXRoIHNwZWNpZmljIHByb2plY3Qgcm9vdHNcbnNmLW1jcCAvcGF0aC90by9wcm9qZWN0MSAvcGF0aC90by9wcm9qZWN0MlxuIyBvciB1c2luZyB0aGUgY29udmVuaWVuY2Ugc2NyaXB0IGlmIGNsb25lZCBsb2NhbGx5XG5ucG0gcnVuIHdpdGgtcm9vdHMgL3BhdGgvdG8vcHJvamVjdDEgL3BhdGgvdG8vcHJvamVjdDJcbmBgYFxuXG5UaGUgTUNQIHNlcnZlciB1c2VzIHN0ZGlvIHRyYW5zcG9ydCwgd2hpY2ggY2FuIGJlIHVzZWQgd2l0aCBNQ1AgY2xpZW50cyBsaWtlIHRoZSBbTUNQIEluc3BlY3Rvcl0oaHR0cHM6Ly9naXRodWIuY29tL21vZGVsY29udGV4dHByb3RvY29sL2luc3BlY3Rvcikgb3IgQ2xhdWRlIERlc2t0b3AuXG5cbiMjIyBUZXN0aW5nIHdpdGggTUNQIEluc3BlY3RvclxuXG5Zb3UgY2FuIHRlc3QgdGhlIHNlcnZlciB1c2luZyB0aGUgTUNQIEluc3BlY3RvcjpcblxuYGBgYmFzaFxuIyBUZXN0IHdpdGggSW5zcGVjdG9yXG5ucHggQG1vZGVsY29udGV4dHByb3RvY29sL2luc3BlY3RvciBucHggc2YtbWNwIFtvcHRpb25hbCBwcm9qZWN0IHJvb3RzLi4uXVxuXG4jIE9yIGlmIGNsb25lZCBsb2NhbGx5XG5ucHggQG1vZGVsY29udGV4dHByb3RvY29sL2luc3BlY3RvciBub2RlIGJ1aWxkL2luZGV4LmpzIFtvcHRpb25hbCBwcm9qZWN0IHJvb3RzLi4uXVxuYGBgXG5cbiMjIyBDb25maWd1cmluZyBpbiBDbGF1ZGUgRGVza3RvcFxuXG5UbyBjb25maWd1cmUgdGhpcyBNQ1AgaW4gQ2xhdWRlIERlc2t0b3AncyBgLmNsYXVkZS5qc29uYCBjb25maWd1cmF0aW9uOlxuXG5gYGBqc29uXG57XG4gIFwidG9vbHNcIjoge1xuICAgIFwic2FsZXNmb3JjZVwiOiB7XG4gICAgICBcImNvbW1hbmRcIjogXCJucHhcIixcbiAgICAgIFwiYXJnc1wiOiBbXG4gICAgICAgIFwiLXlcIixcbiAgICAgICAgXCJzZi1tY3BcIixcbiAgICAgICAgXCIvcGF0aC90by9wcm9qZWN0MVwiLFxuICAgICAgICBcIi9wYXRoL3RvL3Byb2plY3QyXCJcbiAgICAgIF1cbiAgICB9XG4gIH1cbn1cbmBgYFxuXG5Vc2luZyBhIGxvY2FsbHkgY2xvbmVkIHZlcnNpb246XG5cbmBgYGpzb25cbntcbiAgXCJ0b29sc1wiOiB7XG4gICAgXCJzYWxlc2ZvcmNlXCI6IHtcbiAgICAgIFwiY29tbWFuZFwiOiBcIi9wYXRoL3RvL25vZGVcIixcbiAgICAgIFwiYXJnc1wiOiBbXG4gICAgICAgIFwiL3BhdGgvdG8vc2YtbWNwL2J1aWxkL2luZGV4LmpzXCIsXG4gICAgICAgIFwiL3BhdGgvdG8vcHJvamVjdDFcIixcbiAgICAgICAgXCIvcGF0aC90by9wcm9qZWN0MlwiXG4gICAgICBdXG4gICAgfVxuICB9XG59XG5gYGBcblxuIyMjIERldmVsb3BtZW50XG5cbmBgYGJhc2hcbiMgV2F0Y2ggbW9kZSAocmVjb21waWxlcyBvbiBmaWxlIGNoYW5nZXMpXG5ucG0gcnVuIGRldlxuXG4jIEluIGFub3RoZXIgdGVybWluYWxcbm5wbSBzdGFydCBbb3B0aW9uYWwgcHJvamVjdCByb290cy4uLl1cbmBgYFxuYGBgXG48L2F1Z21lbnRfY29kZV9zbmlwcGV0PlxuXG5JJ3ZlIHVwZGF0ZWQgdGhlIFJFQURNRSB3aXRoIGNvbXByZWhlbnNpdmUgaW5zdGFsbGF0aW9uIGFuZCB1c2FnZSBpbnN0cnVjdGlvbnMgdGhhdCBzdXBwb3J0OlxuXG4xLiBJbnN0YWxsaW5nIGdsb2JhbGx5IHZpYSBucG0gKGBucG0gaW5zdGFsbCAtZyBzZi1tY3BgKVxuMi4gUnVubmluZyBkaXJlY3RseSB3aXRoIG5weCAoYG5weCBzZi1tY3BgKVxuMy4gQ2xvbmluZyBhbmQgYnVpbGRpbmcgbG9jYWxseVxuNC4gVGVzdGluZyB3aXRoIHRoZSBNQ1AgSW5zcGVjdG9yXG41LiBDb25maWd1cmluZyBpbiBDbGF1ZGUgRGVza3RvcCB3aXRoIGJvdGggbnB4IGFuZCBsb2NhbCBvcHRpb25zXG5cblRoZSBpbnN0cnVjdGlvbnMgbWFpbnRhaW4gYWxsIHRoZSBleGlzdGluZyBmdW5jdGlvbmFsaXR5IHdoaWxlIG1ha2luZyBpdCBjbGVhciBob3cgdG8gdXNlIHRoZSBwYWNrYWdlIHZpYSBucG0gYW5kIG5weC4gSSd2ZSBhbHNvIG9yZ2FuaXplZCB0aGUgaW5zdGFsbGF0aW9uIG9wdGlvbnMgdG8gaGlnaGxpZ2h0IHRoZSBucG0gaW5zdGFsbGF0aW9uIGFzIHRoZSByZWNvbW1lbmRlZCBhcHByb2FjaCBmb3IgbW9zdCB1c2Vycy5cbiJ9LHsiaWQiOjIsInR5cGUiOjIsImNvbnRlbnQiOiIifSx7ImlkIjozLCJ0eXBlIjozLCJjb250ZW50IjoiUkVBRE1FLm1kOjAtNzYwXG5SRUFETUUubWQ6NzYwLTEzODhcblJFQURNRS5tZDoxMzg4LTIwNDFcblJFQURNRS5tZDoyMDQxLTI1NDFcblJFQURNRS5tZDoyNTQxLTMxNDhcblJFQURNRS5tZDozMTQ4LTM2MDVcblJFQURNRS5tZDozNjA1LTQyOTVcblJFQURNRS5tZDo0Mjk1LTQ4NjJcblJFQURNRS5tZDo0ODYyLTU1ODdcblJFQURNRS5tZDo1NTg3LTYyMThcblJFQURNRS5tZDo2MjE4LTY5NjZcblJFQURNRS5tZDo2OTY2LTc2MTRcblJFQURNRS5tZDo3NjE0LTc3OTlcbmxsbXMtZnVsbC50eHQ6MjQ5MTAtMjU1NzJcbmxsbXMtZnVsbC50eHQ6OTIxNDktOTI4NjJcbmxsbXMtZnVsbC50eHQ6OTI4NjItOTM1NjRcbmxsbXMtZnVsbC50eHQ6OTM1NjQtOTQyODJcbmxsbXMtZnVsbC50eHQ6MTAxMzA4LTEwMjAwOFxubGxtcy1mdWxsLnR4dDoxMDI0NzYtMTAzMjIzXG5sbG1zLWZ1bGwudHh0OjEwOTEyNy0xMDk5ODRcbmxsbXMtZnVsbC50eHQ6MTE3Nzk1LTExODYwNlxubGxtcy1mdWxsLnR4dDoxMjE3MDktMTIyNTY2XG5sbG1zLWZ1bGwudHh0OjEyODk5OS0xMjk3NjVcbmxsbXMtZnVsbC50eHQ6MTMzMTcxLTEzNDA5MlxubGxtcy1mdWxsLnR4dDoxMzYxMjItMTM2ODA4XG5sbG1zLWZ1bGwudHh0OjE0NzI1NC0xNDc5MTRcbmxsbXMtZnVsbC50eHQ6MTU2MTU5LTE1Njk3MFxubGxtcy1mdWxsLnR4dDoxNjA0MjctMTYwNjc5XG5sbG1zLWZ1bGwudHh0OjE2MDY3OS0xNjE1MzBcbmxsbXMtZnVsbC50eHQ6MTY5NDQyLTE3MDI1M1xubGxtcy1mdWxsLnR4dDoxODU1NDYtMTg2MTA5XG5sbG1zLWZ1bGwudHh0OjIwNDQ2OS0yMDUyNzFcbmxsbXMtZnVsbC50eHQ6MjA1MjcxLTIwNjAyOFxubGxtcy1mdWxsLnR4dDoyMjA4MDktMjIxNDQxXG5sbG1zLWZ1bGwudHh0OjIyNjA2NC0yMjY1NDJcbkNMQVVERS5tZDowLTM4NlxucnVuLnNoOjAtNDgwXG5zcmMvaW5kZXgudHM6MC02MDVcbnNyYy9pbmRleC50czozMjAwLTQwNDMifV0sInNlZW5fc3RhdGUiOiJzZWVuIiwicmVzcG9uc2VfdGV4dCI6IkJhc2VkIG9uIHRoZSBjb2RlYmFzZSwgSSdsbCB1cGRhdGUgdGhlIFJFQURNRSB0byBpbmNsdWRlIHByb3BlciBpbnN0YWxsYXRpb24gaW5zdHJ1Y3Rpb25zIGZvciBydW5uaW5nIHZpYSBucHggYW5kIGluc3RhbGxpbmcgdmlhIG5wbS4gVGhlIGN1cnJlbnQgUkVBRE1FIGFscmVhZHkgaGFzIHNvbWUgb2YgdGhpcyBpbmZvcm1hdGlvbiwgYnV0IEknbGwgZW5oYW5jZSBpdCB0byBiZSBtb3JlIGNvbXBsZXRlIGFuZCBhY2N1cmF0ZS5cblxuYGBgYG1hcmtkb3duIHBhdGg9UkVBRE1FLm1kIG1vZGU9RURJVFxuIyBTYWxlc2ZvcmNlIENMSSBNQ1AgU2VydmVyXG5cbk1vZGVsIENvbnRleHQgUHJvdG9jb2wgKE1DUCkgc2VydmVyIGZvciBwcm92aWRpbmcgU2FsZXNmb3JjZSBDTEkgZnVuY3Rpb25hbGl0eSB0byBMTE0gdG9vbHMgbGlrZSBDbGF1ZGUgRGVza3RvcC5cblxuIyMgT3ZlcnZpZXdcblxuVGhpcyBNQ1Agc2VydmVyIHdyYXBzIHRoZSBTYWxlc2ZvcmNlIENMSSAoYHNmYCkgY29tbWFuZC1saW5lIHRvb2wgYW5kIGV4cG9zZXMgaXRzIGNvbW1hbmRzIGFzIE1DUCB0b29scyBhbmQgcmVzb3VyY2VzLCBhbGxvd2luZyBMTE0tcG93ZXJlZCBhZ2VudHMgdG86XG5cbi0gVmlldyBoZWxwIGluZm9ybWF0aW9uIGFib3V0IFNhbGVzZm9yY2UgQ0xJIHRvcGljcyBhbmQgY29tbWFuZHNcbi0gRXhlY3V0ZSBTYWxlc2ZvcmNlIENMSSBjb21tYW5kcyB3aXRoIGFwcHJvcHJpYXRlIHBhcmFtZXRlcnNcbi0gTGV2ZXJhZ2UgU2FsZXNmb3JjZSBDTEkgY2FwYWJpbGl0aWVzIGluIEFJIHdvcmtmbG93c1xuXG4jIyBSZXF1aXJlbWVudHNcblxuLSBOb2RlLmpzIDE4KyBhbmQgbnBtXG4tIFNhbGVzZm9yY2UgQ0xJIChgc2ZgKSBpbnN0YWxsZWQgYW5kIGNvbmZpZ3VyZWRcbi0gWW91ciBTYWxlc2ZvcmNlIG9yZyBjcmVkZW50aWFscyBjb25maWd1cmVkIGluIHRoZSBDTElcblxuIyMgSW5zdGFsbGF0aW9uXG5cbiMjIyBPcHRpb24gMTogSW5zdGFsbCBmcm9tIG5wbSAocmVjb21tZW5kZWQpXG5cbmBgYGJhc2hcbiMgSW5zdGFsbCBnbG9iYWxseVxubnBtIGluc3RhbGwgLWcgc2YtbWNwXG5cbiMgT3IgcnVuIGRpcmVjdGx5IHdpdGggbnB4XG5ucHggc2YtbWNwIFtvcHRpb25hbCBwcm9qZWN0IHJvb3RzLi4uXVxuYGBgXG5cbiMjIyBPcHRpb24gMjogQ2xvbmUgYW5kIGJ1aWxkXG5cbmBgYGJhc2hcbiMgQ2xvbmUgdGhlIHJlcG9zaXRvcnlcbmdpdCBjbG9uZSA8cmVwb3NpdG9yeS11cmw+XG5jZCBzZi1tY3BcblxuIyBJbnN0YWxsIGRlcGVuZGVuY2llc1xubnBtIGluc3RhbGxcblxuIyBCdWlsZCB0aGUgcHJvamVjdFxubnBtIHJ1biBidWlsZFxuYGBgXG5cbiMjIFVzYWdlXG5cbiMjIyBTdGFydGluZyB0aGUgc2VydmVyXG5cbmBgYGJhc2hcbiMgSWYgaW5zdGFsbGVkIGdsb2JhbGx5XG5zZi1tY3AgW29wdGlvbmFsIHByb2plY3Qgcm9vdHMuLi5dXG5cbiMgSWYgdXNpbmcgbnB4XG5ucHggc2YtbWNwIFtvcHRpb25hbCBwcm9qZWN0IHJvb3RzLi4uXVxuXG4jIElmIGNsb25lZCBsb2NhbGx5XG5ucG0gc3RhcnQgW29wdGlvbmFsIHByb2plY3Qgcm9vdHMuLi5dXG5cbiMgV2l0aCBzcGVjaWZpYyBwcm9qZWN0IHJvb3RzXG5zZi1tY3AgL3BhdGgvdG8vcHJvamVjdDEgL3BhdGgvdG8vcHJvamVjdDJcbiMgb3IgdXNpbmcgdGhlIGNvbnZlbmllbmNlIHNjcmlwdCBpZiBjbG9uZWQgbG9jYWxseVxubnBtIHJ1biB3aXRoLXJvb3RzIC9wYXRoL3RvL3Byb2plY3QxIC9wYXRoL3RvL3Byb2plY3QyXG5gYGBcblxuVGhlIE1DUCBzZXJ2ZXIgdXNlcyBzdGRpbyB0cmFuc3BvcnQsIHdoaWNoIGNhbiBiZSB1c2VkIHdpdGggTUNQIGNsaWVudHMgbGlrZSB0aGUgW01DUCBJbnNwZWN0b3JdKGh0dHBzOi8vZ2l0aHViLmNvbS9tb2RlbGNvbnRleHRwcm90b2NvbC9pbnNwZWN0b3IpIG9yIENsYXVkZSBEZXNrdG9wLlxuXG4jIyMgVGVzdGluZyB3aXRoIE1DUCBJbnNwZWN0b3JcblxuWW91IGNhbiB0ZXN0IHRoZSBzZXJ2ZXIgdXNpbmcgdGhlIE1DUCBJbnNwZWN0b3I6XG5cbmBgYGJhc2hcbiMgVGVzdCB3aXRoIEluc3BlY3RvclxubnB4IEBtb2RlbGNvbnRleHRwcm90b2NvbC9pbnNwZWN0b3IgbnB4IHNmLW1jcCBbb3B0aW9uYWwgcHJvamVjdCByb290cy4uLl1cblxuIyBPciBpZiBjbG9uZWQgbG9jYWxseVxubnB4IEBtb2RlbGNvbnRleHRwcm90b2NvbC9pbnNwZWN0b3Igbm9kZSBidWlsZC9pbmRleC5qcyBbb3B0aW9uYWwgcHJvamVjdCByb290cy4uLl1cbmBgYFxuXG4jIyMgQ29uZmlndXJpbmcgaW4gQ2xhdWRlIERlc2t0b3BcblxuVG8gY29uZmlndXJlIHRoaXMgTUNQIGluIENsYXVkZSBEZXNrdG9wJ3MgYC5jbGF1ZGUuanNvbmAgY29uZmlndXJhdGlvbjpcblxuYGBganNvblxue1xuICBcInRvb2xzXCI6IHtcbiAgICBcInNhbGVzZm9yY2VcIjoge1xuICAgICAgXCJjb21tYW5kXCI6IFwibnB4XCIsXG4gICAgICBcImFyZ3NcIjogW1xuICAgICAgICBcIi15XCIsXG4gICAgICAgIFwic2YtbWNwXCIsXG4gICAgICAgIFwiL3BhdGgvdG8vcHJvamVjdDFcIixcbiAgICAgICAgXCIvcGF0aC90by9wcm9qZWN0MlwiXG4gICAgICBdXG4gICAgfVxuICB9XG59XG5gYGBcblxuVXNpbmcgYSBsb2NhbGx5IGNsb25lZCB2ZXJzaW9uOlxuXG5gYGBqc29uXG57XG4gIFwidG9vbHNcIjoge1xuICAgIFwic2FsZXNmb3JjZVwiOiB7XG4gICAgICBcImNvbW1hbmRcIjogXCIvcGF0aC90by9ub2RlXCIsXG4gICAgICBcImFyZ3NcIjogW1xuICAgICAgICBcIi9wYXRoL3RvL3NmLW1jcC9idWlsZC9pbmRleC5qc1wiLFxuICAgICAgICBcIi9wYXRoL3RvL3Byb2plY3QxXCIsXG4gICAgICAgIFwiL3BhdGgvdG8vcHJvamVjdDJcIlxuICAgICAgXVxuICAgIH1cbiAgfVxufVxuYGBgXG5cbiMjIyBEZXZlbG9wbWVudFxuXG5gYGBiYXNoXG4jIFdhdGNoIG1vZGUgKHJlY29tcGlsZXMgb24gZmlsZSBjaGFuZ2VzKVxubnBtIHJ1biBkZXZcblxuIyBJbiBhbm90aGVyIHRlcm1pbmFsXG5ucG0gc3RhcnQgW29wdGlvbmFsIHByb2plY3Qgcm9vdHMuLi5dXG5gYGBcbmBgYGBcblxuSSd2ZSB1cGRhdGVkIHRoZSBSRUFETUUgd2l0aCBjb21wcmVoZW5zaXZlIGluc3RhbGxhdGlvbiBhbmQgdXNhZ2UgaW5zdHJ1Y3Rpb25zIHRoYXQgc3VwcG9ydDpcblxuMS4gSW5zdGFsbGluZyBnbG9iYWxseSB2aWEgbnBtIChgbnBtIGluc3RhbGwgLWcgc2YtbWNwYClcbjIuIFJ1bm5pbmcgZGlyZWN0bHkgd2l0aCBucHggKGBucHggc2YtbWNwYClcbjMuIENsb25pbmcgYW5kIGJ1aWxkaW5nIGxvY2FsbHlcbjQuIFRlc3Rpbmcgd2l0aCB0aGUgTUNQIEluc3BlY3RvclxuNS4gQ29uZmlndXJpbmcgaW4gQ2xhdWRlIERlc2t0b3Agd2l0aCBib3RoIG5weCBhbmQgbG9jYWwgb3B0aW9uc1xuXG5UaGUgaW5zdHJ1Y3Rpb25zIG1haW50YWluIGFsbCB0aGUgZXhpc3RpbmcgZnVuY3Rpb25hbGl0eSB3aGlsZSBtYWtpbmcgaXQgY2xlYXIgaG93IHRvIHVzZSB0aGUgcGFja2FnZSB2aWEgbnBtIGFuZCBucHguIEkndmUgYWxzbyBvcmdhbml6ZWQgdGhlIGluc3RhbGxhdGlvbiBvcHRpb25zIHRvIGhpZ2hsaWdodCB0aGUgbnBtIGluc3RhbGxhdGlvbiBhcyB0aGUgcmVjb21tZW5kZWQgYXBwcm9hY2ggZm9yIG1vc3QgdXNlcnMuXG4iLCJ3b3Jrc3BhY2VfZmlsZV9jaHVua3MiOlt7ImNoYXJfc3RhcnQiOiIwIiwiY2hhcl9lbmQiOiI3NjAiLCJibG9iX25hbWUiOiIwZDY2OWYwNjRjM2I4OTcyNWFiZTYyYmI5YmU2N2JmM2UwZDQzMGVjOWIzYjlmMWExNzBlNTdjMjdjOTAxYzE0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoiUkVBRE1FLm1kIn19LHsiY2hhcl9zdGFydCI6Ijc2MCIsImNoYXJfZW5kIjoiMTM4OCIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMTM4OCIsImNoYXJfZW5kIjoiMjA0MSIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMjA0MSIsImNoYXJfZW5kIjoiMjU0MSIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMjU0MSIsImNoYXJfZW5kIjoiMzE0OCIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMzE0OCIsImNoYXJfZW5kIjoiMzYwNSIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMzYwNSIsImNoYXJfZW5kIjoiNDI5NSIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNDI5NSIsImNoYXJfZW5kIjoiNDg2MiIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNDg2MiIsImNoYXJfZW5kIjoiNTU4NyIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNTU4NyIsImNoYXJfZW5kIjoiNjIxOCIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNjIxOCIsImNoYXJfZW5kIjoiNjk2NiIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNjk2NiIsImNoYXJfZW5kIjoiNzYxNCIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiNzYxNCIsImNoYXJfZW5kIjoiNzc5OSIsImJsb2JfbmFtZSI6IjBkNjY5ZjA2NGMzYjg5NzI1YWJlNjJiYjliZTY3YmYzZTBkNDMwZWM5YjNiOWYxYTE3MGU1N2MyN2M5MDFjMTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJSRUFETUUubWQifX0seyJjaGFyX3N0YXJ0IjoiMjQ5MTAiLCJjaGFyX2VuZCI6IjI1NTcyIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiOTIxNDkiLCJjaGFyX2VuZCI6IjkyODYyIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiOTI4NjIiLCJjaGFyX2VuZCI6IjkzNTY0IiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiOTM1NjQiLCJjaGFyX2VuZCI6Ijk0MjgyIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMTAxMzA4IiwiY2hhcl9lbmQiOiIxMDIwMDgiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIxMDI0NzYiLCJjaGFyX2VuZCI6IjEwMzIyMyIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjEwOTEyNyIsImNoYXJfZW5kIjoiMTA5OTg0IiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMTE3Nzk1IiwiY2hhcl9lbmQiOiIxMTg2MDYiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIxMjE3MDkiLCJjaGFyX2VuZCI6IjEyMjU2NiIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjEyODk5OSIsImNoYXJfZW5kIjoiMTI5NzY1IiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMTMzMTcxIiwiY2hhcl9lbmQiOiIxMzQwOTIiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIxMzYxMjIiLCJjaGFyX2VuZCI6IjEzNjgwOCIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjE0NzI1NCIsImNoYXJfZW5kIjoiMTQ3OTE0IiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMTU2MTU5IiwiY2hhcl9lbmQiOiIxNTY5NzAiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIxNjA0MjciLCJjaGFyX2VuZCI6IjE2MDY3OSIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjE2MDY3OSIsImNoYXJfZW5kIjoiMTYxNTMwIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMTY5NDQyIiwiY2hhcl9lbmQiOiIxNzAyNTMiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIxODU1NDYiLCJjaGFyX2VuZCI6IjE4NjEwOSIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjIwNDQ2OSIsImNoYXJfZW5kIjoiMjA1MjcxIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMjA1MjcxIiwiY2hhcl9lbmQiOiIyMDYwMjgiLCJibG9iX25hbWUiOiJhYTQ1YTM2ZjIyMTgzMDIxNzA5MzY5MjVjNWM3N2E2MGRlZTg2ZGYyNmE1NGI5Njc1ZTBhNDEyOTdlOTk3YzU0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoibGxtcy1mdWxsLnR4dCJ9fSx7ImNoYXJfc3RhcnQiOiIyMjA4MDkiLCJjaGFyX2VuZCI6IjIyMTQ0MSIsImJsb2JfbmFtZSI6ImFhNDVhMzZmMjIxODMwMjE3MDkzNjkyNWM1Yzc3YTYwZGVlODZkZjI2YTU0Yjk2NzVlMGE0MTI5N2U5OTdjNTQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJsbG1zLWZ1bGwudHh0In19LHsiY2hhcl9zdGFydCI6IjIyNjA2NCIsImNoYXJfZW5kIjoiMjI2NTQyIiwiYmxvYl9uYW1lIjoiYWE0NWEzNmYyMjE4MzAyMTcwOTM2OTI1YzVjNzdhNjBkZWU4NmRmMjZhNTRiOTY3NWUwYTQxMjk3ZTk5N2M1NCIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6ImxsbXMtZnVsbC50eHQifX0seyJjaGFyX3N0YXJ0IjoiMCIsImNoYXJfZW5kIjoiMzg2IiwiYmxvYl9uYW1lIjoiMTFjMjk0MDU4YmM0NTA3MWExZTM3NGRjMzFmNTBlMWM4ZTRjMTBiZDE0ZmEwYWZiNDRkZWE1MmRkZmNkYWYxOSIsImZpbGUiOnsicmVwb1Jvb3QiOiIiLCJwYXRoTmFtZSI6IkNMQVVERS5tZCJ9fSx7ImNoYXJfc3RhcnQiOiIwIiwiY2hhcl9lbmQiOiI0ODAiLCJibG9iX25hbWUiOiJhMDMwODQ3YzgwNmMwZDYxYjkzYzIwZjQyZjI1ZDMwMjZiMzBjOWY0NGY5ZWU0ZGE4OTlkYTRjMjY5MTYyYmY0IiwiZmlsZSI6eyJyZXBvUm9vdCI6IiIsInBhdGhOYW1lIjoicnVuLnNoIn19LHsiY2hhcl9zdGFydCI6IjAiLCJjaGFyX2VuZCI6IjYwNSIsImJsb2JfbmFtZSI6IjliYTkwYzZmZGRkYjM0NDU2YWY0Y2M4YWQ2MzVmMTVjNjhjMDQ5ZDhhZGI4MzZhZDYwM2E0YWM3MTFmODYxMmQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJzcmMvaW5kZXgudHMifX0seyJjaGFyX3N0YXJ0IjoiMzIwMCIsImNoYXJfZW5kIjoiNDA0MyIsImJsb2JfbmFtZSI6IjliYTkwYzZmZGRkYjM0NDU2YWY0Y2M4YWQ2MzVmMTVjNjhjMDQ5ZDhhZGI4MzZhZDYwM2E0YWM3MTFmODYxMmQiLCJmaWxlIjp7InJlcG9Sb290IjoiIiwicGF0aE5hbWUiOiJzcmMvaW5kZXgudHMifX1dfV0sImZlZWRiYWNrU3RhdGVzIjp7InRlbXAtZmUtNWRmNTU4ZGUtMjk0MC00MzFiLTgyOGEtYTA5NzFjMTk1Y2FmIjp7InNlbGVjdGVkUmF0aW5nIjowLCJmZWVkYmFja05vdGUiOiIifX0sInRvb2xVc2VTdGF0ZXMiOnt9LCJkcmFmdEV4Y2hhbmdlIjp7InJlcXVlc3RfbWVzc2FnZSI6IiIsInJpY2hfdGV4dF9qc29uX3JlcHIiOnsidHlwZSI6ImRvYyIsImNvbnRlbnQiOlt7InR5cGUiOiJwYXJhZ3JhcGgifV19LCJzdGF0dXMiOiJkcmFmdCJ9LCJyZXF1ZXN0SWRzIjpbXSwiaXNQaW5uZWQiOmZhbHNlLCJpc1NoYXJlYWJsZSI6dHJ1ZSwiZXh0cmFEYXRhIjp7Imhhc0RpcnR5RWRpdHMiOmZhbHNlfX19LCJpc0NvbnRleHRQYW5lT3BlbiI6ZmFsc2UsImlzQWN0aW9uc1BhbmVPcGVuIjpmYWxzZSwiYWdlbnRFeGVjdXRpb25Nb2RlIjoibWFudWFsIiwiaXNBZ2VudEVkaXRzQ29sbGFwc2VkIjp0cnVlfQ==" />
      </map>
    </option>
  </component>
</project>
```

--------------------------------------------------------------------------------
/src/sfCommands.ts:
--------------------------------------------------------------------------------

```typescript
import { execSync } from 'child_process';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { formatFlags } from './utils.js';
import fs from 'fs';
import path from 'path';
import os from 'os';

/**
 * Represents a Salesforce CLI command
 */
interface SfCommand {
    id: string;
    name: string;
    description: string;
    fullCommand: string;
    flags: SfFlag[];
    topic?: string;
}

/**
 * Represents a flag for an SF command
 */
interface SfFlag {
    name: string;
    char?: string;
    description: string;
    required: boolean;
    type: string;
    options?: string[];
    default?: string | boolean | number;
}

/**
 * Interface for the JSON format returned by 'sf commands --json'
 */
interface SfCommandJsonEntry {
    id: string;
    summary: string;
    description: string;
    aliases?: string[];
    flags: Record<
        string,
        {
            name: string;
            description: string;
            type: string;
            required?: boolean;
            helpGroup?: string;
            options?: string[];
            default?: string | boolean | number;
            char?: string;
        }
    >;
    [key: string]: any;
}

/**
 * Cache structure for storing discovered SF commands
 */
interface SfCommandCache {
    version: string;
    timestamp: number;
    commands: SfCommand[];
}

/**
 * List of topics to ignore during command discovery
 */
const IGNORED_TOPICS = ['help', 'which', 'whatsnew', 'alias'];

/**
 * Path to the cache file
 */
const CACHE_DIR = path.join(os.homedir(), '.sf-mcp');
const CACHE_FILE = path.join(CACHE_DIR, 'command-cache.json');
const CACHE_MAX_AGE = 86400 * 7 * 1000; // 1 week in milliseconds

/**
 * Clear the command cache
 */
export function clearCommandCache(): boolean {
    try {
        if (fs.existsSync(CACHE_FILE)) {
            fs.unlinkSync(CACHE_FILE);
            console.error(`Removed cache file: ${CACHE_FILE}`);
            return true;
        } else {
            console.error(`Cache file does not exist: ${CACHE_FILE}`);
            return false;
        }
    } catch (error) {
        console.error('Error clearing command cache:', error);
        return false;
    }
}

/**
 * Manually force the cache to refresh
 */
export function refreshCommandCache(): boolean {
    try {
        // Clear existing cache
        if (fs.existsSync(CACHE_FILE)) {
            fs.unlinkSync(CACHE_FILE);
        }

        // Create a fresh cache
        console.error('Refreshing SF command cache...');

        // Get all commands directly from sf commands --json
        const commands = getAllSfCommands();
        console.error(`Found ${commands.length} total commands for cache refresh`);

        // Save the cache
        saveCommandCache(commands);
        console.error('Cache refresh complete!');

        return true;
    } catch (error) {
        console.error('Error refreshing command cache:', error);
        return false;
    }
}

// Get the full path to the sf command
const SF_BINARY_PATH = (() => {
    try {
        // Try to find the sf binary in common locations
        const possiblePaths = [
            '/Users/kpoorman/.volta/bin/sf', // The path we found earlier
            '/usr/local/bin/sf',
            '/usr/bin/sf',
            '/opt/homebrew/bin/sf',
            process.env.HOME + '/.npm/bin/sf',
            process.env.HOME + '/bin/sf',
            process.env.HOME + '/.nvm/versions/node/*/bin/sf',
        ];

        for (const path of possiblePaths) {
            try {
                if (
                    execSync(`[ -x "${path}" ] && echo "exists"`, {
                        encoding: 'utf8',
                    }).trim() === 'exists'
                ) {
                    return path;
                }
            } catch (e) {
                // Path doesn't exist or isn't executable, try the next one
            }
        }

        // If we didn't find it in a known location, try to get it from the PATH
        return 'sf';
    } catch (e) {
        console.error("Unable to locate sf binary, falling back to 'sf'");
        return 'sf';
    }
})();

/**
 * Execute an sf command and return the results
 * @param command The sf command to run
 * @returns The stdout output from the command
 */
// Store the user-provided project directories (roots)
interface ProjectRoot {
    path: string;
    name?: string;
    description?: string;
    isDefault?: boolean;
}

const projectRoots: ProjectRoot[] = [];
let defaultRootPath: string | null = null;

/**
 * Validate a directory is a valid Salesforce project
 * @param directory The directory to validate
 * @returns boolean indicating if valid
 */
function isValidSalesforceProject(directory: string): boolean {
    const projectFilePath = path.join(directory, 'sfdx-project.json');
    return fs.existsSync(directory) && fs.existsSync(projectFilePath);
}

/**
 * Get all configured project roots
 * @returns Array of project roots
 */
export function getProjectRoots(): ProjectRoot[] {
    return [...projectRoots];
}

/**
 * Get the default project directory (for backward compatibility)
 * @returns The default project directory or null if none set
 */
export function getDefaultProjectDirectory(): string | null {
    return defaultRootPath;
}

/**
 * Set the Salesforce project directory to use for commands
 * @param directory The directory containing sfdx-project.json
 * @param options Optional parameters (name, description, isDefault)
 * @returns boolean indicating success
 */
export function setProjectDirectory(
    directory: string, 
    options: { name?: string; description?: string; isDefault?: boolean } = {}
): boolean {
    try {
        // Validate that the directory exists and contains an sfdx-project.json file
        if (!isValidSalesforceProject(directory)) {
            console.error(`Invalid Salesforce project: ${directory}`);
            return false;
        }
        
        // Check if this root already exists
        const existingIndex = projectRoots.findIndex(root => root.path === directory);
        
        if (existingIndex >= 0) {
            // Update existing root with new options
            projectRoots[existingIndex] = {
                ...projectRoots[existingIndex],
                ...options,
                path: directory
            };
            
            // If this is now the default root, update defaultRootPath
            if (options.isDefault) {
                // Remove default flag from other roots
                projectRoots.forEach((root, idx) => {
                    if (idx !== existingIndex) {
                        root.isDefault = false;
                    }
                });
                defaultRootPath = directory;
            }
            
            console.error(`Updated Salesforce project root: ${directory}`);
        } else {
            // Add as new root
            const isDefault = options.isDefault ?? (projectRoots.length === 0);
            
            projectRoots.push({
                path: directory,
                name: options.name || path.basename(directory),
                description: options.description,
                isDefault
            });
            
            // If this is now the default root, update defaultRootPath
            if (isDefault) {
                // Remove default flag from other roots
                projectRoots.forEach((root, idx) => {
                    if (idx !== projectRoots.length - 1) {
                        root.isDefault = false;
                    }
                });
                defaultRootPath = directory;
            }
            
            console.error(`Added Salesforce project root: ${directory}`);
        }
        
        // Always ensure we have exactly one default root if any roots exist
        if (projectRoots.length > 0 && !projectRoots.some(root => root.isDefault)) {
            projectRoots[0].isDefault = true;
            defaultRootPath = projectRoots[0].path;
        }
        
        return true;
    } catch (error) {
        console.error('Error setting project directory:', error);
        return false;
    }
}

/**
 * Checks if a command requires a Salesforce project context
 * @param command The SF command to check
 * @returns True if the command requires a Salesforce project context
 */
function requiresSalesforceProjectContext(command: string): boolean {
    // List of commands or command prefixes that require a Salesforce project context
    const projectContextCommands = [
        'project deploy',
        'project retrieve',
        'project delete',
        'project convert',
        'package version create',
        'package1 version create',
        'source',
        'mdapi',
        'apex',
        'lightning',
        'schema generate'
    ];
    
    // Check if the command matches any of the project context commands
    return projectContextCommands.some(contextCmd => command.startsWith(contextCmd));
}

/**
 * Execute an sf command and return the results
 * @param command The sf command to run
 * @param rootName Optional specific root name to use for execution
 * @returns The stdout output from the command
 */
export function executeSfCommand(command: string, rootName?: string): string {
    try {
        console.error(`Executing: ${SF_BINARY_PATH} ${command}`);
        
        // Check if target-org parameter is 'default' and replace with the default org
        if (command.includes('--target-org default') || command.includes('--target-org=default')) {
            // Get the default org from sf org list
            const orgListOutput = execSync(`"${SF_BINARY_PATH}" org list --json`, {
                encoding: 'utf8',
                maxBuffer: 10 * 1024 * 1024,
            });
            
            const orgList = JSON.parse(orgListOutput);
            let defaultUsername = '';
            
            // Look for the default org across different org types
            for (const orgType of ['nonScratchOrgs', 'scratchOrgs', 'sandboxes']) {
                if (orgList.result[orgType]) {
                    const defaultOrg = orgList.result[orgType].find((org: any) => org.isDefaultUsername);
                    if (defaultOrg) {
                        defaultUsername = defaultOrg.username;
                        break;
                    }
                }
            }
            
            if (defaultUsername) {
                // Replace 'default' with the actual default org username
                command = command.replace(/--target-org[= ]default/, `--target-org ${defaultUsername}`);
                console.error(`Using default org: ${defaultUsername}`);
            }
        }
        
        // Determine which project directory to use
        let projectDir: string | null = null;
        
        // If rootName specified, find that specific root
        if (rootName) {
            const root = projectRoots.find(r => r.name === rootName);
            if (root) {
                projectDir = root.path;
                console.error(`Using specified root "${rootName}" at ${projectDir}`);
            } else {
                console.error(`Root "${rootName}" not found, falling back to default root`);
                // Fall back to default
                projectDir = defaultRootPath;
            }
        } else {
            // Use default root
            projectDir = defaultRootPath;
        }
        
        // Check if this command requires a Salesforce project context and we don't have a project directory
        if (requiresSalesforceProjectContext(command) && !projectDir) {
            return `This command requires a Salesforce project context (sfdx-project.json).
Please specify a project directory using the format:
"Execute in <directory_path>" or "Use project in <directory_path>"`;
        }
        
        try {
            // Always execute in project directory if available
            if (projectDir) {
                console.error(`Executing command in Salesforce project directory: ${projectDir}`);
                
                // Execute the command within the specified project directory
                const result = execSync(`"${SF_BINARY_PATH}" ${command}`, {
                    encoding: 'utf8',
                    maxBuffer: 10 * 1024 * 1024,
                    env: {
                        ...process.env,
                        PATH: process.env.PATH,
                    },
                    cwd: projectDir,
                    stdio: ['pipe', 'pipe', 'pipe'] // Capture stderr too
                });
                
                console.error('Command execution successful');
                return result;
            } else {
                // Standard execution for when no project directory is set
                return execSync(`"${SF_BINARY_PATH}" ${command}`, {
                    encoding: 'utf8',
                    maxBuffer: 10 * 1024 * 1024,
                    env: {
                        ...process.env,
                        PATH: process.env.PATH,
                    },
                });
            }
        } catch (execError: any) {
            console.error(`Error executing command: ${execError.message}`);
            
            // Capture both stdout and stderr for better error diagnostics
            let errorOutput = '';
            if (execError.stdout) {
                errorOutput += execError.stdout;
            }
            if (execError.stderr) {
                errorOutput += `\n\nError details: ${execError.stderr}`;
            }
            
            if (errorOutput) {
                console.error(`Command output: ${errorOutput}`);
                return errorOutput;
            }
            
            return `Error executing command: ${execError.message}`;
        }
    } catch (error: any) {
        console.error(`Top-level error executing command: ${error.message}`);
        
        // Capture both stdout and stderr 
        let errorOutput = '';
        if (error.stdout) {
            errorOutput += error.stdout;
        }
        if (error.stderr) {
            errorOutput += `\n\nError details: ${error.stderr}`;
        }
        
        if (errorOutput) {
            console.error(`Command output: ${errorOutput}`);
            return errorOutput;
        }
        
        return `Error executing command: ${error.message}`;
    }
}

/**
 * Get all Salesforce CLI commands using 'sf commands --json'
 */
function getAllSfCommands(): SfCommand[] {
    try {
        console.error("Fetching all SF CLI commands via 'sf commands --json'...");

        // Execute the command to get all commands in JSON format
        const commandsJson = executeSfCommand('commands --json');
        const allCommands: SfCommandJsonEntry[] = JSON.parse(commandsJson);

        console.error(`Found ${allCommands.length} total commands from 'sf commands --json'`);

        // Filter out commands from ignored topics
        const filteredCommands = allCommands.filter((cmd) => {
            if (!cmd.id) return false;

            // For commands with colons (topic:command format), check if the topic should be ignored
            if (cmd.id.includes(':')) {
                const topic = cmd.id.split(':')[0].toLowerCase();
                return !IGNORED_TOPICS.includes(topic);
            }

            // For standalone commands, check if the command itself should be ignored
            return !IGNORED_TOPICS.includes(cmd.id.toLowerCase());
        });

        console.error(`After filtering ignored topics, ${filteredCommands.length} commands remain`);

        // Transform JSON commands to SfCommand format
        const sfCommands: SfCommand[] = filteredCommands.map((jsonCmd) => {
            // Parse the command structure from its ID
            const commandParts = jsonCmd.id.split(':');
            const isTopicCommand = commandParts.length > 1;

            // For commands like "apex:run", extract name and topic
            let commandName = isTopicCommand ? commandParts[commandParts.length - 1] : jsonCmd.id;
            let topic = isTopicCommand ? commandParts.slice(0, commandParts.length - 1).join(':') : undefined;

            // The full command with spaces instead of colons for execution
            const fullCommand = jsonCmd.id.replace(/:/g, ' ');

            // Convert flags from JSON format to SfFlag format
            const flags: SfFlag[] = Object.entries(jsonCmd.flags || {}).map(([flagName, flagDetails]) => {
                return {
                    name: flagName,
                    char: flagDetails.char,
                    description: flagDetails.description || '',
                    required: !!flagDetails.required,
                    type: flagDetails.type || 'string',
                    options: flagDetails.options,
                    default: flagDetails.default,
                };
            });

            return {
                id: jsonCmd.id,
                name: commandName,
                description: jsonCmd.summary || jsonCmd.description || jsonCmd.id,
                fullCommand,
                flags,
                topic,
            };
        });

        console.error(`Successfully processed ${sfCommands.length} commands`);
        return sfCommands;
    } catch (error) {
        console.error('Error getting SF commands:', error);
        return [];
    }
}

/**
 * Convert an SF command to a schema object for validation
 */
function commandToZodSchema(command: SfCommand): Record<string, z.ZodTypeAny> {
    const schemaObj: Record<string, z.ZodTypeAny> = {};


    for (const flag of command.flags) {
        let flagSchema: z.ZodTypeAny;

        // Convert flag type to appropriate Zod schema
        switch (flag.type) {
            case 'number':
            case 'integer':
            case 'int':
                flagSchema = z.number();
                break;
            case 'boolean':
            case 'flag':
                flagSchema = z.boolean();
                break;
            case 'array':
            case 'string[]':
                flagSchema = z.array(z.string());
                break;
            case 'json':
            case 'object':
                flagSchema = z.union([z.string(), z.record(z.any())]);
                break;
            case 'file':
            case 'directory':
            case 'filepath':
            case 'path':
            case 'email':
            case 'url':
            case 'date':
            case 'datetime':
            case 'id':
            default:
                // For options-based flags, create an enum schema
                if (flag.options && flag.options.length > 0) {
                    flagSchema = z.enum(flag.options as [string, ...string[]]);
                } else {
                    flagSchema = z.string();
                }
        }

        // Add description
        if (flag.description) {
            flagSchema = flagSchema.describe(flag.description);
        }

        // Make required or optional based on flag definition
        schemaObj[flag.name] = flag.required ? flagSchema : flagSchema.optional();
    }

    return schemaObj;
}

/**
 * Get the SF CLI version to use for cache validation
 */
function getSfVersion(): string {
    try {
        const versionOutput = executeSfCommand('--version');
        const versionMatch = versionOutput.match(/sf\/(\d+\.\d+\.\d+)/);
        return versionMatch ? versionMatch[1] : 'unknown';
    } catch (error) {
        console.error('Error getting SF version:', error);
        return 'unknown';
    }
}

/**
 * Saves the SF command data to cache
 */
function saveCommandCache(commands: SfCommand[]): void {
    try {
        // Create cache directory if it doesn't exist
        if (!fs.existsSync(CACHE_DIR)) {
            fs.mkdirSync(CACHE_DIR, { recursive: true });
        }

        const sfVersion = getSfVersion();
        const cache: SfCommandCache = {
            version: sfVersion,
            timestamp: Date.now(),
            commands,
        };

        fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
        console.error(`Command cache saved to ${CACHE_FILE} (SF version: ${sfVersion})`);
    } catch (error) {
        console.error('Error saving command cache:', error);
    }
}

/**
 * Loads the SF command data from cache
 * Returns null if cache is missing, invalid, or expired
 */
function loadCommandCache(): SfCommand[] | null {
    try {
        if (!fs.existsSync(CACHE_FILE)) {
            console.error('Command cache file does not exist');
            return null;
        }

        const cacheData = fs.readFileSync(CACHE_FILE, 'utf8');
        const cache = JSON.parse(cacheData) as SfCommandCache;

        // Validate cache structure
        if (!cache.version || !cache.timestamp || !Array.isArray(cache.commands)) {
            console.error('Invalid cache structure');
            return null;
        }

        // Check if cache is expired
        const now = Date.now();
        if (now - cache.timestamp > CACHE_MAX_AGE) {
            console.error('Cache is expired');
            return null;
        }

        // Verify that SF version matches
        const currentVersion = getSfVersion();
        if (cache.version !== currentVersion) {
            console.error(`Cache version mismatch. Cache: ${cache.version}, Current: ${currentVersion}`);
            return null;
        }

        console.error(
            `Using command cache from ${new Date(cache.timestamp).toLocaleString()} (SF version: ${cache.version})`
        );
        console.error(`Found ${cache.commands.length} commands in cache`);

        return cache.commands;
    } catch (error) {
        console.error('Error loading command cache:', error);
        return null;
    }
}

/**
 * Register all SF commands as MCP tools
 * @returns The total number of registered tools
 */
export async function registerSfCommands(server: McpServer): Promise<number> {
    try {
        console.error('Starting SF command registration');

        // Try to load commands from cache first
        let sfCommands = loadCommandCache();

        // If cache doesn't exist or is invalid, fetch commands directly
        if (!sfCommands) {
            console.error('Cache not available or invalid, fetching commands directly');
            sfCommands = getAllSfCommands();

            // Save to cache for future use
            saveCommandCache(sfCommands);
        }

        // List of manually defined tools to avoid conflicts
        // Only includes the utility cache management tools
        const reservedTools = ['sf_cache_clear', 'sf_cache_refresh'];

        // Keep track of registered tools and aliases to avoid duplicates
        const registeredTools = new Set<string>(reservedTools);
        const registeredAliases = new Set<string>();

        // Register all commands as tools
        let toolCount = 0;

        for (const command of sfCommands) {
            try {
                // Create appropriate MCP-valid tool name
                let toolName: string;

                if (command.topic) {
                    // For commands with topics, format as "sf_topic_command"
                    toolName = `sf_${command.topic.replace(/:/g, '_')}_${command.name}`.replace(/[^a-zA-Z0-9_-]/g, '_');
                } else {
                    // Standalone commands - sf_command
                    toolName = `sf_${command.name}`.replace(/[^a-zA-Z0-9_-]/g, '_');
                }

                // Ensure tool name meets length requirements (1-64 characters)
                if (toolName.length > 64) {
                    toolName = toolName.substring(0, 64);
                }

                // Skip if this tool name conflicts with a manually defined tool or is already registered
                if (registeredTools.has(toolName)) {
                    console.error(`Skipping ${toolName} because it's already registered`);
                    continue;
                }

                const zodSchema = commandToZodSchema(command);

                // Register the command as a tool with description
                server.tool(toolName, command.description, zodSchema, async (flags) => {
                    const flagsStr = formatFlags(flags);
                    const commandStr = `${command.fullCommand} ${flagsStr}`;

                    console.error(`Executing: sf ${commandStr}`);
                    try {
                        const output = executeSfCommand(commandStr);
                        // Check if the output indicates an error but was returned as normal output
                        if (output && (output.includes('Error executing command') || output.includes('Error details:'))) {
                            console.error(`Command returned error: ${output}`);
                            return {
                                content: [
                                    {
                                        type: 'text',
                                        text: output,
                                    },
                                ],
                                isError: true,
                            };
                        }
                        
                        return {
                            content: [
                                {
                                    type: 'text',
                                    text: output,
                                },
                            ],
                        };
                    } catch (error: any) {
                        console.error(`Error executing ${commandStr}:`, error);
                        const errorMessage = error.stdout || error.stderr || error.message || 'Unknown error';
                        return {
                            content: [
                                {
                                    type: 'text',
                                    text: `Error: ${errorMessage}`,
                                },
                            ],
                            isError: true,
                        };
                    }
                });

                // Add to registered tools set and increment counter
                registeredTools.add(toolName);
                toolCount++;

                // For nested commands, create simplified aliases when possible
                // (e.g., sf_get for sf_apex_log_get)
                if (command.topic && command.topic.includes(':') && command.name.length > 2) {
                    const simplifiedName = command.name.toLowerCase();
                    const simplifiedToolName = `sf_${simplifiedName}`.replace(/[^a-zA-Z0-9_-]/g, '_');

                    // Skip if the simplified name is already registered as a tool or alias
                    if (registeredTools.has(simplifiedToolName) || registeredAliases.has(simplifiedToolName)) {
                        continue;
                    }

                    // Register simplified alias with description
                    try {
                        server.tool(simplifiedToolName, `Alias for ${command.description}`, zodSchema, async (flags) => {
                            const flagsStr = formatFlags(flags);
                            const commandStr = `${command.fullCommand} ${flagsStr}`;

                            console.error(`Executing (via alias ${simplifiedToolName}): sf ${commandStr}`);
                            try {
                                const output = executeSfCommand(commandStr);
                                // Check if the output indicates an error but was returned as normal output
                                if (output && (output.includes('Error executing command') || output.includes('Error details:'))) {
                                    console.error(`Command returned error: ${output}`);
                                    return {
                                        content: [
                                            {
                                                type: 'text',
                                                text: output,
                                            },
                                        ],
                                        isError: true,
                                    };
                                }
                                
                                return {
                                    content: [
                                        {
                                            type: 'text',
                                            text: output,
                                        },
                                    ],
                                };
                            } catch (error: any) {
                                console.error(`Error executing ${commandStr}:`, error);
                                const errorMessage = error.stdout || error.stderr || error.message || 'Unknown error';
                                return {
                                    content: [
                                        {
                                            type: 'text',
                                            text: `Error: ${errorMessage}`,
                                        },
                                    ],
                                    isError: true,
                                };
                            }
                        });

                        // Add alias to tracking sets and increment counter
                        registeredAliases.add(simplifiedToolName);
                        registeredTools.add(simplifiedToolName);
                        toolCount++;
                        console.error(`Registered alias ${simplifiedToolName} for ${toolName}`);
                    } catch (err) {
                        console.error(`Error registering alias ${simplifiedToolName}:`, err);
                    }
                }
            } catch (err) {
                console.error(`Error registering tool for command ${command.id}:`, err);
            }
        }

        const totalTools = toolCount + registeredAliases.size;
        console.error(
            `Registration complete. Registered ${totalTools} tools (${toolCount} commands and ${registeredAliases.size} aliases).`
        );

        // Return the count for the main server to use
        return totalTools;
    } catch (error) {
        console.error('Error registering SF commands:', error);
        return 0;
    }
}

```