Files
freecad-robust-mcp-fc111/just/install.just
T
8c338f6da7 feat: MCP Bridge Workbench, just command cleanup, testing, etc. (#24)
* fix: lots of fixes and name refactoring

* feat: Add workbench preferences

* fix: MCP bridge status widget and just command fixes

* fix(tests): Use the correct mesa-glx package

* fix(ci): Add fontconfig to GUI test dependencies

FreeCAD GUI was failing to start with:
"Fontconfig error: Cannot load default config file: No such file"

Added fontconfig and fonts-dejavu-core packages to the GUI test job
dependencies to resolve the font configuration issue.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(addon): Extract path utilities into shared module

Create path_utils.py module that consolidates duplicated path-finding
logic from commands.py and InitGui.py:
- get_addon_path(): Find addon directory with caching and fallbacks
- get_icon_path(): Get full path to an icon file
- get_icons_dir(): Get path to icons directory
- get_workbench_icon(): Get path to workbench main icon

This removes ~100 lines of duplicated code while preserving the same
behavior including _addon_path_cache and all fallback methods.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(addon): Prevent stale plugin state on startup failure

The StartMCPBridgeCommand.Activated method could leave _mcp_plugin in
a partially initialized state if FreecadMCPPlugin.start() failed after
the plugin was instantiated.

Changes:
- Create plugin in a local variable first
- Only assign to _mcp_plugin after start() succeeds
- Explicitly clear _mcp_plugin and _running_config in exception
  handlers to ensure clean state for subsequent retry attempts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Lot of broad improvements

* fix(ci): Use blocking headless_server.py for GUI tests

The GUI test was using startup_bridge.py which is non-blocking
(designed for interactive use). For CI, even in GUI mode, we need
the blocking headless_server.py that calls run_forever() to keep
FreeCAD running. GUI features are still available since we use
the 'freecad' executable instead of 'freecadcmd'.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(addon): Rename headless_server.py to blocking_bridge.py

The old name was misleading because:
- It works with both GUI (freecad) and headless (freecadcmd) modes
- The key characteristic is that it BLOCKS with run_forever()

New naming convention clarifies the difference:
- blocking_bridge.py: Starts bridge and blocks (for CI, servers)
- startup_bridge.py: Starts bridge and returns (for interactive GUI)

Updated all references across:
- GitHub workflow (macro-test.yaml)
- Just commands (freecad.just)
- Unit tests (test_addon_structure.py)
- Documentation (5 files)
- CLAUDE.md

Also improved the script to detect GUI mode dynamically using
FreeCAD.GuiUp and display the appropriate status message.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(just): Remove erroneous rm of startup_bridge.py on error

The startup script is now a permanent source file in the repository,
not a generated temporary file. The rm -f would have deleted source
code if FreeCAD wasn't found.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: General improvements

* fix: Lots of general fixes and only stable to PyPi

* fix: small cleanup

* fix: Small fixes and hopefully fixes the GUI tests

* fix: Add proper library paths for FreeCAD GUI in CI

- Create wrapper scripts instead of symlinks for AppImage binaries
- Set LD_LIBRARY_PATH, QT_PLUGIN_PATH for GUI mode
- Add diagnostic output to identify startup failures

* fix: Use apprun for GUI tests in CI

* fix: Improving Xvfb tests

* fix: GUI tests worlk

* chore: remove invalid --no-splash comments

* fix: ARM64 architecture support and other fixes

* fix: cleanup

* test: just commands test suite

* test: improve just command tests

* fix: more general improvements

* fix: more cleanup

* fix: more updates

* fix: small tweaks

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 15:26:33 -08:00

605 lines
22 KiB
Plaintext

# Installation commands for users
# Usage: just install::mcp-server, just install::mcp-bridge-workbench, etc.
#
# This module installs components for end users:
# - Robust MCP Server (as a uv tool, available system-wide)
# - Robust MCP Bridge Workbench (FreeCAD addon)
# - FreeCAD Macros (CutObjectForMagnets, MultiExport)
#
# For developer setup (Python dependencies in virtualenv), use: just dev::install-deps
# Project root directory (justfile_directory() returns the main justfile's directory)
project_root := justfile_directory()
# =============================================================================
# Helper: FreeCAD Directory Detection
# =============================================================================
# This function sets MOD_DIR and MACRO_DIR based on the current OS.
# Source it at the start of any recipe that needs FreeCAD paths.
#
# Usage in recipes:
# eval "$(just install::_freecad-dirs)"
# echo "Mod directory: $MOD_DIR"
# echo "Macro directory: $MACRO_DIR"
# Private recipe that outputs shell code to set FreeCAD directories
# FreeCAD 1.x uses versioned directories (v1-1, v1-2, etc.) for user data.
# This helper detects the latest versioned directory if present.
[private]
_freecad-dirs:
#!/usr/bin/env bash
cat << 'DIRS_EOF'
# Determine base FreeCAD directory based on OS
if [[ "$OSTYPE" == "darwin"* ]]; then
FREECAD_BASE="$HOME/Library/Application Support/FreeCAD"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
FREECAD_BASE="$HOME/.local/share/FreeCAD"
else
# Windows: validate APPDATA or use fallback
if [[ -n "$APPDATA" ]]; then
FREECAD_BASE="$APPDATA/FreeCAD"
elif [[ -n "$HOME" ]]; then
FREECAD_BASE="$HOME/AppData/Roaming/FreeCAD"
echo "Warning: APPDATA not set, using fallback: $FREECAD_BASE" >&2
else
echo "Error: Neither APPDATA nor HOME is set. Cannot determine FreeCAD directory." >&2
exit 1
fi
fi
# FreeCAD 1.x+ uses versioned directories (v1-1, v1-2, v2-0, etc.)
# Find the latest versioned directory if present
VERSIONED_DIR=""
if [[ -d "$FREECAD_BASE" ]]; then
# Find directories matching v*-* pattern (supports v1-*, v2-*, etc.)
# Use sort -t- -k1.2 -k2 -n to sort by major then minor version
LATEST_VERSION=$(ls -d "$FREECAD_BASE"/v*-* 2>/dev/null | sort -t- -k1.2 -k2 -n | tail -n 1)
if [[ -n "$LATEST_VERSION" && -d "$LATEST_VERSION" ]]; then
VERSIONED_DIR="$LATEST_VERSION"
fi
fi
# Use versioned directory if found, otherwise use base directory
if [[ -n "$VERSIONED_DIR" ]]; then
MOD_DIR="$VERSIONED_DIR/Mod"
MACRO_DIR="$VERSIONED_DIR/Macro"
echo "Note: Using FreeCAD versioned directory: $VERSIONED_DIR" >&2
else
MOD_DIR="$FREECAD_BASE/Mod"
MACRO_DIR="$FREECAD_BASE/Macro"
fi
DIRS_EOF
# =============================================================================
# Robust MCP Server Installation
# =============================================================================
# Install the Robust MCP Server as a user tool (available system-wide via uv)
# Uses cached builds for faster installation. For development with uncommitted
# changes, use mcp-server-clean instead.
mcp-server:
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="{{project_root}}"
echo "Installing Robust MCP Server as a uv tool..."
echo ""
# Install from the local project directory
# --force handles reinstallation automatically, no need to uninstall first
uv tool install --force "$PROJECT_DIR"
echo ""
echo "=========================================="
echo "Robust MCP Server installed!"
echo "=========================================="
echo ""
echo "The 'freecad-mcp' command is now available system-wide."
echo ""
echo "To run:"
echo " freecad-mcp # stdio mode (for Claude Code)"
echo " freecad-mcp --help # show all options"
echo ""
echo "To configure Claude Code, add to your MCP settings:"
echo ' "freecad": {'
echo ' "command": "freecad-mcp"'
echo ' }'
echo ""
echo "Note: If you have uncommitted local changes, use 'just install::mcp-server-clean'"
echo ""
# Install with cache clearing (for development with uncommitted changes)
# Clears uv cache first to ensure the build picks up all local changes.
mcp-server-clean:
#!/usr/bin/env bash
set -euo pipefail
echo "Clearing uv cache for fresh build..."
uv cache clean --force 2>/dev/null || true
just install::mcp-server
# Uninstall the Robust MCP Server tool
uninstall-mcp-server:
#!/usr/bin/env bash
set -euo pipefail
echo "Uninstalling Robust MCP Server..."
uv tool uninstall freecad-robust-mcp || echo "Robust MCP Server was not installed as a uv tool"
# =============================================================================
# Robust MCP Bridge Workbench Installation
# =============================================================================
# Install the FreeCAD Robust MCP workbench addon to FreeCAD's Mod directory
mcp-bridge-workbench:
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="{{project_root}}"
ADDON_NAME="FreecadRobustMCP"
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
ADDON_DEST="$MOD_DIR/$ADDON_NAME"
# Create Mod directory if it doesn't exist
mkdir -p "$MOD_DIR"
# Remove existing installation if present
if [[ -d "$ADDON_DEST" ]]; then
echo "Removing existing installation at: $ADDON_DEST"
rm -rf "$ADDON_DEST"
fi
# Verify source exists before copying
ADDON_SRC="${PROJECT_DIR}/addon/$ADDON_NAME"
if [[ ! -d "$ADDON_SRC" ]]; then
echo "Error: Addon source directory not found: $ADDON_SRC" >&2
exit 1
fi
# Copy the addon directory
cp -r "$ADDON_SRC" "$ADDON_DEST"
# Generate package.xml for the workbench from root package.xml
# FreeCAD requires package.xml in the addon directory for proper workbench detection
ROOT_PACKAGE_XML="${PROJECT_DIR}/package.xml"
if [[ -f "$ROOT_PACKAGE_XML" ]]; then
echo "Generating package.xml from root package.xml..."
export ROOT_PACKAGE_XML ADDON_DEST
python3 << 'PYEOF'
import xml.etree.ElementTree as ET
import sys
import os
try:
root_pkg = os.environ.get('ROOT_PACKAGE_XML', '')
addon_dest = os.environ.get('ADDON_DEST', '')
tree = ET.parse(root_pkg)
root = tree.getroot()
ns = {'pkg': 'https://wiki.freecad.org/Package_Metadata'}
# Find the workbench content element
workbench = root.find('.//pkg:content/pkg:workbench', ns)
if workbench is None:
print("Warning: No workbench found in root package.xml", file=sys.stderr)
sys.exit(0)
# Extract workbench metadata
wb_name = workbench.find('pkg:name', ns)
wb_version = workbench.find('pkg:version', ns)
wb_date = workbench.find('pkg:date', ns)
wb_description = workbench.find('pkg:description', ns)
wb_classname = workbench.find('pkg:classname', ns)
wb_icon = workbench.find('pkg:icon', ns)
wb_freecadmin = workbench.find('pkg:freecadmin', ns)
# Get maintainer and license from root
maintainer = root.find('pkg:maintainer', ns)
license_el = root.find('pkg:license', ns)
repo_url = root.find('pkg:url[@type="repository"]', ns)
readme_url = root.find('pkg:url[@type="readme"]', ns)
# Create standalone package.xml
standalone = ET.Element('package', {
'format': '1',
'xmlns': 'https://wiki.freecad.org/Package_Metadata'
})
# Add metadata
name_text = wb_name.text if wb_name is not None else 'Robust MCP Bridge'
ET.SubElement(standalone, 'name').text = name_text
desc_text = wb_description.text if wb_description is not None else 'MCP Bridge for FreeCAD'
ET.SubElement(standalone, 'description').text = desc_text
ver_text = wb_version.text if wb_version is not None else '0.0.0'
ET.SubElement(standalone, 'version').text = ver_text
# Fall back to today's date if not specified
from datetime import date
date_text = wb_date.text if wb_date is not None else date.today().isoformat()
ET.SubElement(standalone, 'date').text = date_text
if maintainer is not None:
m = ET.SubElement(standalone, 'maintainer')
m.text = maintainer.text
if maintainer.get('email'):
m.set('email', maintainer.get('email'))
if license_el is not None:
l = ET.SubElement(standalone, 'license')
l.text = license_el.text
if license_el.get('file'):
l.set('file', license_el.get('file'))
if repo_url is not None:
u = ET.SubElement(standalone, 'url', type='repository')
u.text = repo_url.text
if repo_url.get('branch'):
u.set('branch', repo_url.get('branch'))
if readme_url is not None:
u = ET.SubElement(standalone, 'url', type='readme')
u.text = readme_url.text
icon_text = wb_icon.text if wb_icon is not None else 'FreecadRobustMCP.svg'
ET.SubElement(standalone, 'icon').text = icon_text
fcmin_text = wb_freecadmin.text if wb_freecadmin is not None else '0.21'
ET.SubElement(standalone, 'freecadmin').text = fcmin_text
# Add content/workbench section
content = ET.SubElement(standalone, 'content')
wb_el = ET.SubElement(content, 'workbench')
cls_text = wb_classname.text if wb_classname is not None else 'FreecadRobustMCPWorkbench'
ET.SubElement(wb_el, 'classname').text = cls_text
ET.SubElement(wb_el, 'subdirectory').text = './'
# Add tags
for tag in ['MCP', 'AI', 'automation', 'Claude', 'bridge', 'headless']:
ET.SubElement(wb_el, 'tag').text = tag
# Helper to indent XML for Python < 3.9 compatibility
def indent_xml(elem, level=0, space=' '):
"""Indent XML element tree (fallback for Python < 3.9)."""
indent_str = '\n' + level * space
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = indent_str + space
for child in elem:
indent_xml(child, level + 1, space)
if not child.tail or not child.tail.strip():
child.tail = indent_str
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = indent_str
# Write the standalone package.xml
# Use ET.indent if available (Python 3.9+), otherwise use fallback
if hasattr(ET, 'indent'):
ET.indent(standalone, space=' ')
else:
indent_xml(standalone)
tree = ET.ElementTree(standalone)
output_path = os.path.join(addon_dest, 'package.xml')
tree.write(output_path, encoding='UTF-8', xml_declaration=True)
print("Generated package.xml successfully")
except Exception as e:
print(f"Warning: Could not generate package.xml: {e}", file=sys.stderr)
# Don't fail the installation if package.xml generation fails
PYEOF
else
echo "Warning: Root package.xml not found, skipping package.xml generation"
fi
echo ""
echo "=========================================="
echo "FreeCAD Robust MCP Workbench installed!"
echo "=========================================="
echo ""
echo "Installation path: $ADDON_DEST"
echo ""
echo "To use:"
echo " 1. Start FreeCAD"
echo " 2. Select the 'Robust MCP Bridge' workbench from the workbench selector"
echo " 3. Click 'Start MCP Bridge' in the toolbar"
echo " 4. Connect your MCP client (Claude Code, etc.) to FreeCAD"
echo ""
# Uninstall the FreeCAD Robust MCP workbench addon
uninstall-mcp-bridge-workbench:
#!/usr/bin/env bash
set -euo pipefail
ADDON_NAME="FreecadRobustMCP"
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
ADDON_DEST="$MOD_DIR/$ADDON_NAME"
if [[ -d "$ADDON_DEST" ]]; then
rm -rf "$ADDON_DEST"
echo "FreeCAD Robust MCP Workbench uninstalled from: $ADDON_DEST"
else
echo "Workbench not found at: $ADDON_DEST"
fi
# =============================================================================
# Macro Installation
# =============================================================================
# Install the CutObjectForMagnets macro to FreeCAD's macro directory
macro-cut:
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="{{project_root}}"
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
# Verify source exists before copying
MACRO_SRC="${PROJECT_DIR}/macros/Cut_Object_for_Magnets/CutObjectForMagnets.FCMacro"
if [[ ! -f "$MACRO_SRC" ]]; then
echo "Error: Macro source file not found: $MACRO_SRC" >&2
exit 1
fi
mkdir -p "$MACRO_DIR"
# Remove existing installation if present (clean install)
if [[ -f "$MACRO_DIR/CutObjectForMagnets.FCMacro" ]]; then
echo "Removing existing CutObjectForMagnets macro..."
rm -f "$MACRO_DIR/CutObjectForMagnets.FCMacro"
rm -f "$MACRO_DIR/CutObjectForMagnets.svg"
fi
# Copy the macro file
cp "$MACRO_SRC" "$MACRO_DIR/"
# Optionally copy the icon if it exists
ICON_SRC="${PROJECT_DIR}/macros/Cut_Object_for_Magnets/CutObjectForMagnets.svg"
if [[ -f "$ICON_SRC" ]]; then
cp "$ICON_SRC" "$MACRO_DIR/"
fi
echo "CutObjectForMagnets macro installed to: $MACRO_DIR"
echo ""
echo "To use:"
echo " 1. Start FreeCAD"
echo " 2. Select an object to cut"
echo " 3. Go to: Macro → Macros → CutObjectForMagnets → Execute"
echo ""
echo "For angled cuts:"
echo " 1. Create a datum plane at your desired angle (Part Design → Create datum plane)"
echo " 2. Select 'Model Plane' in the macro dialog"
echo " 3. Choose your datum plane from the dropdown"
# Uninstall the CutObjectForMagnets macro
uninstall-macro-cut:
#!/usr/bin/env bash
set -euo pipefail
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
rm -f "$MACRO_DIR/CutObjectForMagnets.FCMacro"
rm -f "$MACRO_DIR/CutObjectForMagnets.svg"
echo "CutObjectForMagnets macro uninstalled"
# Install the MultiExport macro to FreeCAD's macro directory
macro-export:
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="{{project_root}}"
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
# Verify source exists before copying
MACRO_SRC="${PROJECT_DIR}/macros/Multi_Export/MultiExport.FCMacro"
if [[ ! -f "$MACRO_SRC" ]]; then
echo "Error: Macro source file not found: $MACRO_SRC" >&2
exit 1
fi
mkdir -p "$MACRO_DIR"
# Remove existing installation if present (clean install)
if [[ -f "$MACRO_DIR/MultiExport.FCMacro" ]]; then
echo "Removing existing MultiExport macro..."
rm -f "$MACRO_DIR/MultiExport.FCMacro"
rm -f "$MACRO_DIR/MultiExport.svg"
fi
# Copy the macro file
cp "$MACRO_SRC" "$MACRO_DIR/"
# Optionally copy the icon if it exists
ICON_SRC="${PROJECT_DIR}/macros/Multi_Export/MultiExport.svg"
if [[ -f "$ICON_SRC" ]]; then
cp "$ICON_SRC" "$MACRO_DIR/"
fi
echo "MultiExport macro installed to: $MACRO_DIR"
echo ""
echo "To use:"
echo " 1. Start FreeCAD"
echo " 2. Select one or more objects to export"
echo " 3. Go to: Macro → Macros → MultiExport → Execute"
echo " 4. Choose export formats (STL, STEP, 3MF selected by default)"
echo " 5. Set output directory and filename"
echo " 6. Click Export"
# Uninstall the MultiExport macro
uninstall-macro-export:
#!/usr/bin/env bash
set -euo pipefail
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
rm -f "$MACRO_DIR/MultiExport.FCMacro"
rm -f "$MACRO_DIR/MultiExport.svg"
echo "MultiExport macro uninstalled"
# Install all macros to FreeCAD's macro directory
macro-all: macro-cut macro-export
@echo "All macros installed successfully!"
# Uninstall all macros from FreeCAD's macro directory
uninstall-macro-all: uninstall-macro-cut uninstall-macro-export
@echo "All macros uninstalled successfully!"
# =============================================================================
# Status Check
# =============================================================================
# Check installation status of all components
status:
#!/usr/bin/env bash
set -euo pipefail
# Set FreeCAD directories
eval "$(just install::_freecad-dirs)"
# Helper function to get file modification time (cross-platform)
get_mod_time() {
local file="$1"
if [[ "$OSTYPE" == "darwin"* ]]; then
stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$file" 2>/dev/null || echo "unknown"
else
stat -c "%y" "$file" 2>/dev/null | cut -d'.' -f1 || echo "unknown"
fi
}
echo "=========================================="
echo "Installation Status"
echo "=========================================="
echo ""
# Check Robust MCP Server (installed as uv tool)
if command -v freecad-mcp &> /dev/null; then
MCP_VERSION=$(freecad-mcp --version 2>/dev/null || echo "unknown")
MCP_PATH=$(command -v freecad-mcp)
MCP_MOD_TIME=$(get_mod_time "$MCP_PATH")
echo "✓ Robust MCP Server: INSTALLED (as uv tool)"
echo " Version: $MCP_VERSION"
echo " Updated: $MCP_MOD_TIME"
echo " Run: freecad-mcp"
elif [[ -f "{{project_root}}/pyproject.toml" ]] && grep -q 'name = "freecad-robust-mcp"' "{{project_root}}/pyproject.toml" 2>/dev/null; then
# Dev environment exists - check if synced by looking for .venv
if [[ -d "{{project_root}}/.venv" ]]; then
DEV_VERSION=$(cd "{{project_root}}" && uv run python -c "from freecad_mcp import __version__; print(__version__)" 2>/dev/null || echo "unknown")
echo "✓ Robust MCP Server: AVAILABLE (via dev environment)"
echo " Version: $DEV_VERSION"
echo " Run: just mcp::run"
echo " For system-wide install: just install::mcp-server"
else
echo "○ Robust MCP Server: DEV SOURCE AVAILABLE (needs setup)"
echo " Setup: uv sync --all-extras"
echo " Then run: just mcp::run"
fi
else
echo "✗ Robust MCP Server: NOT INSTALLED"
echo " Install: just install::mcp-server"
fi
echo ""
# Helper function to extract version from package.xml files
# Uses environment variable to pass file path safely to Python (avoids shell interpolation)
extract_package_version() {
local package_file="$1"
PACKAGE_FILE="$package_file" python3 -c '
import os
import xml.etree.ElementTree as ET
try:
package_file = os.environ.get("PACKAGE_FILE", "")
tree = ET.parse(package_file)
root = tree.getroot()
ns = {"pkg": "https://wiki.freecad.org/Package_Metadata"}
# Try with namespace first, then without
ver = root.find("pkg:version", ns) or root.find("version")
print(ver.text if ver is not None else "unknown")
except Exception:
print("unknown")
' 2>/dev/null || echo "unknown"
}
# Helper function to extract __Version__ from macro files (handles single/double quotes)
# Uses environment variable to pass file path safely to Python (avoids shell interpolation)
extract_macro_version() {
local file="$1"
MACRO_FILE="$file" python3 -c '
import os
import re
try:
macro_file = os.environ.get("MACRO_FILE", "")
content = open(macro_file).read()
match = re.search(r"__Version__\s*=\s*[\"'"'"']([^\"'"'"']+)[\"'"'"']", content)
print(match.group(1) if match else "unknown")
except Exception:
print("unknown")
' 2>/dev/null || echo "unknown"
}
# Check workbench
if [[ -d "$MOD_DIR/FreecadRobustMCP" ]]; then
WB_VERSION="unknown"
if [[ -f "$MOD_DIR/FreecadRobustMCP/package.xml" ]]; then
WB_VERSION=$(extract_package_version "$MOD_DIR/FreecadRobustMCP/package.xml")
fi
WB_MOD_TIME=$(get_mod_time "$MOD_DIR/FreecadRobustMCP/InitGui.py")
echo "✓ Robust MCP Bridge Workbench: INSTALLED"
echo " Version: $WB_VERSION"
echo " Updated: $WB_MOD_TIME"
echo " Path: $MOD_DIR/FreecadRobustMCP"
else
echo "✗ Robust MCP Bridge Workbench: NOT INSTALLED"
echo " Install: just install::mcp-bridge-workbench"
fi
echo ""
# Check macros
echo "Macros:"
if [[ -f "$MACRO_DIR/CutObjectForMagnets.FCMacro" ]]; then
CUT_VERSION=$(extract_macro_version "$MACRO_DIR/CutObjectForMagnets.FCMacro")
CUT_MOD_TIME=$(get_mod_time "$MACRO_DIR/CutObjectForMagnets.FCMacro")
echo " ✓ CutObjectForMagnets: INSTALLED"
echo " Version: $CUT_VERSION"
echo " Updated: $CUT_MOD_TIME"
else
echo " ✗ CutObjectForMagnets: NOT INSTALLED"
echo " Install: just install::macro-cut"
fi
if [[ -f "$MACRO_DIR/MultiExport.FCMacro" ]]; then
EXPORT_VERSION=$(extract_macro_version "$MACRO_DIR/MultiExport.FCMacro")
EXPORT_MOD_TIME=$(get_mod_time "$MACRO_DIR/MultiExport.FCMacro")
echo " ✓ MultiExport: INSTALLED"
echo " Version: $EXPORT_VERSION"
echo " Updated: $EXPORT_MOD_TIME"
else
echo " ✗ MultiExport: NOT INSTALLED"
echo " Install: just install::macro-export"
fi
echo ""
# Check for legacy installations
LEGACY_COUNT=0
if [[ -d "$MOD_DIR/MCPBridge" ]]; then
echo "⚠ Legacy plugin found: $MOD_DIR/MCPBridge"
echo " Run: rm -rf \"$MOD_DIR/MCPBridge\""
((LEGACY_COUNT++)) || true
fi
if [[ -f "$MACRO_DIR/StartMCPBridge.FCMacro" ]]; then
echo "⚠ Legacy macro found: $MACRO_DIR/StartMCPBridge.FCMacro"
echo " Run: rm \"$MACRO_DIR/StartMCPBridge.FCMacro\""
((LEGACY_COUNT++)) || true
fi
if [[ $LEGACY_COUNT -gt 0 ]]; then
echo ""
echo "Note: Legacy installations can be removed. The workbench replaces them."
fi
echo "=========================================="