chrore: major overhaul of docs, commands, and workflows (#23)
* ci: add status badges to README * chore: major overhaul - docs, commands & workflows * fix: general fixes and improvements * chore: various updates and fixes * chore: minor fixes
This commit is contained in:
@@ -3,6 +3,3 @@ contact_links:
|
|||||||
- name: Documentation
|
- name: Documentation
|
||||||
url: https://github.com/spkane/freecad-robust-mcp-and-more#readme
|
url: https://github.com/spkane/freecad-robust-mcp-and-more#readme
|
||||||
about: Check the README for usage instructions and troubleshooting
|
about: Check the README for usage instructions and troubleshooting
|
||||||
- name: Discussions
|
|
||||||
url: https://github.com/spkane/freecad-robust-mcp-and-more/discussions
|
|
||||||
about: Ask questions and discuss ideas with the community
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
# Run weekly on Sundays at midnight UTC
|
# Run weekly on Sundays at midnight UTC
|
||||||
- cron: "0 0 * * 0"
|
- cron: "0 0 * * 0"
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
# Cancel in-progress runs for the same branch
|
# Cancel in-progress runs for the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
name: Docker Release
|
|
||||||
|
|
||||||
# Only trigger on GitHub release publication, not on tag push.
|
|
||||||
# When a release is created, GitHub fires both 'release' and 'push' events.
|
|
||||||
# Using only 'release' prevents duplicate workflow runs.
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
# Cancel in-progress runs for the same tag
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKERHUB_REPO: spkane/freecad-robust-mcp
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Build and Push to Docker Hub
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Validate semantic version tag
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Validate semantic versioning format (v1.2.3 or v1.2.3-prerelease)
|
|
||||||
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
||||||
echo "ERROR: Tag '$TAG' does not follow semantic versioning (vX.Y.Z or vX.Y.Z-prerelease)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract version without 'v' prefix
|
|
||||||
VERSION="${TAG#v}"
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Extract major.minor for additional tag
|
|
||||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
|
|
||||||
echo "major_minor=$MAJOR_MINOR" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Extract major for additional tag
|
|
||||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
|
||||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Check if this is a prerelease
|
|
||||||
if [[ "$TAG" =~ -[a-zA-Z0-9.]+ ]]; then
|
|
||||||
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
|
||||||
IS_PRERELEASE="true"
|
|
||||||
else
|
|
||||||
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
|
||||||
IS_PRERELEASE="false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Parsed version: $VERSION (major: $MAJOR, major.minor: $MAJOR_MINOR, prerelease: $IS_PRERELEASE)"
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ${{ env.DOCKERHUB_REPO }}
|
|
||||||
tags: |
|
|
||||||
# Full semantic version (v1.2.3 -> 1.2.3)
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
# Major.minor version (v1.2.3 -> 1.2)
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
# Major version only (v1.2.3 -> 1) - only for stable releases
|
|
||||||
type=semver,pattern={{major}},enable=${{ steps.version.outputs.is_prerelease == 'false' }}
|
|
||||||
# Latest tag - only for stable releases
|
|
||||||
type=raw,value=latest,enable=${{ steps.version.outputs.is_prerelease == 'false' }}
|
|
||||||
labels: |
|
|
||||||
org.opencontainers.image.title=FreeCAD Robust MCP Server
|
|
||||||
org.opencontainers.image.description=Model Context Protocol server for FreeCAD integration
|
|
||||||
org.opencontainers.image.vendor=spkane
|
|
||||||
org.opencontainers.image.version=${{ steps.version.outputs.version }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
provenance: true
|
|
||||||
sbom: true
|
|
||||||
|
|
||||||
- name: Test pushed image
|
|
||||||
run: |
|
|
||||||
# Pull and test the image
|
|
||||||
docker pull ${{ env.DOCKERHUB_REPO }}:${{ steps.version.outputs.version }}
|
|
||||||
|
|
||||||
# Test that the container starts and responds to MCP initialize
|
|
||||||
echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
|
|
||||||
timeout 30 docker run --rm -i \
|
|
||||||
-e FREECAD_MODE=xmlrpc \
|
|
||||||
${{ env.DOCKERHUB_REPO }}:${{ steps.version.outputs.version }} 2>&1 | \
|
|
||||||
grep -q '"result"' && echo "Container test passed" || echo "Container test completed"
|
|
||||||
|
|
||||||
- name: Create Docker Hub README
|
|
||||||
run: |
|
|
||||||
# Create a concise README for Docker Hub (limit is 25000 bytes)
|
|
||||||
cat > ./DOCKERHUB_README.md << 'EOF'
|
|
||||||
# FreeCAD Robust MCP Server
|
|
||||||
|
|
||||||
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that enables integration between AI assistants (Claude, GPT, and other MCP-compatible tools) and [FreeCAD](https://www.freecadweb.org/), allowing AI-assisted development and debugging of 3D models, macros, and workbenches.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **82+ MCP Tools**: Comprehensive CAD operations including primitives, PartDesign, booleans, export
|
|
||||||
- **Multiple Connection Modes**: XML-RPC (recommended), JSON-RPC socket, or embedded
|
|
||||||
- **GUI & Headless Support**: Full modeling in headless mode, plus screenshots/colors in GUI mode
|
|
||||||
- **Macro Development**: Create, edit, run, and template FreeCAD macros via MCP
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker pull spkane/freecad-robust-mcp:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running with Claude Desktop (macOS/Windows)
|
|
||||||
|
|
||||||
Add to your Claude Desktop configuration:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"freecad": {
|
|
||||||
"command": "docker",
|
|
||||||
"args": [
|
|
||||||
"run", "-i", "--rm",
|
|
||||||
"-e", "FREECAD_MODE=xmlrpc",
|
|
||||||
"-e", "FREECAD_SOCKET_HOST=host.docker.internal",
|
|
||||||
"spkane/freecad-robust-mcp:latest"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linux Users
|
|
||||||
|
|
||||||
`host.docker.internal` is only available on Docker Desktop (macOS/Windows).
|
|
||||||
On Linux Docker Engine, use one of these alternatives:
|
|
||||||
|
|
||||||
**Option 1: Host networking (recommended)**
|
|
||||||
```bash
|
|
||||||
docker run -i --rm --network=host \
|
|
||||||
-e FREECAD_MODE=xmlrpc \
|
|
||||||
-e FREECAD_SOCKET_HOST=localhost \
|
|
||||||
spkane/freecad-robust-mcp:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 2: Use host IP address**
|
|
||||||
```bash
|
|
||||||
docker run -i --rm \
|
|
||||||
-e FREECAD_MODE=xmlrpc \
|
|
||||||
-e FREECAD_SOCKET_HOST=192.168.1.100 \
|
|
||||||
spkane/freecad-robust-mcp:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 3: Add host gateway (Docker 20.10+)**
|
|
||||||
```bash
|
|
||||||
docker run -i --rm --add-host=host.docker.internal:host-gateway \
|
|
||||||
-e FREECAD_MODE=xmlrpc \
|
|
||||||
-e FREECAD_SOCKET_HOST=host.docker.internal \
|
|
||||||
spkane/freecad-robust-mcp:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Connection Modes
|
|
||||||
|
|
||||||
| Mode | Description | Use Case |
|
|
||||||
|------|-------------|----------|
|
|
||||||
| `xmlrpc` | XML-RPC over HTTP (port 9875) | **Recommended** - Works with FreeCAD GUI or headless |
|
|
||||||
| `socket` | JSON-RPC over TCP (port 9876) | Alternative to XML-RPC |
|
|
||||||
| `embedded` | Direct Python import | Linux only, requires FreeCAD in Python path |
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `FREECAD_MODE` | `xmlrpc` | Connection mode |
|
|
||||||
| `FREECAD_SOCKET_HOST` | `localhost` | Server host (used for both XML-RPC and socket modes) |
|
|
||||||
| `FREECAD_XMLRPC_PORT` | `9875` | XML-RPC server port |
|
|
||||||
| `FREECAD_SOCKET_PORT` | `9876` | Socket server port |
|
|
||||||
|
|
||||||
## Starting FreeCAD with MCP Bridge
|
|
||||||
|
|
||||||
Before using this Docker image, start FreeCAD with the MCP bridge:
|
|
||||||
|
|
||||||
1. Install the **MCP Bridge** workbench via FreeCAD Addon Manager
|
|
||||||
2. Switch to the MCP Bridge workbench and click **Start MCP Bridge**
|
|
||||||
3. The bridge will start listening on ports 9875 (XML-RPC) and 9876 (Socket)
|
|
||||||
|
|
||||||
## Full Documentation
|
|
||||||
|
|
||||||
📖 **For complete documentation, examples, and source code, visit:**
|
|
||||||
|
|
||||||
**[https://github.com/spkane/freecad-robust-mcp-and-more](https://github.com/spkane/freecad-robust-mcp-and-more)**
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License - see [LICENSE](https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/LICENSE) for details.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Validate the generated README
|
|
||||||
if [ ! -f ./DOCKERHUB_README.md ]; then
|
|
||||||
echo "ERROR: DOCKERHUB_README.md was not created" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -s ./DOCKERHUB_README.md ]; then
|
|
||||||
echo "ERROR: DOCKERHUB_README.md is empty" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check file size (Docker Hub limit is 25000 bytes)
|
|
||||||
FILE_SIZE=$(wc -c < ./DOCKERHUB_README.md)
|
|
||||||
MAX_SIZE=25000
|
|
||||||
echo "Docker Hub README size: ${FILE_SIZE} bytes (limit: ${MAX_SIZE} bytes)"
|
|
||||||
|
|
||||||
if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
|
|
||||||
echo "ERROR: DOCKERHUB_README.md exceeds Docker Hub limit of ${MAX_SIZE} bytes (actual: ${FILE_SIZE} bytes)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Docker Hub README validation passed"
|
|
||||||
|
|
||||||
- name: Update Docker Hub description
|
|
||||||
uses: peter-evans/dockerhub-description@v5
|
|
||||||
with:
|
|
||||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
repository: ${{ env.DOCKERHUB_REPO }}
|
|
||||||
readme-filepath: ./DOCKERHUB_README.md
|
|
||||||
short-description: "MCP (Model Context Protocol) server for FreeCAD integration"
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Generate release summary
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
echo "## Docker Release Summary"
|
|
||||||
echo ""
|
|
||||||
echo "**Version:** ${{ steps.version.outputs.version }}"
|
|
||||||
echo "**Prerelease:** ${{ steps.version.outputs.is_prerelease }}"
|
|
||||||
echo ""
|
|
||||||
echo "### Published Tags"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`"
|
|
||||||
echo "${{ steps.meta.outputs.tags }}"
|
|
||||||
echo "\`\`\`"
|
|
||||||
echo ""
|
|
||||||
echo "### Pull Command"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "docker pull ${{ env.DOCKERHUB_REPO }}:${{ steps.version.outputs.version }}"
|
|
||||||
echo "\`\`\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
name: Docker Build
|
name: Docker Build
|
||||||
|
|
||||||
# Build and test Docker images on pushes to main and pull requests.
|
# Build and test Docker images on pushes to main and pull requests.
|
||||||
# Note: Version tags (v*) are NOT included here - they trigger docker-release.yaml
|
# Note: Release tags (robust-mcp-server-v*) trigger mcp-server-release.yaml
|
||||||
# via the 'release: published' event instead, to avoid duplicate workflow runs.
|
# which handles Docker Hub releases. This workflow is for CI testing only.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, master]
|
branches: [main, master]
|
||||||
@@ -20,6 +20,8 @@ on:
|
|||||||
- "src/**/*.py"
|
- "src/**/*.py"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- ".github/workflows/docker.yaml"
|
- ".github/workflows/docker.yaml"
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
# Cancel in-progress runs for the same branch
|
# Cancel in-progress runs for the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Macro Cut Object for Magnets Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'macro-cut-object-for-magnets-v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: ./.github/workflows/macro-release-reusable.yaml
|
||||||
|
with:
|
||||||
|
tag_prefix: 'macro-cut-object-for-magnets-v'
|
||||||
|
macro_dir: 'Cut_Object_for_Magnets'
|
||||||
|
macro_file: 'CutObjectForMagnets.FCMacro'
|
||||||
|
macro_name: 'Cut Object for Magnets'
|
||||||
|
archive_name: 'CutObjectForMagnets'
|
||||||
|
readme_file: 'README-CutObjectForMagnets.md'
|
||||||
|
wiki_url: 'https://wiki.freecad.org/Macro_Cut_Object_for_Magnets'
|
||||||
|
description: |
|
||||||
|
A FreeCAD macro that cuts 3D objects along a plane and automatically adds aligned magnet holes with surface collision detection. Perfect for creating 3D printed parts that snap together with embedded magnets.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Cut along preset planes (XY, XZ, YZ) or model datum planes
|
||||||
|
- Automatic hole placement with even distribution
|
||||||
|
- Surface collision detection prevents holes from breaking through walls
|
||||||
|
- Configurable hole diameter, depth, and count
|
||||||
|
- Creates PartDesign::Body objects with parametric Hole features
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Macro Multi Export Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'macro-multi-export-v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: ./.github/workflows/macro-release-reusable.yaml
|
||||||
|
with:
|
||||||
|
tag_prefix: 'macro-multi-export-v'
|
||||||
|
macro_dir: 'Multi_Export'
|
||||||
|
macro_file: 'MultiExport.FCMacro'
|
||||||
|
macro_name: 'Multi Export'
|
||||||
|
archive_name: 'MultiExport'
|
||||||
|
readme_file: 'README-MultiExport.md'
|
||||||
|
wiki_url: 'https://wiki.freecad.org/Macro_Multi_Export'
|
||||||
|
description: |
|
||||||
|
A FreeCAD macro that exports selected bodies to multiple file formats simultaneously with a user-friendly dialog.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Export to STL, STEP, 3MF, OBJ, IGES, BREP, PLY, and AMF formats
|
||||||
|
- Batch export multiple objects at once
|
||||||
|
- Configurable mesh quality settings (tolerance and deflection)
|
||||||
|
- Real-time file preview before export
|
||||||
|
- Remember last-used export directory
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
name: Macro Release (Reusable)
|
||||||
|
|
||||||
|
# Reusable workflow for releasing individual FreeCAD macros
|
||||||
|
# Called by component-specific workflows with appropriate inputs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag_prefix:
|
||||||
|
description: 'Tag prefix to match (e.g., macro-cut-object-for-magnets-v)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_dir:
|
||||||
|
description: 'Macro directory relative to macros/ (e.g., Cut_Object_for_Magnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_file:
|
||||||
|
description: 'Main macro filename (e.g., CutObjectForMagnets.FCMacro)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_name:
|
||||||
|
description: 'Human-readable macro name (e.g., Cut Object for Magnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
archive_name:
|
||||||
|
description: 'Archive base name (e.g., CutObjectForMagnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
readme_file:
|
||||||
|
description: 'README filename (e.g., README-CutObjectForMagnets.md)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
wiki_url:
|
||||||
|
description: 'FreeCAD Wiki URL for this macro'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
description:
|
||||||
|
description: 'Short description for release notes'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
tag_prefix:
|
||||||
|
description: 'Tag prefix to match (e.g., macro-cut-object-for-magnets-v)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_dir:
|
||||||
|
description: 'Macro directory relative to macros/ (e.g., Cut_Object_for_Magnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_file:
|
||||||
|
description: 'Main macro filename (e.g., CutObjectForMagnets.FCMacro)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
macro_name:
|
||||||
|
description: 'Human-readable macro name (e.g., Cut Object for Magnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
archive_name:
|
||||||
|
description: 'Archive base name (e.g., CutObjectForMagnets)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
readme_file:
|
||||||
|
description: 'README filename (e.g., README-CutObjectForMagnets.md)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
wiki_url:
|
||||||
|
description: 'FreeCAD Wiki URL for this macro'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
description:
|
||||||
|
description: 'Short description for release notes'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-and-release:
|
||||||
|
name: Validate Tag and Create Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Validate semantic version tag
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
TAG_PREFIX: ${{ inputs.tag_prefix }}
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Build regex pattern from tag prefix
|
||||||
|
PATTERN="^${TAG_PREFIX}([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)\$"
|
||||||
|
|
||||||
|
if [[ ! "$TAG" =~ $PATTERN ]]; then
|
||||||
|
echo "ERROR: Tag '$TAG' does not match expected format (${TAG_PREFIX}X.Y.Z)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check if this is a prerelease
|
||||||
|
if [[ "$VERSION" =~ -[a-zA-Z0-9.]+ ]]; then
|
||||||
|
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Parsed version: $VERSION"
|
||||||
|
|
||||||
|
- name: Verify macro version in files
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
MACRO_DIR: ${{ inputs.macro_dir }}
|
||||||
|
MACRO_FILE: ${{ inputs.macro_file }}
|
||||||
|
MACRO_NAME: ${{ inputs.macro_name }}
|
||||||
|
run: |
|
||||||
|
MACRO_PATH="macros/${MACRO_DIR}/${MACRO_FILE}"
|
||||||
|
|
||||||
|
echo "Verifying version in source files matches tag: $VERSION"
|
||||||
|
|
||||||
|
# Check __Version__ in .FCMacro file
|
||||||
|
if [ -f "$MACRO_PATH" ]; then
|
||||||
|
MACRO_VERSION=$(grep -o '__Version__ = "[^"]*"' "$MACRO_PATH" | cut -d'"' -f2)
|
||||||
|
if [ "$MACRO_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $MACRO_PATH"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $MACRO_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "The version in source files must be updated before tagging."
|
||||||
|
echo "Run the appropriate bump command first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ $MACRO_PATH: $MACRO_VERSION"
|
||||||
|
else
|
||||||
|
echo "ERROR: Macro file not found: $MACRO_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check package.xml
|
||||||
|
PKG_VERSION=$(awk -v name="$MACRO_NAME" '
|
||||||
|
/<macro>/ { in_macro=1 }
|
||||||
|
/<\/macro>/ { in_macro=0; found_name=0 }
|
||||||
|
in_macro && index($0, name) > 0 { found_name=1 }
|
||||||
|
in_macro && found_name && /<version>/ {
|
||||||
|
gsub(/.*<version>/, ""); gsub(/<\/version>.*/, ""); print; exit
|
||||||
|
}
|
||||||
|
' package.xml)
|
||||||
|
if [ "$PKG_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in package.xml ($MACRO_NAME section)"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $PKG_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ package.xml ($MACRO_NAME): $PKG_VERSION"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Version verification passed!"
|
||||||
|
|
||||||
|
- name: Create macro archive
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
MACRO_DIR: ${{ inputs.macro_dir }}
|
||||||
|
MACRO_FILE: ${{ inputs.macro_file }}
|
||||||
|
ARCHIVE_NAME: ${{ inputs.archive_name }}
|
||||||
|
README_FILE: ${{ inputs.readme_file }}
|
||||||
|
run: |
|
||||||
|
# Create staging directory
|
||||||
|
mkdir -p "staging/${ARCHIVE_NAME}-${VERSION}"
|
||||||
|
|
||||||
|
# Copy macro files
|
||||||
|
cp "macros/${MACRO_DIR}/${MACRO_FILE}" "staging/${ARCHIVE_NAME}-${VERSION}/"
|
||||||
|
|
||||||
|
# Copy icon if exists
|
||||||
|
for ext in svg png; do
|
||||||
|
ICON_FILE="macros/${MACRO_DIR}/${ARCHIVE_NAME}.${ext}"
|
||||||
|
if [ -f "$ICON_FILE" ]; then
|
||||||
|
cp "$ICON_FILE" "staging/${ARCHIVE_NAME}-${VERSION}/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Copy README
|
||||||
|
if [ -f "macros/${MACRO_DIR}/${README_FILE}" ]; then
|
||||||
|
cp "macros/${MACRO_DIR}/${README_FILE}" "staging/${ARCHIVE_NAME}-${VERSION}/README.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy LICENSE
|
||||||
|
cp LICENSE "staging/${ARCHIVE_NAME}-${VERSION}/"
|
||||||
|
|
||||||
|
# Create tar.gz archive
|
||||||
|
cd staging
|
||||||
|
tar -czvf "${ARCHIVE_NAME}-${VERSION}.tar.gz" "${ARCHIVE_NAME}-${VERSION}"
|
||||||
|
|
||||||
|
# Create zip archive
|
||||||
|
zip -r "${ARCHIVE_NAME}-${VERSION}.zip" "${ARCHIVE_NAME}-${VERSION}"
|
||||||
|
|
||||||
|
mv "${ARCHIVE_NAME}-${VERSION}.tar.gz" ../
|
||||||
|
mv "${ARCHIVE_NAME}-${VERSION}.zip" ../
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "Created archives:"
|
||||||
|
ls -la "${ARCHIVE_NAME}-${VERSION}."*
|
||||||
|
|
||||||
|
- name: Extract changelog section
|
||||||
|
id: changelog
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
MACRO_NAME: ${{ inputs.macro_name }}
|
||||||
|
DESCRIPTION: ${{ inputs.description }}
|
||||||
|
MACRO_FILE: ${{ inputs.macro_file }}
|
||||||
|
MACRO_DIR: ${{ inputs.macro_dir }}
|
||||||
|
README_FILE: ${{ inputs.readme_file }}
|
||||||
|
WIKI_URL: ${{ inputs.wiki_url }}
|
||||||
|
run: |
|
||||||
|
# Match header exactly as it appears in CHANGELOG.md
|
||||||
|
HEADER="### ${MACRO_NAME} Macro v${VERSION}"
|
||||||
|
|
||||||
|
# Extract section between this version header and the next component header or separator
|
||||||
|
# Only exit on: "---" separator OR "### " followed by component name (capital letter)
|
||||||
|
# This allows #### Added, #### Changed, etc. to be included
|
||||||
|
CHANGELOG_CONTENT=$(awk -v header="$HEADER" '
|
||||||
|
BEGIN { found=0 }
|
||||||
|
$0 == header || $0 == header " " { found=1; next }
|
||||||
|
found && /^---$/ { exit }
|
||||||
|
found && /^### [A-Z]/ { exit }
|
||||||
|
found { print }
|
||||||
|
' CHANGELOG.md)
|
||||||
|
|
||||||
|
# Build release body
|
||||||
|
cat > release_body.md << EOF
|
||||||
|
## ${MACRO_NAME} Macro v${VERSION}
|
||||||
|
|
||||||
|
${DESCRIPTION}
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Copy \`${MACRO_FILE}\` to your FreeCAD macro directory:
|
||||||
|
|
||||||
|
- **macOS**: \`~/Library/Application Support/FreeCAD/Macro/\`
|
||||||
|
- **Linux**: \`~/.local/share/FreeCAD/Macro/\`
|
||||||
|
- **Windows**: \`%APPDATA%/FreeCAD/Macro/\`
|
||||||
|
|
||||||
|
Optionally copy the icon file (\`.svg\` or \`.png\`) to the same location.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- [Full README](https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/macros/${MACRO_DIR}/${README_FILE})
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$WIKI_URL" ]; then
|
||||||
|
echo "- [FreeCAD Wiki Page](${WIKI_URL})" >> release_body.md
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### Changelog"
|
||||||
|
echo ""
|
||||||
|
echo "$CHANGELOG_CONTENT"
|
||||||
|
} >> release_body.md
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: "${{ inputs.macro_name }} Macro v${{ steps.version.outputs.version }}"
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
prerelease: ${{ steps.version.outputs.is_prerelease == 'true' }}
|
||||||
|
generate_release_notes: true
|
||||||
|
body_path: release_body.md
|
||||||
|
files: |
|
||||||
|
${{ inputs.archive_name }}-${{ steps.version.outputs.version }}.tar.gz
|
||||||
|
${{ inputs.archive_name }}-${{ steps.version.outputs.version }}.zip
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate summary
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
MACRO_NAME: ${{ inputs.macro_name }}
|
||||||
|
ARCHIVE_NAME: ${{ inputs.archive_name }}
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## ${MACRO_NAME} Macro Release"
|
||||||
|
echo ""
|
||||||
|
echo "**Version:** ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
echo "### Downloads"
|
||||||
|
echo ""
|
||||||
|
echo "- \`${ARCHIVE_NAME}-${VERSION}.tar.gz\`"
|
||||||
|
echo "- \`${ARCHIVE_NAME}-${VERSION}.zip\`"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
name: Macro Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
# Note: Only trigger on release.published to avoid duplicate runs.
|
|
||||||
# Creating a release implicitly pushes a tag, so triggering on both
|
|
||||||
# would cause the workflow to run twice.
|
|
||||||
|
|
||||||
# Cancel in-progress runs for the same tag
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
package-macros:
|
|
||||||
name: Package FreeCAD Macros
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Get version from tag
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Extract version without 'v' prefix
|
|
||||||
VERSION="${TAG#v}"
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
echo "Packaging macros for version: $VERSION"
|
|
||||||
|
|
||||||
- name: Update macro versions
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
TODAY=$(date +%Y-%m-%d)
|
|
||||||
|
|
||||||
echo "Updating macros to version: $VERSION (date: $TODAY)"
|
|
||||||
|
|
||||||
# Update __Version__ and __Date__ in all .FCMacro files
|
|
||||||
for macro_file in macros/*/*.FCMacro; do
|
|
||||||
if [ -f "$macro_file" ]; then
|
|
||||||
echo "Updating: $macro_file"
|
|
||||||
|
|
||||||
# Update __Version__ line (handles both single and double quotes)
|
|
||||||
sed -i "s/^__Version__ = [\"'].*[\"']/__Version__ = \"${VERSION}\"/" "$macro_file"
|
|
||||||
|
|
||||||
# Update __Date__ line (handles both single and double quotes)
|
|
||||||
sed -i "s/^__Date__ = [\"'].*[\"']/__Date__ = \"${TODAY}\"/" "$macro_file"
|
|
||||||
|
|
||||||
# Verify the changes
|
|
||||||
grep -E "^__(Version|Date)__" "$macro_file" || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Create macro archive
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
|
|
||||||
# Create a staging directory
|
|
||||||
mkdir -p "staging/freecad-macros-${VERSION}"
|
|
||||||
|
|
||||||
# Copy macro directories (excluding test files and development artifacts)
|
|
||||||
for macro_dir in macros/*/; do
|
|
||||||
if [ -d "$macro_dir" ]; then
|
|
||||||
macro_name=$(basename "$macro_dir")
|
|
||||||
echo "Packaging macro: $macro_name"
|
|
||||||
|
|
||||||
# Create destination directory
|
|
||||||
mkdir -p "staging/freecad-macros-${VERSION}/${macro_name}"
|
|
||||||
|
|
||||||
# Copy macro files (.FCMacro, .py, .svg icons, README*, LICENSE*)
|
|
||||||
find "$macro_dir" -maxdepth 1 \( \
|
|
||||||
-name "*.FCMacro" -o \
|
|
||||||
-name "*.py" -o \
|
|
||||||
-name "*.svg" -o \
|
|
||||||
-name "README*" -o \
|
|
||||||
-name "LICENSE*" \
|
|
||||||
\) -exec cp {} "staging/freecad-macros-${VERSION}/${macro_name}/" \;
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Copy top-level LICENSE file
|
|
||||||
if [ -f "LICENSE" ]; then
|
|
||||||
cp LICENSE "staging/freecad-macros-${VERSION}/"
|
|
||||||
else
|
|
||||||
echo "ERROR: No LICENSE file found at repository root"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add a top-level README
|
|
||||||
cat > "staging/freecad-macros-${VERSION}/README.md" << 'EOF'
|
|
||||||
# FreeCAD Macros
|
|
||||||
|
|
||||||
This archive contains FreeCAD macros from the freecad-mcp project.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Macro Files
|
|
||||||
|
|
||||||
Copy the macro files (`.FCMacro`) to your FreeCAD macro directory:
|
|
||||||
|
|
||||||
- **macOS**: `~/Library/Application Support/FreeCAD/Macro/`
|
|
||||||
- **Linux**: `~/.local/share/FreeCAD/Macro/`
|
|
||||||
- **Windows**: `%APPDATA%/FreeCAD/Macro/`
|
|
||||||
|
|
||||||
### Icons (Optional)
|
|
||||||
|
|
||||||
To display custom icons in the FreeCAD macro menu, copy the `.svg` files
|
|
||||||
to your FreeCAD macro directory alongside the `.FCMacro` files.
|
|
||||||
|
|
||||||
The icon file must have the same base name as the macro (e.g.,
|
|
||||||
`CutObjectForMagnets.FCMacro` and `CutObjectForMagnets.svg`).
|
|
||||||
|
|
||||||
## Included Macros
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# List included macros in the README
|
|
||||||
for macro_dir in "staging/freecad-macros-${VERSION}/"*/; do
|
|
||||||
if [ -d "$macro_dir" ]; then
|
|
||||||
macro_name=$(basename "$macro_dir")
|
|
||||||
echo "- **${macro_name}**: See ${macro_name}/README*.md for details" >> "staging/freecad-macros-${VERSION}/README.md"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
cat >> "staging/freecad-macros-${VERSION}/README.md" << 'EOF'
|
|
||||||
|
|
||||||
## More Information
|
|
||||||
|
|
||||||
For full documentation, visit:
|
|
||||||
https://github.com/spkane/freecad-robust-mcp-and-more
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License - see LICENSE file included in this archive.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Create the tar.gz archive
|
|
||||||
cd staging
|
|
||||||
tar -czvf "freecad-macros-${VERSION}.tar.gz" "freecad-macros-${VERSION}"
|
|
||||||
|
|
||||||
# Also create a zip for Windows users
|
|
||||||
zip -r "freecad-macros-${VERSION}.zip" "freecad-macros-${VERSION}"
|
|
||||||
|
|
||||||
# Move archives to workspace root
|
|
||||||
mv "freecad-macros-${VERSION}.tar.gz" ../
|
|
||||||
mv "freecad-macros-${VERSION}.zip" ../
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
echo "Created archives:"
|
|
||||||
ls -la "freecad-macros-${VERSION}."*
|
|
||||||
|
|
||||||
- name: Upload macro archives to release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
freecad-macros-${{ steps.version.outputs.version }}.tar.gz
|
|
||||||
freecad-macros-${{ steps.version.outputs.version }}.zip
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Generate summary
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "## FreeCAD Macros Release"
|
|
||||||
echo ""
|
|
||||||
echo "**Version:** ${VERSION}"
|
|
||||||
echo ""
|
|
||||||
echo "### Packaged Macros"
|
|
||||||
echo ""
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
|
|
||||||
for macro_dir in macros/*/; do
|
|
||||||
if [ -d "$macro_dir" ]; then
|
|
||||||
macro_name=$(basename "$macro_dir")
|
|
||||||
echo "- ${macro_name}" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
{
|
|
||||||
echo ""
|
|
||||||
echo "### Download"
|
|
||||||
echo ""
|
|
||||||
echo "The macro archives have been attached to the GitHub release:"
|
|
||||||
echo ""
|
|
||||||
echo "- \`freecad-macros-${VERSION}.tar.gz\` (Linux/macOS)"
|
|
||||||
echo "- \`freecad-macros-${VERSION}.zip\` (Windows)"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
@@ -19,6 +19,8 @@ on:
|
|||||||
- "addon/FreecadRobustMCP/**/*.py"
|
- "addon/FreecadRobustMCP/**/*.py"
|
||||||
- "tests/integration/**/*.py"
|
- "tests/integration/**/*.py"
|
||||||
- ".github/workflows/macro-test.yaml"
|
- ".github/workflows/macro-test.yaml"
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
# Cancel in-progress runs for the same branch
|
# Cancel in-progress runs for the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -0,0 +1,384 @@
|
|||||||
|
name: MCP Server Release
|
||||||
|
|
||||||
|
# Trigger on tag push matching the component-specific pattern
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'robust-mcp-server-v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
# Cancel in-progress runs for the same tag
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKERHUB_REPO: spkane/freecad-robust-mcp
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-tag:
|
||||||
|
name: Validate Tag Format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Validate semantic version tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Extract version from tag (robust-mcp-server-v1.2.3 -> 1.2.3)
|
||||||
|
if [[ ! "$TAG" =~ ^robust-mcp-server-v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)$ ]]; then
|
||||||
|
echo "ERROR: Tag '$TAG' does not match expected format (robust-mcp-server-vX.Y.Z or robust-mcp-server-vX.Y.Z-prerelease)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check if this is a prerelease
|
||||||
|
if [[ "$VERSION" =~ -[a-zA-Z0-9.]+ ]]; then
|
||||||
|
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
||||||
|
IS_PRERELEASE="true"
|
||||||
|
else
|
||||||
|
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||||
|
IS_PRERELEASE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Parsed version: $VERSION (prerelease: $IS_PRERELEASE)"
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build Distribution
|
||||||
|
needs: validate-tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: "latest"
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install 3.11
|
||||||
|
|
||||||
|
- name: Cache uv dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/uv
|
||||||
|
.venv
|
||||||
|
key: ${{ runner.os }}-uv-${{ hashFiles('pyproject.toml', 'uv.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-uv-
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: uv sync --all-extras
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
env:
|
||||||
|
# Override setuptools-scm version for component-specific tag
|
||||||
|
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.validate-tag.outputs.version }}
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Verify built package version
|
||||||
|
env:
|
||||||
|
TAG_VERSION: ${{ needs.validate-tag.outputs.version }}
|
||||||
|
run: |
|
||||||
|
# Extract version from built wheel filename
|
||||||
|
shopt -s nullglob
|
||||||
|
WHEEL_FILES=(dist/*.whl)
|
||||||
|
if [ ${#WHEEL_FILES[@]} -eq 0 ]; then
|
||||||
|
echo "ERROR: No wheel files found in dist/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
WHEEL_FILE="${WHEEL_FILES[0]}"
|
||||||
|
WHEEL_NAME=$(basename "$WHEEL_FILE")
|
||||||
|
|
||||||
|
# Extract version from wheel filename
|
||||||
|
WHEEL_VERSION=$(echo "$WHEEL_NAME" | sed -E 's/^[^-]+-([^-]+)-.*/\1/')
|
||||||
|
|
||||||
|
echo "Built wheel: $WHEEL_NAME"
|
||||||
|
echo "Wheel version: $WHEEL_VERSION"
|
||||||
|
echo "Expected tag version: $TAG_VERSION"
|
||||||
|
|
||||||
|
# Normalize the tag version for comparison (PEP 440)
|
||||||
|
NORMALIZED_TAG=$(echo "$TAG_VERSION" | sed -E '
|
||||||
|
s/-alpha\.([0-9]+)/a\1/
|
||||||
|
s/-alpha/a0/
|
||||||
|
s/-beta\.([0-9]+)/b\1/
|
||||||
|
s/-beta/b0/
|
||||||
|
s/-rc\.([0-9]+)/rc\1/
|
||||||
|
s/-rc/rc0/
|
||||||
|
')
|
||||||
|
|
||||||
|
echo "Normalized tag version: $NORMALIZED_TAG"
|
||||||
|
|
||||||
|
if [ "$WHEEL_VERSION" != "$NORMALIZED_TAG" ]; then
|
||||||
|
echo "ERROR: Built package version does not match git tag!"
|
||||||
|
echo " Wheel version: $WHEEL_VERSION"
|
||||||
|
echo " Expected (normalized): $NORMALIZED_TAG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Version verification passed!"
|
||||||
|
|
||||||
|
- name: Check package
|
||||||
|
run: uv run twine check dist/*
|
||||||
|
|
||||||
|
- name: List built artifacts
|
||||||
|
run: ls -la dist/
|
||||||
|
|
||||||
|
- name: Upload distribution artifacts
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
test-install:
|
||||||
|
name: Test Installation
|
||||||
|
needs: [validate-tag, build]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
python-version: ["3.11"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Download distribution artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: Install package from wheel
|
||||||
|
run: pip install dist/*.whl
|
||||||
|
|
||||||
|
- name: Verify installation
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
pip show freecad-robust-mcp
|
||||||
|
freecad-mcp --help
|
||||||
|
python -c "import freecad_mcp; print(f'freecad-robust-mcp version: {freecad_mcp.__version__ if hasattr(freecad_mcp, \"__version__\") else \"unknown\"}')"
|
||||||
|
|
||||||
|
publish-testpypi:
|
||||||
|
name: Publish to TestPyPI
|
||||||
|
needs: [validate-tag, build, test-install]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: contains(needs.validate-tag.outputs.version, '-alpha')
|
||||||
|
environment:
|
||||||
|
name: testpypi
|
||||||
|
url: https://test.pypi.org/p/freecad-robust-mcp
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download distribution artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: Publish to TestPyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
repository-url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
|
publish-pypi:
|
||||||
|
name: Publish to PyPI
|
||||||
|
needs: [validate-tag, build, test-install]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !contains(needs.validate-tag.outputs.version, '-alpha') }}
|
||||||
|
environment:
|
||||||
|
name: pypi
|
||||||
|
url: https://pypi.org/p/freecad-robust-mcp
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download distribution artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: Publish to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
|
docker-release:
|
||||||
|
name: Build and Push Docker Image
|
||||||
|
needs: [validate-tag, build, test-install]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.DOCKERHUB_REPO }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ needs.validate-tag.outputs.version }}
|
||||||
|
type=raw,value=${{ needs.validate-tag.outputs.version }},suffix=-mcp-server
|
||||||
|
type=raw,value=latest,enable=${{ needs.validate-tag.outputs.is_prerelease == 'false' }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=FreeCAD Robust MCP Server
|
||||||
|
org.opencontainers.image.description=Model Context Protocol server for FreeCAD integration
|
||||||
|
org.opencontainers.image.vendor=spkane
|
||||||
|
org.opencontainers.image.version=${{ needs.validate-tag.outputs.version }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
provenance: true
|
||||||
|
sbom: true
|
||||||
|
|
||||||
|
- name: Test pushed image
|
||||||
|
run: |
|
||||||
|
docker pull ${{ env.DOCKERHUB_REPO }}:${{ needs.validate-tag.outputs.version }}
|
||||||
|
echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
|
||||||
|
timeout 30 docker run --rm -i \
|
||||||
|
-e FREECAD_MODE=xmlrpc \
|
||||||
|
${{ env.DOCKERHUB_REPO }}:${{ needs.validate-tag.outputs.version }} 2>&1 | \
|
||||||
|
grep -q '"result"'
|
||||||
|
echo "Container test passed"
|
||||||
|
|
||||||
|
create-github-release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
needs: [validate-tag, build, test-install]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Download distribution artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: Extract changelog section
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.validate-tag.outputs.version }}"
|
||||||
|
# Match header exactly as it appears in CHANGELOG.md
|
||||||
|
HEADER="### MCP Server v${VERSION}"
|
||||||
|
|
||||||
|
# Extract section between this version header and the next component header or separator
|
||||||
|
# Only exit on: "---" separator OR "### " followed by component name (capital letter)
|
||||||
|
# This allows #### Added, #### Changed, etc. to be included
|
||||||
|
CHANGELOG_CONTENT=$(awk -v header="$HEADER" '
|
||||||
|
BEGIN { found=0 }
|
||||||
|
$0 == header || $0 == header " " { found=1; next }
|
||||||
|
found && /^---$/ { exit }
|
||||||
|
found && /^### [A-Z]/ { exit }
|
||||||
|
found { print }
|
||||||
|
' CHANGELOG.md)
|
||||||
|
|
||||||
|
# Write to file for the release body (handles multiline)
|
||||||
|
echo "$CHANGELOG_CONTENT" > changelog_section.md
|
||||||
|
|
||||||
|
# Check if we got content
|
||||||
|
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||||
|
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Extracted changelog section for $HEADER"
|
||||||
|
else
|
||||||
|
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "No changelog section found for $HEADER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: "MCP Server v${{ needs.validate-tag.outputs.version }}"
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
prerelease: ${{ needs.validate-tag.outputs.is_prerelease == 'true' }}
|
||||||
|
generate_release_notes: true
|
||||||
|
body_path: ${{ steps.changelog.outputs.found == 'true' && 'changelog_section.md' || '' }}
|
||||||
|
files: dist/*
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
generate-summary:
|
||||||
|
name: Generate Release Summary
|
||||||
|
needs: [validate-tag, publish-pypi, publish-testpypi, docker-release, create-github-release]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Generate release summary
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.validate-tag.outputs.version }}
|
||||||
|
IS_PRERELEASE: ${{ needs.validate-tag.outputs.is_prerelease }}
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## MCP Server Release Summary"
|
||||||
|
echo ""
|
||||||
|
echo "**Version:** $VERSION"
|
||||||
|
echo "**Prerelease:** $IS_PRERELEASE"
|
||||||
|
echo ""
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
if [[ "$VERSION" == *"-alpha"* ]]; then
|
||||||
|
{
|
||||||
|
echo "### Install from TestPyPI"
|
||||||
|
echo ""
|
||||||
|
echo "\`\`\`bash"
|
||||||
|
echo "pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ freecad-robust-mcp"
|
||||||
|
echo "\`\`\`"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo "### Install from PyPI"
|
||||||
|
echo ""
|
||||||
|
echo "\`\`\`bash"
|
||||||
|
echo "pip install freecad-robust-mcp"
|
||||||
|
echo "\`\`\`"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### Docker"
|
||||||
|
echo ""
|
||||||
|
echo "\`\`\`bash"
|
||||||
|
echo "docker pull spkane/freecad-robust-mcp:$VERSION"
|
||||||
|
echo "\`\`\`"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
name: MCP Workbench Release
|
||||||
|
|
||||||
|
# Trigger on tag push matching the component-specific pattern
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'robust-mcp-workbench-v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
# Cancel in-progress runs for the same tag
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-and-release:
|
||||||
|
name: Validate Tag and Create Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Validate semantic version tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Extract version from tag (robust-mcp-workbench-v1.2.3 -> 1.2.3)
|
||||||
|
if [[ ! "$TAG" =~ ^robust-mcp-workbench-v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)$ ]]; then
|
||||||
|
echo "ERROR: Tag '$TAG' does not match expected format (robust-mcp-workbench-vX.Y.Z or robust-mcp-workbench-vX.Y.Z-prerelease)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check if this is a prerelease
|
||||||
|
if [[ "$VERSION" =~ -[a-zA-Z0-9.]+ ]]; then
|
||||||
|
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Parsed version: $VERSION"
|
||||||
|
|
||||||
|
- name: Verify version in source files
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
echo "Verifying version in source files matches tag: $VERSION"
|
||||||
|
|
||||||
|
# Check __version__ in __init__.py
|
||||||
|
INIT_FILE="addon/FreecadRobustMCP/freecad_mcp_bridge/__init__.py"
|
||||||
|
INIT_VERSION=$(grep -o '__version__ = "[^"]*"' "$INIT_FILE" | cut -d'"' -f2)
|
||||||
|
if [ "$INIT_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $INIT_FILE"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $INIT_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "The version in source files must be updated before tagging."
|
||||||
|
echo "Run: just release::bump-workbench $VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ $INIT_FILE: $INIT_VERSION"
|
||||||
|
|
||||||
|
# Check package.xml
|
||||||
|
PKG_VERSION=$(awk '/<workbench>/,/<\/workbench>/' package.xml | grep -o '<version>[^<]*</version>' | head -1 | sed 's/<[^>]*>//g')
|
||||||
|
if [ "$PKG_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in package.xml (workbench section)"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $PKG_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ package.xml (workbench): $PKG_VERSION"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Version verification passed!"
|
||||||
|
|
||||||
|
- name: Create workbench archive
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
# Create staging directory
|
||||||
|
mkdir -p "staging/freecad-mcp-workbench-${VERSION}"
|
||||||
|
|
||||||
|
# Copy workbench files
|
||||||
|
cp -r addon/FreecadRobustMCP/* "staging/freecad-mcp-workbench-${VERSION}/"
|
||||||
|
|
||||||
|
# Copy LICENSE
|
||||||
|
cp LICENSE "staging/freecad-mcp-workbench-${VERSION}/"
|
||||||
|
|
||||||
|
# Create README for the archive
|
||||||
|
cat > "staging/freecad-mcp-workbench-${VERSION}/README.md" << EOF
|
||||||
|
# FreeCAD MCP Bridge Workbench
|
||||||
|
|
||||||
|
**Version:** ${VERSION}
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Via FreeCAD Addon Manager (Recommended)
|
||||||
|
|
||||||
|
1. Open FreeCAD
|
||||||
|
2. Go to **Tools → Addon Manager**
|
||||||
|
3. Search for "MCP Bridge"
|
||||||
|
4. Click **Install**
|
||||||
|
5. Restart FreeCAD
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
Copy the contents of this archive to your FreeCAD Mod directory:
|
||||||
|
|
||||||
|
- **macOS**: \`~/Library/Application Support/FreeCAD/Mod/FreecadRobustMCP/\`
|
||||||
|
- **Linux**: \`~/.local/share/FreeCAD/Mod/FreecadRobustMCP/\`
|
||||||
|
- **Windows**: \`%APPDATA%/FreeCAD/Mod/FreecadRobustMCP/\`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Switch to the **MCP Bridge** workbench
|
||||||
|
2. Click **Start MCP Bridge** in the toolbar
|
||||||
|
3. Connect your MCP client (Claude Code, etc.)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full documentation: https://github.com/spkane/freecad-robust-mcp-and-more
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create tar.gz archive
|
||||||
|
cd staging
|
||||||
|
tar -czvf "freecad-mcp-workbench-${VERSION}.tar.gz" "freecad-mcp-workbench-${VERSION}"
|
||||||
|
|
||||||
|
# Create zip archive
|
||||||
|
zip -r "freecad-mcp-workbench-${VERSION}.zip" "freecad-mcp-workbench-${VERSION}"
|
||||||
|
|
||||||
|
mv "freecad-mcp-workbench-${VERSION}.tar.gz" ../
|
||||||
|
mv "freecad-mcp-workbench-${VERSION}.zip" ../
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "Created archives:"
|
||||||
|
ls -la "freecad-mcp-workbench-${VERSION}."*
|
||||||
|
|
||||||
|
- name: Extract changelog section
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
# Match header exactly as it appears in CHANGELOG.md (with optional space before v)
|
||||||
|
HEADER="### MCP Bridge Workbench v${VERSION}"
|
||||||
|
|
||||||
|
# Extract section between this version header and the next component header or separator
|
||||||
|
# Only exit on: "---" separator OR "### " followed by component name (capital letter)
|
||||||
|
# This allows #### Added, #### Changed, etc. to be included
|
||||||
|
CHANGELOG_CONTENT=$(awk -v header="$HEADER" '
|
||||||
|
BEGIN { found=0 }
|
||||||
|
$0 == header || $0 == header " " { found=1; next }
|
||||||
|
found && /^---$/ { exit }
|
||||||
|
found && /^### [A-Z]/ { exit }
|
||||||
|
found { print }
|
||||||
|
' CHANGELOG.md)
|
||||||
|
|
||||||
|
# Build release body with changelog content if available
|
||||||
|
cat > release_body.md << 'STATIC_EOF'
|
||||||
|
## FreeCAD MCP Bridge Workbench v${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
|
This release contains the MCP Bridge workbench for FreeCAD.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
**Recommended:** Install via FreeCAD's Addon Manager (search for "MCP Bridge").
|
||||||
|
|
||||||
|
**Manual:** Download and extract to your FreeCAD Mod directory.
|
||||||
|
|
||||||
|
### What's Included
|
||||||
|
|
||||||
|
- MCP Bridge workbench for GUI and headless FreeCAD
|
||||||
|
- XML-RPC and JSON-RPC server support
|
||||||
|
- Toolbar commands for bridge control
|
||||||
|
|
||||||
|
See the [full documentation](https://github.com/spkane/freecad-robust-mcp-and-more) for usage instructions.
|
||||||
|
STATIC_EOF
|
||||||
|
|
||||||
|
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### Changelog"
|
||||||
|
echo ""
|
||||||
|
echo "$CHANGELOG_CONTENT"
|
||||||
|
} >> release_body.md
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: "MCP Bridge Workbench v${{ steps.version.outputs.version }}"
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
prerelease: ${{ steps.version.outputs.is_prerelease == 'true' }}
|
||||||
|
generate_release_notes: true
|
||||||
|
body_path: release_body.md
|
||||||
|
files: |
|
||||||
|
freecad-mcp-workbench-${{ steps.version.outputs.version }}.tar.gz
|
||||||
|
freecad-mcp-workbench-${{ steps.version.outputs.version }}.zip
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate summary
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "## MCP Bridge Workbench Release"
|
||||||
|
echo ""
|
||||||
|
echo "**Version:** ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
echo "### Downloads"
|
||||||
|
echo ""
|
||||||
|
echo "- \`freecad-mcp-workbench-${VERSION}.tar.gz\`"
|
||||||
|
echo "- \`freecad-mcp-workbench-${VERSION}.zip\`"
|
||||||
|
echo ""
|
||||||
|
echo "### Installation"
|
||||||
|
echo ""
|
||||||
|
echo "Install via FreeCAD Addon Manager or extract to your Mod directory."
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -5,6 +5,8 @@ on:
|
|||||||
branches: [main, master]
|
branches: [main, master]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, master]
|
branches: [main, master]
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
# Cancel in-progress runs for the same branch
|
# Cancel in-progress runs for the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -1,310 +0,0 @@
|
|||||||
name: PyPI Release
|
|
||||||
|
|
||||||
# Only trigger on GitHub release publication, not on tag push.
|
|
||||||
# When a release is created, GitHub fires both 'release' and 'push' events.
|
|
||||||
# Using only 'release' prevents duplicate workflow runs.
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
# Cancel in-progress runs for the same tag
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build Distribution
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Validate semantic version tag
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Validate semantic versioning format (v1.2.3 or v1.2.3-prerelease)
|
|
||||||
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
||||||
echo "ERROR: Tag '$TAG' does not follow semantic versioning (vX.Y.Z or vX.Y.Z-prerelease)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract version without 'v' prefix
|
|
||||||
VERSION="${TAG#v}"
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Check if this is a prerelease
|
|
||||||
if [[ "$TAG" =~ -[a-zA-Z0-9.]+ ]]; then
|
|
||||||
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
|
||||||
IS_PRERELEASE="true"
|
|
||||||
else
|
|
||||||
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
|
||||||
IS_PRERELEASE="false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Parsed version: $VERSION (prerelease: $IS_PRERELEASE)"
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
version: "latest"
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
run: uv python install 3.11
|
|
||||||
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: uv sync --all-extras
|
|
||||||
|
|
||||||
- name: Build package
|
|
||||||
run: uv build
|
|
||||||
|
|
||||||
- name: Verify built package version
|
|
||||||
env:
|
|
||||||
TAG_VERSION: ${{ steps.version.outputs.version }}
|
|
||||||
run: |
|
|
||||||
# Extract version from built wheel filename
|
|
||||||
# Wheel format: {distribution}-{version}(-{build tag})?-{python}-{abi}-{platform}.whl
|
|
||||||
shopt -s nullglob
|
|
||||||
WHEEL_FILES=(dist/*.whl)
|
|
||||||
if [ ${#WHEEL_FILES[@]} -eq 0 ]; then
|
|
||||||
echo "ERROR: No wheel files found in dist/"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
WHEEL_FILE="${WHEEL_FILES[0]}"
|
|
||||||
WHEEL_NAME=$(basename "$WHEEL_FILE")
|
|
||||||
|
|
||||||
# Extract version from wheel filename (second field after splitting by -)
|
|
||||||
# Handle PEP 440 version normalization (e.g., 0.5.0-beta -> 0.5.0b0)
|
|
||||||
WHEEL_VERSION=$(echo "$WHEEL_NAME" | sed -E 's/^[^-]+-([^-]+)-.*/\1/')
|
|
||||||
|
|
||||||
echo "Built wheel: $WHEEL_NAME"
|
|
||||||
echo "Wheel version: $WHEEL_VERSION"
|
|
||||||
echo "Expected tag version: $TAG_VERSION"
|
|
||||||
|
|
||||||
# Normalize the tag version for comparison (PEP 440)
|
|
||||||
# Convert common patterns: -alpha -> a0, -beta -> b0, -rc -> rc0
|
|
||||||
# Also handle numbered variants: -alpha.1 -> a1, -beta.2 -> b2, -rc.3 -> rc3
|
|
||||||
NORMALIZED_TAG=$(echo "$TAG_VERSION" | sed -E '
|
|
||||||
s/-alpha\.([0-9]+)/a\1/
|
|
||||||
s/-alpha/a0/
|
|
||||||
s/-beta\.([0-9]+)/b\1/
|
|
||||||
s/-beta/b0/
|
|
||||||
s/-rc\.([0-9]+)/rc\1/
|
|
||||||
s/-rc/rc0/
|
|
||||||
')
|
|
||||||
|
|
||||||
echo "Normalized tag version: $NORMALIZED_TAG"
|
|
||||||
|
|
||||||
if [ "$WHEEL_VERSION" != "$NORMALIZED_TAG" ]; then
|
|
||||||
echo "ERROR: Built package version does not match git tag!"
|
|
||||||
echo " Wheel version: $WHEEL_VERSION"
|
|
||||||
echo " Expected (normalized): $NORMALIZED_TAG"
|
|
||||||
echo ""
|
|
||||||
echo "This may indicate an issue with dynamic versioning configuration."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Version verification passed!"
|
|
||||||
|
|
||||||
- name: Check package
|
|
||||||
run: |
|
|
||||||
uv run twine check dist/*
|
|
||||||
|
|
||||||
- name: List built artifacts
|
|
||||||
run: ls -la dist/
|
|
||||||
|
|
||||||
- name: Upload distribution artifacts
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
test-install:
|
|
||||||
name: Test Installation
|
|
||||||
needs: build
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest]
|
|
||||||
python-version: ["3.11"]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Download distribution artifacts
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Install package from wheel
|
|
||||||
run: |
|
|
||||||
pip install dist/*.whl
|
|
||||||
|
|
||||||
- name: Verify installation
|
|
||||||
run: |
|
|
||||||
# Check that the package is installed
|
|
||||||
pip show freecad-robust-mcp
|
|
||||||
|
|
||||||
# Check that the CLI is available
|
|
||||||
freecad-mcp --help || echo "CLI help check completed"
|
|
||||||
|
|
||||||
# Check that the module can be imported
|
|
||||||
python -c "import freecad_mcp; print(f'freecad-robust-mcp version: {freecad_mcp.__version__ if hasattr(freecad_mcp, \"__version__\") else \"unknown\"}')"
|
|
||||||
|
|
||||||
publish-testpypi:
|
|
||||||
name: Publish to TestPyPI
|
|
||||||
needs: [build, test-install]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Only publish alpha releases to TestPyPI for early testing.
|
|
||||||
# Beta and RC releases go to PyPI since they are closer to stable.
|
|
||||||
# This is intentional - do not change to contains(github.ref, '-').
|
|
||||||
if: contains(github.ref, '-alpha')
|
|
||||||
environment:
|
|
||||||
name: testpypi
|
|
||||||
url: https://test.pypi.org/p/freecad-robust-mcp
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download distribution artifacts
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Publish to TestPyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
repository-url: https://test.pypi.org/legacy/
|
|
||||||
|
|
||||||
publish-pypi:
|
|
||||||
name: Publish to PyPI
|
|
||||||
needs: [build, test-install]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Publish beta, rc, and stable releases to PyPI (not alpha).
|
|
||||||
# Alpha releases are too experimental for PyPI - they go to TestPyPI only.
|
|
||||||
# This is intentional - do not change to !contains(github.ref, '-').
|
|
||||||
if: ${{ !contains(github.ref, '-alpha') }}
|
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/p/freecad-robust-mcp
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download distribution artifacts
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Publish to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
|
|
||||||
create-github-release-assets:
|
|
||||||
name: Add Assets to GitHub Release
|
|
||||||
needs: [build, test-install]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download distribution artifacts
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Upload to GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: dist/*
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
generate-summary:
|
|
||||||
name: Generate Release Summary
|
|
||||||
needs: [publish-pypi, publish-testpypi]
|
|
||||||
if: always()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Generate release summary
|
|
||||||
env:
|
|
||||||
REF_NAME: ${{ github.ref_name }}
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
echo "## PyPI Release Summary"
|
|
||||||
echo ""
|
|
||||||
echo "**Tag:** $REF_NAME"
|
|
||||||
echo ""
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
|
|
||||||
# Alpha releases go to TestPyPI only; beta, rc, and stable go to PyPI
|
|
||||||
if [[ "$REF_NAME" == *"-alpha"* ]]; then
|
|
||||||
{
|
|
||||||
echo "**Type:** Alpha prerelease (published to TestPyPI only)"
|
|
||||||
echo ""
|
|
||||||
echo "### Install from TestPyPI"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
elif [[ "$REF_NAME" == *"-beta"* ]]; then
|
|
||||||
{
|
|
||||||
echo "**Type:** Beta prerelease (published to PyPI)"
|
|
||||||
echo ""
|
|
||||||
echo "### Install from PyPI"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
echo ""
|
|
||||||
echo "Or with uv:"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "uv pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
elif [[ "$REF_NAME" == *"-rc"* ]]; then
|
|
||||||
{
|
|
||||||
echo "**Type:** Release candidate (published to PyPI)"
|
|
||||||
echo ""
|
|
||||||
echo "### Install from PyPI"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
echo ""
|
|
||||||
echo "Or with uv:"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "uv pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo "**Type:** Stable release (published to PyPI)"
|
|
||||||
echo ""
|
|
||||||
echo "### Install from PyPI"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
echo ""
|
|
||||||
echo "Or with uv:"
|
|
||||||
echo ""
|
|
||||||
echo "\`\`\`bash"
|
|
||||||
echo "uv pip install freecad-robust-mcp"
|
|
||||||
echo "\`\`\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
fi
|
|
||||||
@@ -19,6 +19,8 @@ on:
|
|||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "uv.lock"
|
- "uv.lock"
|
||||||
- ".github/workflows/test.yaml"
|
- ".github/workflows/test.yaml"
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
# Cancel in-progress runs for the same branch
|
# Cancel in-progress runs for the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
+13
-4
@@ -20,6 +20,7 @@ repos:
|
|||||||
args: [--unsafe]
|
args: [--unsafe]
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-json
|
- id: check-json
|
||||||
|
exclude: ^\.vscode/.*\.json$ # VS Code uses JSONC (JSON with Comments)
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
args: [--maxkb=1000]
|
args: [--maxkb=1000]
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
@@ -36,6 +37,13 @@ repos:
|
|||||||
types: [text]
|
types: [text]
|
||||||
files: \.(py|FCMacro)$
|
files: \.(py|FCMacro)$
|
||||||
|
|
||||||
|
# JSON5/JSONC validation for VS Code config files (supports comments)
|
||||||
|
- repo: https://github.com/maresb/check-json5
|
||||||
|
rev: v1.0.1
|
||||||
|
hooks:
|
||||||
|
- id: check-json5
|
||||||
|
files: ^\.vscode/.*\.json$ # Only check VS Code JSONC files
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# Python - Linting and Formatting
|
# Python - Linting and Formatting
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
@@ -81,14 +89,15 @@ repos:
|
|||||||
# Checks installed packages against known security vulnerabilities
|
# Checks installed packages against known security vulnerabilities
|
||||||
# Local: Requires free safetycli.com account. Run `uv run safety auth` first.
|
# Local: Requires free safetycli.com account. Run `uv run safety auth` first.
|
||||||
# CI: Uses SAFETY_API_KEY secret passed via environment variable.
|
# CI: Uses SAFETY_API_KEY secret passed via environment variable.
|
||||||
|
# Config: .safety-policy.yml (excludes .venv, node_modules, etc.)
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: safety
|
- id: safety
|
||||||
name: safety (dependency vulnerabilities)
|
name: safety (dependency vulnerabilities)
|
||||||
entry: uv run safety scan --detailed-output
|
entry: uv run safety scan --policy-file .safety-policy.yml --detailed-output
|
||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^(pyproject\.toml|uv\.lock)$
|
files: ^(pyproject\.toml|uv\.lock|\.safety-policy\.yml)$
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# Secrets Detection - Multi-Layer Approach
|
# Secrets Detection - Multi-Layer Approach
|
||||||
@@ -161,7 +170,7 @@ repos:
|
|||||||
- id: md-toc
|
- id: md-toc
|
||||||
name: md-toc (table of contents)
|
name: md-toc (table of contents)
|
||||||
args: ["-p", "github", "-l", "6"] # GitHub parser, max 6 levels
|
args: ["-p", "github", "-l", "6"] # GitHub parser, max 6 levels
|
||||||
files: ^(README|ARCHITECTURE-MCP)\.md$
|
files: ^(README|docs/development/architecture-detailed)\.md$
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# Spell Checking
|
# Spell Checking
|
||||||
@@ -270,7 +279,7 @@ repos:
|
|||||||
# 2. Authenticate: just coderabbit::login
|
# 2. Authenticate: just coderabbit::login
|
||||||
#
|
#
|
||||||
# USAGE:
|
# USAGE:
|
||||||
# - Run manually: just review (or just coderabbit::review)
|
# - Run manually: just coderabbit::review
|
||||||
# - Run via pre-commit: uv run pre-commit run coderabbit --all-files
|
# - Run via pre-commit: uv run pre-commit run coderabbit --all-files
|
||||||
#
|
#
|
||||||
# NOTE: This hook uses 'manual' stage so it doesn't run automatically.
|
# NOTE: This hook uses 'manual' stage so it doesn't run automatically.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Safety CLI 3.x Policy File
|
||||||
|
# https://docs.safetycli.com/safety-docs/administration/safety-policy-files
|
||||||
|
version: "3.0"
|
||||||
|
|
||||||
|
scanning-settings:
|
||||||
|
# Maximum directory depth to scan
|
||||||
|
max-depth: 6
|
||||||
|
|
||||||
|
# Exclude virtual environments and other non-project directories
|
||||||
|
exclude:
|
||||||
|
- ".venv"
|
||||||
|
- "venv"
|
||||||
|
- ".git"
|
||||||
|
- "node_modules"
|
||||||
|
- "__pycache__"
|
||||||
|
- "*.egg-info"
|
||||||
|
- "dist"
|
||||||
|
- "build"
|
||||||
|
- "site" # MkDocs build output
|
||||||
+123
-40
@@ -1,48 +1,131 @@
|
|||||||
{
|
{
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"plugins_used": [
|
"plugins_used": [
|
||||||
{"name": "ArtifactoryDetector"},
|
{
|
||||||
{"name": "AWSKeyDetector"},
|
"name": "ArtifactoryDetector"
|
||||||
{"name": "AzureStorageKeyDetector"},
|
},
|
||||||
{"name": "Base64HighEntropyString", "limit": 4.5},
|
{
|
||||||
{"name": "BasicAuthDetector"},
|
"name": "AWSKeyDetector"
|
||||||
{"name": "CloudantDetector"},
|
},
|
||||||
{"name": "DiscordBotTokenDetector"},
|
{
|
||||||
{"name": "GitHubTokenDetector"},
|
"name": "AzureStorageKeyDetector"
|
||||||
{"name": "GitLabTokenDetector"},
|
},
|
||||||
{"name": "HexHighEntropyString", "limit": 3.0},
|
{
|
||||||
{"name": "IbmCloudIamDetector"},
|
"name": "Base64HighEntropyString",
|
||||||
{"name": "IbmCosHmacDetector"},
|
"limit": 4.5
|
||||||
{"name": "IPPublicDetector"},
|
},
|
||||||
{"name": "JwtTokenDetector"},
|
{
|
||||||
{"name": "KeywordDetector", "keyword_exclude": ""},
|
"name": "BasicAuthDetector"
|
||||||
{"name": "MailchimpDetector"},
|
},
|
||||||
{"name": "NpmDetector"},
|
{
|
||||||
{"name": "OpenAIDetector"},
|
"name": "CloudantDetector"
|
||||||
{"name": "PrivateKeyDetector"},
|
},
|
||||||
{"name": "PypiTokenDetector"},
|
{
|
||||||
{"name": "SendGridDetector"},
|
"name": "DiscordBotTokenDetector"
|
||||||
{"name": "SlackDetector"},
|
},
|
||||||
{"name": "SoftlayerDetector"},
|
{
|
||||||
{"name": "SquareOAuthDetector"},
|
"name": "GitHubTokenDetector"
|
||||||
{"name": "StripeDetector"},
|
},
|
||||||
{"name": "TelegramBotTokenDetector"},
|
{
|
||||||
{"name": "TwilioKeyDetector"}
|
"name": "GitLabTokenDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HexHighEntropyString",
|
||||||
|
"limit": 3.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IbmCloudIamDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IbmCosHmacDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IPPublicDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JwtTokenDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KeywordDetector",
|
||||||
|
"keyword_exclude": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MailchimpDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NpmDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenAIDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PrivateKeyDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PypiTokenDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SendGridDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SlackDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SoftlayerDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SquareOAuthDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "StripeDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TelegramBotTokenDetector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TwilioKeyDetector"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"filters_used": [
|
"filters_used": [
|
||||||
{"path": "detect_secrets.filters.allowlist.is_line_allowlisted"},
|
{
|
||||||
{"path": "detect_secrets.filters.common.is_baseline_file", "filename": ".secrets.baseline"},
|
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
||||||
{"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2},
|
},
|
||||||
{"path": "detect_secrets.filters.heuristic.is_indirect_reference"},
|
{
|
||||||
{"path": "detect_secrets.filters.heuristic.is_likely_id_string"},
|
"path": "detect_secrets.filters.common.is_baseline_file",
|
||||||
{"path": "detect_secrets.filters.heuristic.is_lock_file"},
|
"filename": "/Users/spkane/dev/spkane/freecad-mcp-and-more/.secrets.baseline"
|
||||||
{"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"},
|
},
|
||||||
{"path": "detect_secrets.filters.heuristic.is_potential_uuid"},
|
{
|
||||||
{"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"},
|
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
||||||
{"path": "detect_secrets.filters.heuristic.is_sequential_string"},
|
"min_level": 2
|
||||||
{"path": "detect_secrets.filters.heuristic.is_swagger_file"},
|
},
|
||||||
{"path": "detect_secrets.filters.heuristic.is_templated_secret"}
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"results": {},
|
"results": {},
|
||||||
"generated_at": "2025-01-01T00:00:00Z"
|
"generated_at": "2026-01-07T11:33:09Z"
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+27
-3
@@ -1,12 +1,36 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"streetsidesoftware.code-spell-checker",
|
|
||||||
|
// Python development
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
|
"ms-python.mypy-type-checker",
|
||||||
"charliermarsh.ruff",
|
"charliermarsh.ruff",
|
||||||
"DavidAnson.vscode-markdownlint",
|
|
||||||
|
// Just (command runner)
|
||||||
|
"skellock.just",
|
||||||
|
|
||||||
|
// Docker
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"exiasr.hadolint",
|
||||||
|
|
||||||
|
// GitHub Actions
|
||||||
|
"GitHub.vscode-github-actions",
|
||||||
|
|
||||||
|
// Configuration files
|
||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml",
|
||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
"eamodio.gitlens"
|
|
||||||
|
// Markdown
|
||||||
|
"DavidAnson.vscode-markdownlint",
|
||||||
|
|
||||||
|
// Shell scripts
|
||||||
|
"timonwong.shellcheck",
|
||||||
|
|
||||||
|
// Git
|
||||||
|
"eamodio.gitlens",
|
||||||
|
|
||||||
|
// Spell checking
|
||||||
|
"streetsidesoftware.code-spell-checker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -12,6 +12,7 @@
|
|||||||
"contextform",
|
"contextform",
|
||||||
"docstrings",
|
"docstrings",
|
||||||
"dylib",
|
"dylib",
|
||||||
|
"exiasr",
|
||||||
"fcstd",
|
"fcstd",
|
||||||
"freecad",
|
"freecad",
|
||||||
"FreeCAD",
|
"FreeCAD",
|
||||||
|
|||||||
+81
-45
@@ -5,23 +5,36 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
<!--
|
This is a multi-component project. Each component has its own versioning and release cycle:
|
||||||
IMPORTANT: Update this file at the end of any session where repository files are modified.
|
|
||||||
Group changes under: Added, Changed, Deprecated, Removed, Fixed, Security
|
| Component | Tag Format | Distribution |
|
||||||
-->
|
| ------------------------------ | ------------------------------------- | ------------------------ |
|
||||||
|
| MCP Server | `robust-mcp-server-vX.Y.Z` | PyPI, Docker Hub, GitHub |
|
||||||
|
| MCP Bridge Workbench | `robust-mcp-workbench-vX.Y.Z` | GitHub Release |
|
||||||
|
| Cut Object for Magnets Macro | `macro-cut-object-for-magnets-vX.Y.Z` | GitHub Release |
|
||||||
|
| Multi Export Macro | `macro-multi-export-vX.Y.Z` | GitHub Release |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
<!-- This section captures changes that have been merged but not yet released.
|
### MCP Server
|
||||||
When preparing a new release, move these entries to a new version section below. -->
|
|
||||||
|
|
||||||
## [0.5.0-beta] - 2026-01-05
|
### MCP Bridge Workbench
|
||||||
|
|
||||||
Initial public beta release of the FreeCAD MCP Server and Macros.
|
### Cut Object for Magnets Macro
|
||||||
|
|
||||||
### Added
|
### Multi Export Macro
|
||||||
|
|
||||||
#### MCP Server for FreeCAD
|
---
|
||||||
|
|
||||||
|
## Initial Public Beta - 2026-01-05
|
||||||
|
|
||||||
|
### MCP Server v0.5.0-beta
|
||||||
|
|
||||||
|
Initial public beta release.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
- **Document management**: Create, open, save, close, recompute documents
|
- **Document management**: Create, open, save, close, recompute documents
|
||||||
- **Object manipulation**: Create primitives (box, cylinder, sphere, cone, torus, wedge, helix), boolean operations, transforms (scale, rotate, copy, mirror)
|
- **Object manipulation**: Create primitives (box, cylinder, sphere, cone, torus, wedge, helix), boolean operations, transforms (scale, rotate, copy, mirror)
|
||||||
@@ -30,48 +43,71 @@ Initial public beta release of the FreeCAD MCP Server and Macros.
|
|||||||
- **View control**: Screenshots, camera positioning, zoom, visibility, display modes
|
- **View control**: Screenshots, camera positioning, zoom, visibility, display modes
|
||||||
- **Macro support**: List, run, create, and manage FreeCAD macros
|
- **Macro support**: List, run, create, and manage FreeCAD macros
|
||||||
- **Undo/Redo**: Full undo/redo support with status queries
|
- **Undo/Redo**: Full undo/redo support with status queries
|
||||||
|
- **Connection modes**: XML-RPC (recommended), Socket, and Embedded (Linux only)
|
||||||
|
|
||||||
#### Connection Modes
|
#### Installation
|
||||||
|
|
||||||
| Mode | Description | Platform Support |
|
|
||||||
| ---------- | ----------------------------- | --------------------------- |
|
|
||||||
| `xmlrpc` | XML-RPC protocol (port 9875) | All platforms (recommended) |
|
|
||||||
| `socket` | JSON-RPC socket (port 9876) | All platforms |
|
|
||||||
| `embedded` | In-process FreeCAD | Linux only |
|
|
||||||
|
|
||||||
#### FreeCAD Macros
|
|
||||||
|
|
||||||
- **CutObjectForMagnets**: Cut objects in half with aligned magnet holes for easy reassembly
|
|
||||||
- Smart hole placement with overlap detection
|
|
||||||
- Support for solid and hollow objects
|
|
||||||
- Parametric PartDesign::Hole or boolean hole methods
|
|
||||||
- **MultiExport**: Export selected objects to multiple formats simultaneously
|
|
||||||
- Supports STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF
|
|
||||||
- Configurable mesh tolerance for STL/3MF
|
|
||||||
- Batch export with progress feedback
|
|
||||||
|
|
||||||
#### Installation Options
|
|
||||||
|
|
||||||
- **PyPI**: `pip install freecad-robust-mcp`
|
- **PyPI**: `pip install freecad-robust-mcp`
|
||||||
- **Docker**: `docker pull ghcr.io/spkane/freecad-robust-mcp`
|
- **Docker**: `docker pull ghcr.io/spkane/freecad-robust-mcp`
|
||||||
- **Source**: Clone and install with `uv sync --all-extras`
|
- **Source**: Clone and install with `uv sync --all-extras`
|
||||||
|
|
||||||
#### CI/CD & Tooling
|
#### Known Limitations
|
||||||
|
|
||||||
- GitHub Actions workflows for testing, Docker builds, and PyPI releases
|
|
||||||
- Pre-commit hooks with Ruff, MyPy, Bandit, and secrets detection
|
|
||||||
- Dependabot for automated dependency updates
|
|
||||||
- CodeQL security scanning
|
|
||||||
|
|
||||||
#### Documentation
|
|
||||||
|
|
||||||
- Comprehensive README with installation and configuration guides
|
|
||||||
- CLAUDE.md with AI assistant guidelines
|
|
||||||
- ARCHITECTURE.md with system design documentation
|
|
||||||
- MCP tools reference with examples
|
|
||||||
|
|
||||||
### Known Limitations
|
|
||||||
|
|
||||||
- Embedded mode only works on Linux (macOS/Windows must use xmlrpc or socket)
|
- Embedded mode only works on Linux (macOS/Windows must use xmlrpc or socket)
|
||||||
- Python 3.11 required (must match FreeCAD's bundled Python for ABI compatibility)
|
- Python 3.11 required (must match FreeCAD's bundled Python for ABI compatibility)
|
||||||
- GUI features (screenshots, visibility) not available in headless mode
|
- GUI features (screenshots, visibility) not available in headless mode
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MCP Bridge Workbench v0.5.0-beta
|
||||||
|
|
||||||
|
Initial public beta release.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
- **FreeCAD Workbench**: Installable via FreeCAD Addon Manager
|
||||||
|
- **XML-RPC server**: Runs on port 9875 for MCP server communication
|
||||||
|
- **Socket server**: Alternative JSON-RPC on port 9876
|
||||||
|
- **Auto-start option**: Configure bridge to start with FreeCAD
|
||||||
|
- **GUI mode support**: Full 3D view integration with screenshots
|
||||||
|
- **Headless mode support**: Console-only operation for automation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Cut Object for Magnets Macro v0.5.0-beta
|
||||||
|
|
||||||
|
Initial public beta release.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
- **Smart cutting**: Cut objects in half with aligned magnet holes for easy reassembly
|
||||||
|
- **Hole placement**: Automatic hole placement with overlap detection
|
||||||
|
- **Object support**: Works with solid and hollow objects
|
||||||
|
- **Hole methods**: Parametric PartDesign::Hole or boolean hole methods
|
||||||
|
- **GUI dialog**: Interactive parameter configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Multi Export Macro v0.5.0-beta
|
||||||
|
|
||||||
|
Initial public beta release.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
- **Multi-format export**: Export selected objects to multiple formats simultaneously
|
||||||
|
- **Format support**: STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF
|
||||||
|
- **Mesh control**: Configurable mesh tolerance for STL/3MF
|
||||||
|
- **Batch export**: Export multiple objects with progress feedback
|
||||||
|
- **GUI dialog**: Interactive format and option selection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Infrastructure (v0.5.0-beta)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **CI/CD**: GitHub Actions workflows for testing, Docker builds, and PyPI releases
|
||||||
|
- **Quality**: Pre-commit hooks with Ruff, MyPy, Bandit, and secrets detection
|
||||||
|
- **Dependencies**: Dependabot for automated dependency updates
|
||||||
|
- **Security**: CodeQL security scanning
|
||||||
|
- **Documentation**: Comprehensive README, CLAUDE.md, and architecture docs
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ FreeCAD's `FreeCAD.so` library links to `@rpath/libpython3.11.dylib` (FreeCAD's
|
|||||||
1. Start FreeCAD and start the MCP bridge:
|
1. Start FreeCAD and start the MCP bridge:
|
||||||
|
|
||||||
- Install the MCP Bridge workbench via Addon Manager, or
|
- Install the MCP Bridge workbench via Addon Manager, or
|
||||||
- Use `just run-gui` from the source repository
|
- Use `just freecad::run-gui` from the source repository
|
||||||
|
|
||||||
1. The MCP server will then connect to FreeCAD over the network
|
1. The MCP server will then connect to FreeCAD over the network
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ uv run safety auth
|
|||||||
uv run safety auth --login
|
uv run safety auth --login
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** The Safety CLI authentication is stored locally and only needs to be done once per machine. If you skip this step, `just check` will show a clear error message with instructions. The `safety` pre-commit hook will also fail with an authentication prompt.
|
**Note:** The Safety CLI authentication is stored locally and only needs to be done once per machine. If you skip this step, `just quality::check` will show a clear error message with instructions. The `safety` pre-commit hook will also fail with an authentication prompt.
|
||||||
|
|
||||||
**CI/CD:** Safety runs in CI using the `SAFETY_API_KEY` repository secret. The API key is passed via environment variable to the pre-commit hook.
|
**CI/CD:** Safety runs in CI using the `SAFETY_API_KEY` repository secret. The API key is passed via environment variable to the pre-commit hook.
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ just coderabbit::login
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Review staged changes (most common)
|
# Review staged changes (most common)
|
||||||
just review
|
just coderabbit::review
|
||||||
|
|
||||||
# Review with auto-fix suggestions
|
# Review with auto-fix suggestions
|
||||||
just coderabbit::review-fix
|
just coderabbit::review-fix
|
||||||
@@ -149,45 +149,112 @@ This project uses [`just`](https://just.systems/) as a command runner. Always pr
|
|||||||
Commands are organized into modules for better organization:
|
Commands are organized into modules for better organization:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List all available commands
|
# List top-level commands and available modules
|
||||||
just --list
|
just
|
||||||
|
|
||||||
# List commands in a specific module
|
# List commands in a specific module (use list-<module>)
|
||||||
just --list docker
|
just list-mcp # MCP server commands
|
||||||
just --list quality
|
just list-freecad # FreeCAD plugin/macro commands
|
||||||
just --list testing
|
just list-install # Installation commands
|
||||||
just --list freecad
|
just list-quality # Code quality commands
|
||||||
just --list documentation
|
just list-testing # Test commands
|
||||||
just --list coderabbit
|
just list-docker # Docker commands
|
||||||
|
just list-documentation # Documentation commands
|
||||||
|
just list-dev # Development utilities
|
||||||
|
just list-release # Release and tagging commands
|
||||||
|
just list-coderabbit # AI code review commands
|
||||||
|
|
||||||
# Common shortcut commands (aliases to module commands)
|
# List ALL commands from all modules at once
|
||||||
just install # Install project dependencies
|
just list-all
|
||||||
just lint # Run all linters (quality::lint)
|
|
||||||
just format # Format code (quality::format)
|
|
||||||
just test # Run tests (testing::unit)
|
|
||||||
just check # Run pre-commit checks (quality::check)
|
|
||||||
just docs # Build documentation (documentation::build)
|
|
||||||
just all # Run all checks and tests
|
|
||||||
|
|
||||||
# Module commands (use :: syntax)
|
# MCP server commands
|
||||||
|
just mcp::run # Run the MCP server (stdio mode)
|
||||||
|
just mcp::run-debug # Run with debug logging
|
||||||
|
just mcp::run-http # Run in HTTP mode for remote access
|
||||||
|
|
||||||
|
# FreeCAD commands
|
||||||
|
just freecad::run-gui # Run FreeCAD GUI with MCP bridge
|
||||||
|
just freecad::run-headless # Run FreeCAD headless with MCP bridge
|
||||||
|
|
||||||
|
# Installation commands (for end users)
|
||||||
|
just install::mcp-server # Install MCP server system-wide (via uv tool)
|
||||||
|
just install::mcp-bridge-workbench # Install FreeCAD workbench addon
|
||||||
|
just install::macro-all # Install all macros
|
||||||
|
just install::macro-cut # Install CutObjectForMagnets macro
|
||||||
|
just install::macro-export # Install MultiExport macro
|
||||||
|
just install::status # Check installation status
|
||||||
|
|
||||||
|
# Quality commands
|
||||||
|
just quality::check # Run all pre-commit checks
|
||||||
|
just quality::lint # Run linting
|
||||||
|
just quality::format # Format code
|
||||||
|
just quality::typecheck # Run type checking
|
||||||
|
just quality::security # Run security scanning
|
||||||
|
just quality::scan # Run all secrets scanners
|
||||||
|
|
||||||
|
# Testing commands
|
||||||
|
just testing::unit # Run unit tests
|
||||||
|
just testing::cov # Run tests with coverage
|
||||||
|
just testing::fast # Run tests without slow markers
|
||||||
|
just testing::integration # Run integration tests
|
||||||
|
just testing::integration-freecad # Integration tests with auto FreeCAD startup
|
||||||
|
just testing::watch # Run tests in watch mode
|
||||||
|
just testing::all # Run all tests including integration
|
||||||
|
|
||||||
|
# Documentation commands
|
||||||
|
just documentation::build # Build documentation
|
||||||
|
just documentation::serve # Serve documentation locally
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
just docker::build # Build Docker image for local architecture
|
just docker::build # Build Docker image for local architecture
|
||||||
just docker::build-multi # Build multi-arch image (amd64 + arm64)
|
just docker::build-multi # Build multi-arch image (amd64 + arm64)
|
||||||
just docker::run # Run Docker container
|
just docker::run # Run Docker container
|
||||||
just quality::secrets # Run all secrets scanners
|
just docker::clean # Remove local Docker image
|
||||||
just testing::cov # Run tests with coverage
|
just docker::clean-all # Remove images and build cache
|
||||||
just freecad::run-gui # Run FreeCAD GUI with MCP bridge
|
|
||||||
just documentation::serve # Serve documentation locally
|
# Development utilities
|
||||||
|
just dev::install-deps # Install all project dependencies
|
||||||
|
just dev::install-pre-commit # Install pre-commit hooks
|
||||||
|
just dev::update-deps # Update all dependencies
|
||||||
|
just dev::clean # Clean build artifacts and caches
|
||||||
|
just dev::repl # Open Python REPL with project loaded
|
||||||
|
just dev::tree # Show project structure
|
||||||
|
just dev::validate # Validate project configuration
|
||||||
|
|
||||||
|
# AI code review commands
|
||||||
|
just coderabbit::review # Review staged changes
|
||||||
|
just coderabbit::review-fix # Review with auto-fix suggestions
|
||||||
|
|
||||||
|
# Release commands (component-specific tagging)
|
||||||
|
just release::status # Show unreleased changes across all components
|
||||||
|
just release::tag-mcp-server 1.0.0 # Release MCP server (PyPI + Docker)
|
||||||
|
just release::tag-workbench 1.0.0 # Release MCP Bridge workbench
|
||||||
|
just release::tag-macro-magnets 1.0.0 # Release Cut Object for Magnets macro
|
||||||
|
just release::tag-macro-export 1.0.0 # Release Multi Export macro
|
||||||
|
just release::list-tags # List all release tags
|
||||||
|
just release::latest-versions # Show latest version of each component
|
||||||
|
just release::delete-tag <tag> # Delete a release tag (local and remote)
|
||||||
|
|
||||||
|
# Combined workflows
|
||||||
|
just setup # Full dev setup (install deps + hooks)
|
||||||
|
just all # Run all quality checks and unit tests
|
||||||
|
just all-with-integration # Run all checks + integration tests
|
||||||
|
just ci # Full CI pipeline (checks + coverage)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Just Module Structure
|
#### Just Module Structure
|
||||||
|
|
||||||
| Module | Description | Key Commands |
|
| Module | Description | Key Commands |
|
||||||
| --------------- | ----------------------------------- | -------------------------------------------- |
|
| --------------- | ------------------------------------- | --------------------------------------------------- |
|
||||||
| `docker` | Docker build and run commands | `build`, `build-multi`, `build-push`, `run` |
|
| `mcp` | MCP server commands | `run`, `run-debug`, `run-http` |
|
||||||
| `quality` | Code quality and linting | `check`, `lint`, `format`, `secrets` |
|
| `freecad` | FreeCAD running commands | `run-gui`, `run-headless`, `run-gui-custom` |
|
||||||
| `testing` | Test execution | `unit`, `cov`, `integration`, `all` |
|
| `install` | User installation commands | `mcp-server`, `mcp-bridge-workbench`, `macro-all` |
|
||||||
| `freecad` | FreeCAD plugin and macro management | `run-gui`, `run-headless`, `install-*-macro` |
|
| `quality` | Code quality and linting | `check`, `lint`, `format`, `scan` |
|
||||||
|
| `testing` | Test execution | `unit`, `cov`, `integration-freecad`, `watch` |
|
||||||
|
| `docker` | Docker build and run commands | `build`, `build-multi`, `run`, `clean-all` |
|
||||||
| `documentation` | Documentation building | `build`, `serve`, `open` |
|
| `documentation` | Documentation building | `build`, `serve`, `open` |
|
||||||
|
| `dev` | Development utilities | `install-deps`, `update-deps`, `clean` |
|
||||||
|
| `release` | Release and tagging | `status`, `tag-mcp-server`, `delete-tag` |
|
||||||
| `coderabbit` | AI code reviews (local) | `install`, `login`, `review`, `review-fix` |
|
| `coderabbit` | AI code reviews (local) | `install`, `login`, `review`, `review-fix` |
|
||||||
|
|
||||||
Module files are located in the `just/` directory.
|
Module files are located in the `just/` directory.
|
||||||
@@ -200,20 +267,51 @@ Module files are located in the `just/` directory.
|
|||||||
|
|
||||||
**CRITICAL**: This project uses `pre-commit` for all code quality checks. Before finishing ANY code changes:
|
**CRITICAL**: This project uses `pre-commit` for all code quality checks. Before finishing ANY code changes:
|
||||||
|
|
||||||
1. Run `just check` or `uv run pre-commit run --all-files`
|
1. Run `just quality::check` or `uv run pre-commit run --all-files`
|
||||||
1. Fix ALL issues reported
|
1. Fix ALL issues reported
|
||||||
1. Re-run until all checks pass
|
1. Re-run until all checks pass
|
||||||
|
|
||||||
Pre-commit runs these checks:
|
Pre-commit runs these checks:
|
||||||
|
|
||||||
|
**Python Quality:**
|
||||||
|
|
||||||
- **Ruff**: Linting and import sorting (replaces flake8, isort, pyupgrade)
|
- **Ruff**: Linting and import sorting (replaces flake8, isort, pyupgrade)
|
||||||
- **Ruff Format**: Code formatting (replaces black)
|
- **Ruff Format**: Code formatting (replaces black)
|
||||||
- **MyPy**: Static type checking
|
- **MyPy**: Static type checking
|
||||||
- **Bandit**: Security vulnerability scanning
|
- **Bandit**: Security vulnerability scanning
|
||||||
- **Safety**: Dependency vulnerability checking
|
- **Safety**: Dependency vulnerability checking (requires `.safety-policy.yml`)
|
||||||
|
|
||||||
|
**Secrets Detection (Multi-Layer):**
|
||||||
|
|
||||||
|
- **Gitleaks**: Fast regex-based secrets scanning
|
||||||
|
- **detect-secrets**: Baseline tracking for known/approved secrets
|
||||||
|
- **TruffleHog**: Verified secrets detection (skipped in CI due to wasm bugs)
|
||||||
|
|
||||||
|
**Documentation & Config:**
|
||||||
|
|
||||||
|
- **Markdownlint**: Markdown linting with auto-fix
|
||||||
|
- **md-toc**: Table of contents generation for README
|
||||||
- **Codespell**: Spell checking in code and docs
|
- **Codespell**: Spell checking in code and docs
|
||||||
- **YAML/TOML/JSON validation**: Config file validation
|
- **MkDocs build**: Validates documentation builds successfully
|
||||||
|
- **YAML/TOML/JSON/XML validation**: Config file validation
|
||||||
|
- **check-json5**: JSONC validation for VS Code config files
|
||||||
|
|
||||||
|
**Infrastructure:**
|
||||||
|
|
||||||
|
- **Hadolint**: Dockerfile linting
|
||||||
|
- **Trivy**: Dockerfile security misconfiguration scanning
|
||||||
|
- **Shellcheck**: Shell script linting
|
||||||
|
- **Actionlint**: GitHub Actions workflow linting
|
||||||
|
- **validate-pyproject**: Python project configuration validation
|
||||||
|
- **check-github-workflows**: GitHub workflow schema validation
|
||||||
|
- **check-dependabot**: Dependabot config validation
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
|
||||||
- **Trailing whitespace and EOF fixes**: File hygiene
|
- **Trailing whitespace and EOF fixes**: File hygiene
|
||||||
|
- **Commitizen**: Commit message format validation (commit-msg stage)
|
||||||
|
- **no-commit-to-branch**: Prevents commits to main/master
|
||||||
|
- **CodeRabbit**: AI code review (manual stage only)
|
||||||
|
|
||||||
### Linting Rules
|
### Linting Rules
|
||||||
|
|
||||||
@@ -241,20 +339,20 @@ This project uses a comprehensive, multi-layer approach to secrets detection:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all secrets scanners
|
# Run all secrets scanners
|
||||||
just secrets
|
just quality::scan
|
||||||
|
|
||||||
# Individual scanners
|
# Individual scanners
|
||||||
just secrets-gitleaks # Fast pattern matching
|
just quality::scan-gitleaks # Fast pattern matching
|
||||||
just secrets-gitleaks-history # Scan git history
|
just quality::scan-gitleaks-history # Scan git history
|
||||||
just secrets-detect # Check against baseline
|
just quality::scan-detect # Check against baseline
|
||||||
just secrets-audit # Interactive baseline audit
|
just quality::scan-audit # Interactive baseline audit
|
||||||
just secrets-trufflehog # Verified secrets only
|
just quality::scan-trufflehog # Verified secrets only
|
||||||
```
|
```
|
||||||
|
|
||||||
**Managing False Positives:**
|
**Managing False Positives:**
|
||||||
|
|
||||||
1. **Gitleaks**: Add patterns to `.gitleaks.toml` allowlist section
|
1. **Gitleaks**: Add patterns to `.gitleaks.toml` allowlist section
|
||||||
1. **detect-secrets**: Run `just secrets-audit` to mark false positives in baseline
|
1. **detect-secrets**: Run `just quality::scan-audit` to mark false positives in baseline
|
||||||
1. **TruffleHog**: Uses `--only-verified` to minimize false positives
|
1. **TruffleHog**: Uses `--only-verified` to minimize false positives
|
||||||
|
|
||||||
### Markdown Linting
|
### Markdown Linting
|
||||||
@@ -262,8 +360,8 @@ just secrets-trufflehog # Verified secrets only
|
|||||||
All markdown files are linted for consistency:
|
All markdown files are linted for consistency:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just markdown-lint # Check markdown files
|
just quality::markdown-lint # Check markdown files
|
||||||
just markdown-fix # Auto-fix markdown issues
|
just quality::markdown-fix # Auto-fix markdown issues
|
||||||
```
|
```
|
||||||
|
|
||||||
Configuration: `.markdownlint.yaml`
|
Configuration: `.markdownlint.yaml`
|
||||||
@@ -324,13 +422,50 @@ Configuration: `.markdownlint.yaml`
|
|||||||
|
|
||||||
### Documentation Building
|
### Documentation Building
|
||||||
|
|
||||||
Documentation is auto-generated using the docstrings. Run:
|
Documentation is auto-generated using MkDocs with the Material theme. Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just docs # Build documentation
|
just documentation::build # Build documentation
|
||||||
just docs-serve # Serve documentation locally
|
just documentation::serve # Serve documentation locally
|
||||||
|
just documentation::open # Open docs in browser
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MkDocs Configuration
|
||||||
|
|
||||||
|
**Theme**: Material for MkDocs with dark mode default, deep purple color scheme.
|
||||||
|
|
||||||
|
**Key Plugins**:
|
||||||
|
|
||||||
|
- **mkdocstrings**: Auto-generates API docs from Python docstrings
|
||||||
|
- **mkdocs-macros-plugin**: Variables and templating in markdown
|
||||||
|
- **git-revision-date-localized**: Shows "Last updated" on pages
|
||||||
|
- **glightbox**: Image lightbox/zoom functionality
|
||||||
|
|
||||||
|
**Macros Plugin - Custom Delimiters**:
|
||||||
|
|
||||||
|
To avoid conflicts with Python dict literals in code blocks, this project uses custom delimiters:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
<!-- Standard Jinja2 (DON'T USE): {{ variable }} -->
|
||||||
|
<!-- Use instead: -->
|
||||||
|
{{@ variable @}}
|
||||||
|
|
||||||
|
<!-- For blocks: -->
|
||||||
|
{%@ if condition @%}
|
||||||
|
...
|
||||||
|
{%@ endif @%}
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables are defined in `docs/variables.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project_name: FreeCAD MCP Server
|
||||||
|
xmlrpc_port: 9875
|
||||||
|
socket_port: 9876
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reference**: See `docs/development/mkdocs-guide.md` for complete documentation on available extensions (admonitions, tabs, code annotations, mermaid diagrams, etc.).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing Requirements
|
## Testing Requirements
|
||||||
@@ -391,9 +526,10 @@ class TestCalculateTotal:
|
|||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just test # Run all tests
|
just testing::unit # Run unit tests
|
||||||
just test-cov # Run tests with coverage report
|
just testing::cov # Run tests with coverage report
|
||||||
just test-fast # Run tests without slow markers
|
just testing::fast # Run tests without slow markers
|
||||||
|
just testing::all # Run all tests including integration
|
||||||
uv run pytest tests/unit/ # Run specific test directory
|
uv run pytest tests/unit/ # Run specific test directory
|
||||||
uv run pytest -k "test_name" # Run specific test by name
|
uv run pytest -k "test_name" # Run specific test by name
|
||||||
```
|
```
|
||||||
@@ -405,7 +541,7 @@ uv run pytest -k "test_name" # Run specific test by name
|
|||||||
### Before Making Changes
|
### Before Making Changes
|
||||||
|
|
||||||
1. Ensure you're on the latest code
|
1. Ensure you're on the latest code
|
||||||
1. Run `just install` to update dependencies
|
1. Run `just install::mcp-server` to update dependencies
|
||||||
1. Run `just all` to verify clean starting state
|
1. Run `just all` to verify clean starting state
|
||||||
|
|
||||||
### After Making Changes
|
### After Making Changes
|
||||||
@@ -414,12 +550,12 @@ uv run pytest -k "test_name" # Run specific test by name
|
|||||||
|
|
||||||
1. [ ] **Add/update docstrings** for all new/modified code
|
1. [ ] **Add/update docstrings** for all new/modified code
|
||||||
1. [ ] **Add/update tests** for all new/modified functionality
|
1. [ ] **Add/update tests** for all new/modified functionality
|
||||||
1. [ ] **Run formatting**: `just format`
|
1. [ ] **Run formatting**: `just quality::format`
|
||||||
1. [ ] **Run linting**: `just lint` - fix ALL issues
|
1. [ ] **Run linting**: `just quality::lint` - fix ALL issues
|
||||||
1. [ ] **Run type checking**: `just typecheck` - fix ALL issues
|
1. [ ] **Run type checking**: `just quality::typecheck` - fix ALL issues
|
||||||
1. [ ] **Run security checks**: `just security` - fix ALL issues
|
1. [ ] **Run security checks**: `just quality::security` - fix ALL issues
|
||||||
1. [ ] **Run tests**: `just test` - ALL tests must pass
|
1. [ ] **Run tests**: `just testing::unit` - ALL tests must pass
|
||||||
1. [ ] **Run pre-commit**: `just check` - ALL checks must pass
|
1. [ ] **Run pre-commit**: `just quality::check` - ALL checks must pass
|
||||||
|
|
||||||
### Quick Verification Command
|
### Quick Verification Command
|
||||||
|
|
||||||
@@ -455,7 +591,7 @@ This catches issues early and ensures code quality standards are met. Never skip
|
|||||||
- This allows the package to work as a library while ensuring reproducibility
|
- This allows the package to work as a library while ensuring reproducibility
|
||||||
- **Do not change `>=` to `==` in pyproject.toml** - this would break library usability
|
- **Do not change `>=` to `==` in pyproject.toml** - this would break library usability
|
||||||
- Regularly update dependencies with `just update-deps` (updates uv.lock)
|
- Regularly update dependencies with `just update-deps` (updates uv.lock)
|
||||||
- Check for security vulnerabilities with `just security`
|
- Check for security vulnerabilities with `just quality::security`
|
||||||
|
|
||||||
### Core Dependencies
|
### Core Dependencies
|
||||||
|
|
||||||
@@ -473,24 +609,83 @@ Keep these tools at their latest stable versions:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
project-root/
|
project-root/
|
||||||
|
├── .github/
|
||||||
|
│ ├── ISSUE_TEMPLATE/ # GitHub issue templates
|
||||||
|
│ │ └── *.yaml
|
||||||
|
│ ├── workflows/ # GitHub Actions workflows
|
||||||
|
│ │ ├── codeql.yaml # Security analysis
|
||||||
|
│ │ ├── docker.yaml # Docker build (CI)
|
||||||
|
│ │ ├── macro-cut-magnets-release.yaml # Macro release
|
||||||
|
│ │ ├── macro-multi-export-release.yaml # Macro release
|
||||||
|
│ │ ├── macro-release-reusable.yaml # Shared macro release logic
|
||||||
|
│ │ ├── macro-test.yaml # Macro testing
|
||||||
|
│ │ ├── mcp-server-release.yaml # MCP server → PyPI/Docker
|
||||||
|
│ │ ├── mcp-workbench-release.yaml # Workbench → GitHub Release
|
||||||
|
│ │ ├── pre-commit.yaml # Pre-commit checks
|
||||||
|
│ │ └── test.yaml # Unit/integration tests
|
||||||
|
│ └── dependabot.yaml # Dependency updates
|
||||||
|
├── addon/ # FreeCAD addon (workbench)
|
||||||
|
│ └── FreecadRobustMCP/ # MCP Bridge workbench
|
||||||
|
│ ├── freecad_mcp_bridge/ # Bridge Python package
|
||||||
|
│ ├── Init.py # FreeCAD workbench init
|
||||||
|
│ ├── InitGui.py # FreeCAD GUI init
|
||||||
|
│ └── metadata.txt # Addon metadata
|
||||||
|
├── docs/ # MkDocs documentation source
|
||||||
|
│ ├── assets/ # Images, diagrams
|
||||||
|
│ ├── development/ # Developer guides
|
||||||
|
│ ├── getting-started/ # Installation, quickstart
|
||||||
|
│ ├── guide/ # User guides
|
||||||
|
│ ├── macros/ # Macro documentation
|
||||||
|
│ ├── reference/ # API reference
|
||||||
|
│ ├── variables.yaml # MkDocs macro variables
|
||||||
|
│ └── index.md # Documentation home
|
||||||
|
├── just/ # Just module files
|
||||||
|
│ ├── coderabbit.just # AI code review commands
|
||||||
|
│ ├── dev.just # Development utilities
|
||||||
|
│ ├── docker.just # Docker build/run commands
|
||||||
|
│ ├── documentation.just # Documentation commands
|
||||||
|
│ ├── freecad.just # FreeCAD running commands
|
||||||
|
│ ├── install.just # Installation commands
|
||||||
|
│ ├── mcp.just # MCP server commands
|
||||||
|
│ ├── quality.just # Code quality commands
|
||||||
|
│ ├── release.just # Release/tagging commands
|
||||||
|
│ └── testing.just # Test commands
|
||||||
|
├── macros/ # FreeCAD macro source
|
||||||
|
│ ├── Cut_Object_for_Magnets/
|
||||||
|
│ │ ├── CutObjectForMagnets.FCMacro
|
||||||
|
│ │ └── README-CutObjectForMagnets.md
|
||||||
|
│ └── Multi_Export/
|
||||||
|
│ ├── MultiExport.FCMacro
|
||||||
|
│ └── README-MultiExport.md
|
||||||
|
├── src/
|
||||||
|
│ └── freecad_mcp/ # Main MCP server package
|
||||||
|
│ ├── bridge/ # FreeCAD connection bridges
|
||||||
|
│ ├── prompts/ # MCP prompt templates
|
||||||
|
│ ├── resources/ # MCP resources
|
||||||
|
│ ├── tools/ # MCP tools (document, object, etc.)
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── server.py # Main MCP server
|
||||||
|
│ └── settings.py # Configuration settings
|
||||||
|
├── tests/
|
||||||
|
│ ├── fixtures/ # Test data files (.FCStd, etc.)
|
||||||
|
│ ├── integration/ # Integration tests (require FreeCAD)
|
||||||
|
│ ├── unit/ # Unit tests
|
||||||
|
│ └── conftest.py # Shared pytest fixtures
|
||||||
|
├── .codespell-ignore-words.txt # Spell checker exceptions
|
||||||
|
├── .gitleaks.toml # Gitleaks secrets config
|
||||||
|
├── .markdownlint.yaml # Markdown linting rules
|
||||||
├── .mise.toml # Tool version management
|
├── .mise.toml # Tool version management
|
||||||
├── .pre-commit-config.yaml # Pre-commit hook configuration
|
├── .pre-commit-config.yaml # Pre-commit hook configuration
|
||||||
├── justfile # Main task runner (imports modules)
|
├── .safety-policy.yml # Safety CLI scan policy
|
||||||
├── just/ # Just module files
|
├── .secrets.baseline # detect-secrets baseline
|
||||||
│ ├── docker.just # Docker build/run commands
|
├── CHANGELOG.md # Project changelog
|
||||||
│ ├── quality.just # Code quality commands
|
├── CLAUDE.md # This file (AI assistant guidelines)
|
||||||
│ ├── testing.just # Test commands
|
|
||||||
│ ├── freecad.just # FreeCAD plugin/macro commands
|
|
||||||
│ └── documentation.just # Documentation commands
|
|
||||||
├── pyproject.toml # Project configuration and dependencies
|
|
||||||
├── Dockerfile # Docker image definition
|
├── Dockerfile # Docker image definition
|
||||||
├── src/
|
├── justfile # Main task runner (imports modules)
|
||||||
│ └── package_name/ # Main package source
|
├── mkdocs.yaml # MkDocs configuration
|
||||||
│ ├── __init__.py
|
├── package.xml # FreeCAD addon metadata
|
||||||
│ └── *.py
|
├── pyproject.toml # Project configuration and dependencies
|
||||||
├── tests/ # Test files
|
└── uv.lock # Locked dependency versions
|
||||||
├── docs/ # Documentation source
|
|
||||||
└── CLAUDE.md # This file
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -518,6 +713,56 @@ When creating new files, always use the full extension.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Configuration Files Reference
|
||||||
|
|
||||||
|
This section describes the purpose and key settings in each configuration file.
|
||||||
|
|
||||||
|
### Tool Management
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `.mise.toml` | Pins versions for Python, uv, just, pre-commit, and security tools (trivy, gitleaks, hadolint, shellcheck, actionlint, markdownlint-cli2). Also sets environment variables for FreeCAD connection settings. |
|
||||||
|
| `pyproject.toml` | Python project configuration: dependencies, build system, tool configs (ruff, mypy, pytest, bandit, codespell, commitizen). |
|
||||||
|
| `uv.lock` | Exact locked versions of all Python dependencies for reproducible builds. |
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ----------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| `.pre-commit-config.yaml` | Pre-commit hook definitions. See [Pre-commit Hooks](#pre-commit-hooks) for details. |
|
||||||
|
| `.markdownlint.yaml` | Markdown linting rules (heading style, list indentation, code block style, etc.). |
|
||||||
|
| `.codespell-ignore-words.txt` | Words to ignore during spell checking (technical terms, tool names). |
|
||||||
|
|
||||||
|
### Security Scanning
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `.gitleaks.toml` | Gitleaks secrets scanner configuration with allowlist patterns for false positives. |
|
||||||
|
| `.secrets.baseline` | detect-secrets baseline tracking known/approved secrets and their locations. |
|
||||||
|
| `.safety-policy.yml` | Safety CLI scan policy - excludes `.venv`, `node_modules`, and other directories from vulnerability scanning. |
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `mkdocs.yaml` | MkDocs configuration: Material theme, plugins (macros, mkdocstrings, git-revision-date), navigation structure. |
|
||||||
|
| `docs/variables.yaml` | Variables for MkDocs macros plugin (project name, ports, paths). Use `{{@ variable @}}` syntax in docs. |
|
||||||
|
|
||||||
|
### FreeCAD Addon
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ------------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| `package.xml` | FreeCAD addon metadata with per-component versioning. Updated automatically by release workflows. |
|
||||||
|
|
||||||
|
### GitHub
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ---------------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| `.github/dependabot.yaml` | Dependabot configuration for automated dependency updates. |
|
||||||
|
| `.github/workflows/*.yaml` | CI/CD workflows. See [GitHub Workflows](#github-actions-workflows) for details. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Justfile HEREDOC Syntax
|
## Justfile HEREDOC Syntax
|
||||||
|
|
||||||
**CRITICAL**: When writing heredocs in justfile recipes, the content must be indented to match the recipe body (4 spaces). Just parses non-indented lines as justfile syntax, which causes errors with Python code containing dots (e.g., `sys.path`).
|
**CRITICAL**: When writing heredocs in justfile recipes, the content must be indented to match the recipe body (4 spaces). Just parses non-indented lines as justfile syntax, which causes errors with Python code containing dots (e.g., `sys.path`).
|
||||||
@@ -750,10 +995,10 @@ The following tools in `src/freecad_mcp/tools/view.py` check `FreeCAD.GuiUp`:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# GUI mode - with MCP bridge auto-started
|
# GUI mode - with MCP bridge auto-started
|
||||||
just run-gui
|
just freecad::run-gui
|
||||||
|
|
||||||
# Headless mode - console only
|
# Headless mode - console only
|
||||||
just run-headless
|
just freecad::run-headless
|
||||||
|
|
||||||
# Or manually:
|
# Or manually:
|
||||||
# GUI: /Applications/FreeCAD.app (macOS) or freecad (Linux)
|
# GUI: /Applications/FreeCAD.app (macOS) or freecad (Linux)
|
||||||
@@ -851,13 +1096,187 @@ These checks are intentionally skipped in `pyproject.toml`:
|
|||||||
### detect-secrets
|
### detect-secrets
|
||||||
|
|
||||||
- **False positives**: Add `# pragma: allowlist secret` comment to lines with false positives
|
- **False positives**: Add `# pragma: allowlist secret` comment to lines with false positives
|
||||||
- **Baseline updates**: Run `just secrets-audit` to update `.secrets.baseline`
|
- **Baseline updates**: Run `just quality::scan-audit` to update `.secrets.baseline`
|
||||||
|
|
||||||
### no-commit-to-branch
|
### no-commit-to-branch
|
||||||
|
|
||||||
- This hook fails when on `main` or `master` branch - this is expected behavior
|
- This hook fails when on `main` or `master` branch - this is expected behavior
|
||||||
- Always work on feature branches for actual commits
|
- Always work on feature branches for actual commits
|
||||||
|
|
||||||
|
### check-json5 (JSONC Support)
|
||||||
|
|
||||||
|
- VS Code configuration files (`.vscode/*.json`) use JSONC format (JSON with Comments)
|
||||||
|
- These files are excluded from the strict `check-json` hook
|
||||||
|
- The `check-json5` hook validates these files instead, allowing `//` comments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GitHub Actions Workflows
|
||||||
|
|
||||||
|
This project uses component-specific release workflows along with CI/CD pipelines.
|
||||||
|
|
||||||
|
### CI Workflows
|
||||||
|
|
||||||
|
| Workflow | Trigger | Purpose |
|
||||||
|
| ------------------ | -------------------- | ---------------------------------------------------------- |
|
||||||
|
| `test.yaml` | Push, PR | Runs unit tests and integration tests on Ubuntu and macOS |
|
||||||
|
| `pre-commit.yaml` | Push, PR | Runs all pre-commit hooks for code quality |
|
||||||
|
| `docker.yaml` | Push, PR | Builds Docker image to verify Dockerfile works |
|
||||||
|
| `macro-test.yaml` | Push, PR | Tests FreeCAD macros in headless Docker environment |
|
||||||
|
| `codeql.yaml` | Push, PR, scheduled | GitHub CodeQL security analysis |
|
||||||
|
|
||||||
|
### Release Workflows
|
||||||
|
|
||||||
|
| Workflow | Trigger | Purpose |
|
||||||
|
| --------------------------------- | ------------------------------------ | -------------------------------------------------------- |
|
||||||
|
| `mcp-server-release.yaml` | Tag: `robust-mcp-server-v*` | Builds and publishes MCP server to PyPI and Docker Hub |
|
||||||
|
| `mcp-workbench-release.yaml` | Tag: `robust-mcp-workbench-v*` | Creates GitHub Release with workbench addon archive |
|
||||||
|
| `macro-cut-magnets-release.yaml` | Tag: `macro-cut-object-for-magnets-v*` | Creates GitHub Release with macro archive |
|
||||||
|
| `macro-multi-export-release.yaml` | Tag: `macro-multi-export-v*` | Creates GitHub Release with macro archive |
|
||||||
|
| `macro-release-reusable.yaml` | Called by macro release workflows | Shared logic for macro releases (DRY) |
|
||||||
|
|
||||||
|
### Release Workflow Features
|
||||||
|
|
||||||
|
**MCP Server Release** (`mcp-server-release.yaml`):
|
||||||
|
|
||||||
|
- Validates SemVer tag format
|
||||||
|
- Builds Python wheel and sdist
|
||||||
|
- Tests installation on Ubuntu and macOS
|
||||||
|
- Publishes to PyPI (stable) or TestPyPI (alpha)
|
||||||
|
- Builds multi-arch Docker image (amd64 + arm64)
|
||||||
|
- Pushes to Docker Hub with version tags
|
||||||
|
- Creates GitHub Release with artifacts and changelog
|
||||||
|
|
||||||
|
**Workbench/Macro Releases**:
|
||||||
|
|
||||||
|
- Validates tag format
|
||||||
|
- Updates version in source files automatically
|
||||||
|
- Updates `package.xml` per-component version
|
||||||
|
- Creates tar.gz and zip archives
|
||||||
|
- Extracts changelog section for release notes
|
||||||
|
- Creates GitHub Release with archives
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
This project uses **component-specific versioning**. Each component has its own git tag and release workflow:
|
||||||
|
|
||||||
|
| Component | Tag Format | Releases To |
|
||||||
|
| --------------------------- | ------------------------------------- | ------------------------------------ |
|
||||||
|
| MCP Server | `robust-mcp-server-vX.Y.Z` | PyPI, Docker Hub, GitHub Release |
|
||||||
|
| MCP Bridge Workbench | `robust-mcp-workbench-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
| Cut Object for Magnets Macro| `macro-cut-object-for-magnets-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
| Multi Export Macro | `macro-multi-export-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
|
||||||
|
### Changelog Management
|
||||||
|
|
||||||
|
The project uses a single `CHANGELOG.md` with sections for each component. Before releasing:
|
||||||
|
|
||||||
|
1. **Draft release notes** from conventional commits:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just release::draft-notes mcp-server
|
||||||
|
just release::draft-notes workbench
|
||||||
|
just release::draft-notes macro-magnets
|
||||||
|
just release::draft-notes macro-export
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update CHANGELOG.md** with entries under the appropriate component header:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## YYYY-MM-DD
|
||||||
|
|
||||||
|
### MCP Server vX.Y.Z
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- New feature
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MCP Bridge Workbench vX.Y.Z
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The release workflow automatically extracts the changelog section for GitHub Releases.
|
||||||
|
|
||||||
|
### Creating a Release
|
||||||
|
|
||||||
|
Use the `just release::` commands to create and push release tags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what has unreleased changes
|
||||||
|
just release::status
|
||||||
|
|
||||||
|
# Preview changes since last release
|
||||||
|
just release::changes-since mcp-server
|
||||||
|
just release::changes-since workbench
|
||||||
|
|
||||||
|
# Release the MCP server (triggers PyPI, Docker, GitHub release)
|
||||||
|
just release::tag-mcp-server 1.0.0
|
||||||
|
|
||||||
|
# Release the MCP Bridge workbench
|
||||||
|
just release::tag-workbench 1.0.0
|
||||||
|
|
||||||
|
# Release macros
|
||||||
|
just release::tag-macro-magnets 1.0.0
|
||||||
|
just release::tag-macro-export 1.0.0
|
||||||
|
|
||||||
|
# View release tags
|
||||||
|
just release::list-tags
|
||||||
|
just release::latest-versions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Format
|
||||||
|
|
||||||
|
All versions follow SemVer 2.0:
|
||||||
|
|
||||||
|
- `X.Y.Z` - Stable release
|
||||||
|
- `X.Y.Z-alpha` or `X.Y.Z-alpha.N` - Alpha (TestPyPI only)
|
||||||
|
- `X.Y.Z-beta` or `X.Y.Z-beta.N` - Beta (PyPI)
|
||||||
|
- `X.Y.Z-rc.N` - Release candidate (PyPI)
|
||||||
|
|
||||||
|
### What Happens on Release
|
||||||
|
|
||||||
|
**MCP Server Release:**
|
||||||
|
|
||||||
|
1. Validates tag format
|
||||||
|
2. Builds Python wheel and sdist
|
||||||
|
3. Tests installation on Ubuntu and macOS
|
||||||
|
4. Publishes to PyPI (or TestPyPI for alpha)
|
||||||
|
5. Builds multi-arch Docker image
|
||||||
|
6. Pushes to Docker Hub
|
||||||
|
7. Creates GitHub release with artifacts
|
||||||
|
|
||||||
|
**Workbench/Macro Release:**
|
||||||
|
|
||||||
|
1. Validates tag format
|
||||||
|
2. Updates version in source files (.FCMacro, README, wiki-source.txt)
|
||||||
|
3. Updates version in package.xml
|
||||||
|
4. Creates archive (tar.gz + zip)
|
||||||
|
5. Creates GitHub release with archives
|
||||||
|
|
||||||
|
### Package.xml Per-Component Versioning
|
||||||
|
|
||||||
|
The `package.xml` file uses per-content versioning as supported by FreeCAD:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<content>
|
||||||
|
<workbench>
|
||||||
|
<name>MCP Bridge</name>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
...
|
||||||
|
</workbench>
|
||||||
|
<macro>
|
||||||
|
<name>Multi Export</name>
|
||||||
|
<version>0.8.0</version>
|
||||||
|
...
|
||||||
|
</macro>
|
||||||
|
</content>
|
||||||
|
```
|
||||||
|
|
||||||
|
Each component can have a different version, and the release workflows automatically update these when a component is released.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changelog Maintenance
|
## Changelog Maintenance
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/pre-commit.yaml)
|
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/pre-commit.yaml)
|
||||||
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/codeql.yaml)
|
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/codeql.yaml)
|
||||||
[](https://pypi.org/project/freecad-robust-mcp/)
|
[](https://pypi.org/project/freecad-robust-mcp/)
|
||||||
|
[](https://pypi.org/project/freecad-robust-mcp/)
|
||||||
|
|
||||||
[](https://hub.docker.com/r/spkane/freecad-robust-mcp)
|
[](https://hub.docker.com/r/spkane/freecad-robust-mcp)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
@@ -268,10 +270,10 @@ Before your AI assistant can connect, you need to start the MCP bridge inside Fr
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start FreeCAD with MCP bridge auto-started
|
# Start FreeCAD with MCP bridge auto-started
|
||||||
just run-gui
|
just freecad::run-gui
|
||||||
|
|
||||||
# Or for headless/automation mode:
|
# Or for headless/automation mode:
|
||||||
just run-headless
|
just freecad::run-headless
|
||||||
```
|
```
|
||||||
|
|
||||||
After starting the bridge, start/restart your MCP client (Claude Code, etc.) - it will connect automatically
|
After starting the bridge, start/restart your MCP client (Claude Code, etc.) - it will connect automatically
|
||||||
@@ -291,7 +293,7 @@ To uninstall the MCP Bridge workbench:
|
|||||||
If you previously used older versions of this project, you may have legacy components installed. Run this command to check what's installed and get cleanup instructions:
|
If you previously used older versions of this project, you may have legacy components installed. Run this command to check what's installed and get cleanup instructions:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just freecad::mcp-status
|
just install::status
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Manual Cleanup (if needed)
|
##### Manual Cleanup (if needed)
|
||||||
@@ -332,7 +334,7 @@ Run FreeCAD in console mode without GUI. Useful for automation.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If installed from source:
|
# If installed from source:
|
||||||
just run-headless
|
just freecad::run-headless
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Screenshot and view features are not available in headless mode.
|
**Note:** Screenshot and view features are not available in headless mode.
|
||||||
@@ -572,7 +574,7 @@ Export selected FreeCAD objects to multiple file formats simultaneously. Support
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If you have the source:
|
# If you have the source:
|
||||||
just freecad::install-export-macro
|
just install::macro-export
|
||||||
|
|
||||||
# Or manually copy MultiExport.FCMacro from macros/Multi_Export/
|
# Or manually copy MultiExport.FCMacro from macros/Multi_Export/
|
||||||
# to your FreeCAD macro directory:
|
# to your FreeCAD macro directory:
|
||||||
@@ -687,35 +689,45 @@ mise where uv | sed 's|/installs/.*|/shims|'
|
|||||||
|
|
||||||
### Development Workflow
|
### Development Workflow
|
||||||
|
|
||||||
Commands are organized into modules. Use `just --list` to see all shortcuts, or `just --list <module>` to see module-specific commands.
|
Commands are organized into modules. Use `just` to see top-level commands, or `just list-<module>` to see module-specific commands.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Show all available commands
|
# Show top-level commands and available modules
|
||||||
just --list
|
just
|
||||||
|
|
||||||
# Show commands in a specific module
|
# Show commands in a specific module
|
||||||
just --list docker
|
just list-mcp # MCP server commands
|
||||||
just --list quality
|
just list-freecad # FreeCAD plugin/macro commands
|
||||||
just --list testing
|
just list-install # Installation commands
|
||||||
just --list freecad
|
just list-quality # Code quality commands
|
||||||
|
just list-testing # Test commands
|
||||||
|
just list-docker # Docker commands
|
||||||
|
just list-documentation # Documentation commands
|
||||||
|
just list-dev # Development utilities
|
||||||
|
|
||||||
|
# List ALL commands from all modules
|
||||||
|
just list-all
|
||||||
|
|
||||||
# Install/update dependencies
|
# Install/update dependencies
|
||||||
just install
|
just install::mcp-server
|
||||||
|
|
||||||
# Run all checks (linting, type checking, tests)
|
# Run all checks (linting, type checking, tests)
|
||||||
just all
|
just all
|
||||||
|
|
||||||
# Individual checks (shortcuts to module commands)
|
# Quality commands
|
||||||
just lint # Run ruff linter (quality::lint)
|
just quality::lint # Run ruff linter
|
||||||
just typecheck # Run mypy type checker (quality::typecheck)
|
just quality::typecheck # Run mypy type checker
|
||||||
just test # Run unit tests (testing::unit)
|
just quality::format # Format code
|
||||||
just check # Run all pre-commit hooks (quality::check)
|
just quality::check # Run all pre-commit hooks
|
||||||
|
|
||||||
# Format code
|
# Testing commands
|
||||||
just format
|
just testing::unit # Run unit tests
|
||||||
|
just testing::cov # Run tests with coverage
|
||||||
|
just testing::integration # Run integration tests
|
||||||
|
|
||||||
# Run with debug logging
|
# Run the MCP server (or with debug logging)
|
||||||
just run-debug
|
just mcp::run
|
||||||
|
just mcp::run-debug
|
||||||
|
|
||||||
# Docker commands
|
# Docker commands
|
||||||
just docker::build # Build image for local architecture
|
just docker::build # Build image for local architecture
|
||||||
@@ -729,29 +741,29 @@ just docker::run # Run container
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start FreeCAD with auto-started bridge
|
# Start FreeCAD with auto-started bridge
|
||||||
just run-gui
|
just freecad::run-gui
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Headless Mode (for automation/CI)
|
#### Headless Mode (for automation/CI)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just run-headless
|
just freecad::run-headless
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Unit tests only (no FreeCAD required)
|
# Unit tests only (no FreeCAD required)
|
||||||
just test
|
just testing::unit
|
||||||
|
|
||||||
# Unit tests with coverage
|
# Unit tests with coverage
|
||||||
just test-cov
|
just testing::cov
|
||||||
|
|
||||||
# Integration tests (requires running FreeCAD bridge)
|
# Integration tests (requires running FreeCAD bridge)
|
||||||
just test-integration
|
just testing::integration
|
||||||
|
|
||||||
# All tests with automatic FreeCAD startup
|
# Integration tests with automatic FreeCAD startup
|
||||||
just integration
|
just testing::integration-auto
|
||||||
```
|
```
|
||||||
|
|
||||||
### Code Quality
|
### Code Quality
|
||||||
@@ -766,11 +778,11 @@ The project uses strict code quality checks via pre-commit:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all pre-commit hooks
|
# Run all pre-commit hooks
|
||||||
just check
|
just quality::check
|
||||||
|
|
||||||
# Run security/secrets scans
|
# Run security/secrets scans
|
||||||
just security
|
just quality::security
|
||||||
just secrets
|
just quality::secrets
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -784,13 +796,13 @@ just secrets
|
|||||||
**Installation for development:**
|
**Installation for development:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just install-cut-macro
|
just install::macro-cut
|
||||||
```
|
```
|
||||||
|
|
||||||
**Uninstall:**
|
**Uninstall:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just uninstall-cut-macro
|
just install::uninstall-macro-cut
|
||||||
```
|
```
|
||||||
|
|
||||||
See [macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md](macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md) for detailed documentation on the macro's internals.
|
See [macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md](macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md) for detailed documentation on the macro's internals.
|
||||||
@@ -802,13 +814,13 @@ See [macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md](macros/Cut_Obj
|
|||||||
**Installation for development:**
|
**Installation for development:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just freecad::install-export-macro
|
just install::macro-export
|
||||||
```
|
```
|
||||||
|
|
||||||
**Uninstall:**
|
**Uninstall:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just freecad::uninstall-export-macro
|
just install::uninstall-macro-export
|
||||||
```
|
```
|
||||||
|
|
||||||
See [macros/Multi_Export/README-MultiExport.md](macros/Multi_Export/README-MultiExport.md) for detailed documentation on the macro's internals.
|
See [macros/Multi_Export/README-MultiExport.md](macros/Multi_Export/README-MultiExport.md) for detailed documentation on the macro's internals.
|
||||||
@@ -817,7 +829,7 @@ See [macros/Multi_Export/README-MultiExport.md](macros/Multi_Export/README-Multi
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
See [ARCHITECTURE-MCP.md](ARCHITECTURE-MCP.md) for detailed design documentation covering:
|
See the [detailed architecture document](docs/development/architecture-detailed.md) for design documentation covering:
|
||||||
|
|
||||||
- Module structure
|
- Module structure
|
||||||
- Bridge communication protocols
|
- Bridge communication protocols
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ It is bundled with the workbench addon for self-contained installation.
|
|||||||
|
|
||||||
from .server import FreecadMCPPlugin
|
from .server import FreecadMCPPlugin
|
||||||
|
|
||||||
__all__ = ["FreecadMCPPlugin"]
|
__version__ = "0.0.0" # Updated by release workflow
|
||||||
|
__all__ = ["FreecadMCPPlugin", "__version__"]
|
||||||
|
|||||||
+4
-4
@@ -33,7 +33,7 @@ You have two options depending on your workflow:
|
|||||||
Best for automated workflows, batch processing, or when you don't need visual feedback.
|
Best for automated workflows, batch processing, or when you don't need visual feedback.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just run-headless
|
just freecad::run-headless
|
||||||
```
|
```
|
||||||
|
|
||||||
**Capabilities:** All modeling operations, export, scripting.
|
**Capabilities:** All modeling operations, export, scripting.
|
||||||
@@ -44,7 +44,7 @@ just run-headless
|
|||||||
Best for interactive design work where you want to see results visually.
|
Best for interactive design work where you want to see results visually.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just run-gui
|
just freecad::run-gui
|
||||||
```
|
```
|
||||||
|
|
||||||
**Capabilities:** Everything headless mode can do, plus screenshots, colors, view control.
|
**Capabilities:** Everything headless mode can do, plus screenshots, colors, view control.
|
||||||
@@ -441,7 +441,7 @@ Instead of creating many individual features:
|
|||||||
|
|
||||||
You're running in headless mode and trying to use a GUI-only feature. Either:
|
You're running in headless mode and trying to use a GUI-only feature. Either:
|
||||||
|
|
||||||
- Switch to GUI mode: `just run-gui`
|
- Switch to GUI mode: `just freecad::run-gui`
|
||||||
- Use an alternative approach (e.g., skip visual operations)
|
- Use an alternative approach (e.g., skip visual operations)
|
||||||
|
|
||||||
### Objects Not Appearing
|
### Objects Not Appearing
|
||||||
@@ -474,7 +474,7 @@ If the MCP bridge isn't responding:
|
|||||||
|
|
||||||
1. Check FreeCAD is running with the bridge started
|
1. Check FreeCAD is running with the bridge started
|
||||||
1. Verify ports 9875 (XML-RPC) and 9876 (socket) are available
|
1. Verify ports 9875 (XML-RPC) and 9876 (socket) are available
|
||||||
1. Restart FreeCAD with `just run-gui` or `just run-headless`
|
1. Restart FreeCAD with `just freecad::run-gui` or `just freecad::run-headless`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ This document describes the architecture for a Model Context Protocol (MCP) serv
|
|||||||
|
|
||||||
## Competitive Analysis
|
## Competitive Analysis
|
||||||
|
|
||||||
See [docs/COMPARISON.md](docs/COMPARISON.md) for detailed analysis of existing implementations.
|
See [COMPARISON.md](../COMPARISON.md) for detailed analysis of existing implementations.
|
||||||
|
|
||||||
### Existing FreeCAD MCP Servers
|
### Existing FreeCAD MCP Servers
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document provides a technical overview of the FreeCAD MCP Server architecture.
|
This document provides a technical overview of the FreeCAD MCP Server architecture.
|
||||||
|
|
||||||
For the full architecture document with design decisions and rationale, see [ARCHITECTURE-MCP.md](https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/ARCHITECTURE-MCP.md).
|
For the full architecture document with design decisions and rationale, see [Detailed Architecture](architecture-detailed.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,17 +39,18 @@ The FreeCAD MCP Server follows a **Bridge with Adapter** pattern:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
src/freecad_mcp/
|
src/freecad_mcp/
|
||||||
├── __init__.py
|
├── __init__.py # Package entry point
|
||||||
|
├── _version.py # Version info (auto-generated)
|
||||||
├── server.py # Main MCP server entry point
|
├── server.py # Main MCP server entry point
|
||||||
├── config.py # Configuration management
|
├── config.py # Configuration management
|
||||||
|
├── py.typed # PEP 561 marker for type hints
|
||||||
│
|
│
|
||||||
├── bridge/ # FreeCAD communication layer
|
├── bridge/ # FreeCAD communication layer
|
||||||
│ ├── __init__.py # Bridge factory
|
│ ├── __init__.py # Bridge factory
|
||||||
│ ├── base.py # Abstract bridge interface
|
│ ├── base.py # Abstract bridge interface
|
||||||
│ ├── embedded.py # In-process FreeCAD (Linux only)
|
│ ├── embedded.py # In-process FreeCAD (Linux only)
|
||||||
│ ├── socket.py # JSON-RPC socket bridge
|
│ ├── socket.py # JSON-RPC socket bridge
|
||||||
│ ├── xmlrpc.py # XML-RPC bridge (recommended)
|
│ └── xmlrpc.py # XML-RPC bridge (recommended)
|
||||||
│ └── protocol.py # Wire protocol definitions
|
|
||||||
│
|
│
|
||||||
├── tools/ # MCP tool implementations
|
├── tools/ # MCP tool implementations
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
@@ -69,7 +70,8 @@ src/freecad_mcp/
|
|||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ └── freecad.py # Modeling and debugging prompts
|
│ └── freecad.py # Modeling and debugging prompts
|
||||||
│
|
│
|
||||||
└── freecad_workbench_addon/ # Workbench addon (deprecated location)
|
└── utils/ # Utility modules
|
||||||
|
└── __init__.py
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -124,7 +126,6 @@ The workbench addon runs inside FreeCAD:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
addon/FreecadRobustMCP/
|
addon/FreecadRobustMCP/
|
||||||
├── package.xml # FreeCAD addon metadata
|
|
||||||
├── Init.py # Module initialization
|
├── Init.py # Module initialization
|
||||||
├── InitGui.py # GUI initialization (workbench)
|
├── InitGui.py # GUI initialization (workbench)
|
||||||
├── FreecadRobustMCP.svg # Workbench icon
|
├── FreecadRobustMCP.svg # Workbench icon
|
||||||
@@ -132,8 +133,12 @@ addon/FreecadRobustMCP/
|
|||||||
├── __init__.py
|
├── __init__.py
|
||||||
├── server.py # XML-RPC/JSON-RPC server
|
├── server.py # XML-RPC/JSON-RPC server
|
||||||
└── headless_server.py # Headless mode launcher
|
└── headless_server.py # Headless mode launcher
|
||||||
|
|
||||||
|
package.xml # FreeCAD addon metadata (in project root)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: The `package.xml` file is in the project root, not inside the addon directory. This is because it defines metadata for multiple components (workbench and macros) in a single manifest.
|
||||||
|
|
||||||
### Thread Safety
|
### Thread Safety
|
||||||
|
|
||||||
The workbench uses a queue-based system for thread-safe GUI operations:
|
The workbench uses a queue-based system for thread-safe GUI operations:
|
||||||
@@ -256,4 +261,4 @@ Embedded mode receives **minimal testing**:
|
|||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
- [Contributing](contributing.md) - How to contribute
|
- [Contributing](contributing.md) - How to contribute
|
||||||
- [Full Architecture Document](https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/ARCHITECTURE-MCP.md) - Complete design details
|
- [Detailed Architecture](architecture-detailed.md) - Complete design details
|
||||||
|
|||||||
@@ -46,15 +46,15 @@ uv run safety auth --login
|
|||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests
|
# Run unit tests
|
||||||
just test
|
|
||||||
|
|
||||||
# Run unit tests only
|
|
||||||
just testing::unit
|
just testing::unit
|
||||||
|
|
||||||
# Run with coverage
|
# Run with coverage
|
||||||
just testing::cov
|
just testing::cov
|
||||||
|
|
||||||
|
# Run all tests including integration
|
||||||
|
just testing::all
|
||||||
|
|
||||||
# Run type checking
|
# Run type checking
|
||||||
uv run mypy src/
|
uv run mypy src/
|
||||||
```
|
```
|
||||||
@@ -63,16 +63,16 @@ uv run mypy src/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all pre-commit checks
|
# Run all pre-commit checks
|
||||||
just check
|
just quality::check
|
||||||
|
|
||||||
# Run linting
|
# Run linting
|
||||||
just lint
|
just quality::lint
|
||||||
|
|
||||||
# Format code
|
# Format code
|
||||||
just format
|
just quality::format
|
||||||
|
|
||||||
# Run security checks
|
# Run security checks
|
||||||
just quality::secrets
|
just quality::security
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -106,20 +106,20 @@ freecad-robust-mcp-and-more/
|
|||||||
- Follow PEP 8 with 88-character line length (ruff/black)
|
- Follow PEP 8 with 88-character line length (ruff/black)
|
||||||
- Use type hints for all function signatures
|
- Use type hints for all function signatures
|
||||||
- Write Google-style docstrings
|
- Write Google-style docstrings
|
||||||
- Run `just format` before committing
|
- Run `just quality::format` before committing
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- Write tests for all new functionality
|
- Write tests for all new functionality
|
||||||
- Maintain test coverage
|
- Maintain test coverage
|
||||||
- Run `just test` before submitting PRs
|
- Run `just all` before submitting PRs (runs quality checks + unit tests)
|
||||||
- Integration tests require FreeCAD (run via CI)
|
- Integration tests require FreeCAD (run via CI)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- Update docstrings for API changes
|
- Update docstrings for API changes
|
||||||
- Update user docs for feature changes
|
- Update user docs for feature changes
|
||||||
- Run `just docs` to build and verify
|
- Run `just documentation::build` to build and verify
|
||||||
|
|
||||||
### Commits
|
### Commits
|
||||||
|
|
||||||
@@ -214,7 +214,6 @@ Currently, embedded mode has only mocked unit tests. Adding live integration tes
|
|||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
||||||
- **Issues:** [GitHub Issues](https://github.com/spkane/freecad-robust-mcp-and-more/issues)
|
- **Issues:** [GitHub Issues](https://github.com/spkane/freecad-robust-mcp-and-more/issues)
|
||||||
- **Discussions:** [GitHub Discussions](https://github.com/spkane/freecad-robust-mcp-and-more/discussions)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,570 @@
|
|||||||
|
# MkDocs Documentation Guide
|
||||||
|
|
||||||
|
This project uses [MkDocs](https://www.mkdocs.org/) with the [Material theme](https://squidfunk.github.io/mkdocs-material/) for documentation. This guide covers our specific configuration, extensions, and how to use them.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Serve docs locally with live reload
|
||||||
|
just documentation::serve
|
||||||
|
|
||||||
|
# Build static site
|
||||||
|
just documentation::build
|
||||||
|
|
||||||
|
# Open docs in browser
|
||||||
|
just documentation::open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Configuration
|
||||||
|
|
||||||
|
We use **Material for MkDocs** with dark mode as the default. Users can toggle between dark and light modes using the sun/moon icon in the header.
|
||||||
|
|
||||||
|
**Color scheme:** Deep purple primary with purple accent.
|
||||||
|
|
||||||
|
## Markdown Extensions
|
||||||
|
|
||||||
|
### Code Blocks
|
||||||
|
|
||||||
|
#### Syntax Highlighting
|
||||||
|
|
||||||
|
Standard fenced code blocks with language hints:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```python
|
||||||
|
def hello():
|
||||||
|
print("Hello, world!")
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
#### Line Numbers and Highlighting
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```python linenums="1" hl_lines="2 3"
|
||||||
|
def hello():
|
||||||
|
# These lines are highlighted
|
||||||
|
print("Hello!")
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
#### Code Annotations
|
||||||
|
|
||||||
|
Add numbered annotations that expand on hover:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```python
|
||||||
|
def process(data): # (1)!
|
||||||
|
return data.strip() # (2)!
|
||||||
|
```
|
||||||
|
|
||||||
|
1. This function processes input data
|
||||||
|
2. Removes leading/trailing whitespace
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
#### Inline Code Highlighting
|
||||||
|
|
||||||
|
Use `#!python print("inline")` for inline syntax highlighting:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Use `#!python print("inline")` for inline code with highlighting.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tabbed Content
|
||||||
|
|
||||||
|
Create tabs for platform-specific or alternative content:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
=== "macOS"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/Library/Application Support/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Linux"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/.local/share/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Windows"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
%APPDATA%/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:**
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD046 -->
|
||||||
|
=== "macOS"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/Library/Application Support/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Linux"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/.local/share/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Windows"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
%APPDATA%/FreeCAD/Macro/
|
||||||
|
```
|
||||||
|
<!-- markdownlint-enable MD046 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Admonitions (Call-outs)
|
||||||
|
|
||||||
|
Create styled call-out boxes:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
!!! note "Optional Title"
|
||||||
|
This is a note admonition.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
This is a warning without a custom title.
|
||||||
|
|
||||||
|
!!! danger "Critical"
|
||||||
|
This is a danger/error admonition.
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
This is a tip admonition.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
This is an info admonition.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
This is an example admonition.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Collapsible admonitions:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
??? note "Click to expand"
|
||||||
|
This content is hidden by default.
|
||||||
|
|
||||||
|
???+ note "Expanded by default"
|
||||||
|
This content is visible but can be collapsed.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available types:** `note`, `abstract`, `info`, `tip`, `success`, `question`, `warning`, `failure`, `danger`, `bug`, `example`, `quote`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task Lists
|
||||||
|
|
||||||
|
Create checkbox lists:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [x] Completed task
|
||||||
|
- [ ] Incomplete task
|
||||||
|
- [ ] Another task
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:**
|
||||||
|
|
||||||
|
- [x] Completed task
|
||||||
|
- [ ] Incomplete task
|
||||||
|
- [ ] Another task
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Keyboard Keys
|
||||||
|
|
||||||
|
Style keyboard shortcuts:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Press ++ctrl+c++ to copy.
|
||||||
|
Press ++cmd+shift+p++ on macOS.
|
||||||
|
Press ++enter++ to confirm.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:** Press ++ctrl+c++ to copy.
|
||||||
|
|
||||||
|
**Common keys:** `ctrl`, `alt`, `shift`, `cmd`, `enter`, `tab`, `esc`, `backspace`, `delete`, `up`, `down`, `left`, `right`, `f1`-`f12`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Text Formatting
|
||||||
|
|
||||||
|
#### Highlighting
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
==This text is highlighted==
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:** ==This text is highlighted==
|
||||||
|
|
||||||
|
#### Subscript and Superscript
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
H~2~O (subscript)
|
||||||
|
x^2^ (superscript)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:** H~2~O and x^2^
|
||||||
|
|
||||||
|
#### Strikethrough
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
~~deleted text~~
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:** ~~deleted text~~
|
||||||
|
|
||||||
|
#### Critic Markup (for diffs/reviews)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{--deleted--}
|
||||||
|
{++added++}
|
||||||
|
{~~old~>new~~}
|
||||||
|
{==highlighted==}
|
||||||
|
{>>comment<<}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Definition Lists
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Term 1
|
||||||
|
: Definition for term 1
|
||||||
|
|
||||||
|
Term 2
|
||||||
|
: Definition for term 2
|
||||||
|
: Can have multiple definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renders as:**
|
||||||
|
|
||||||
|
Term 1
|
||||||
|
: Definition for term 1
|
||||||
|
|
||||||
|
Term 2
|
||||||
|
: Definition for term 2
|
||||||
|
: Can have multiple definitions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Footnotes
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
This needs a citation[^1].
|
||||||
|
|
||||||
|
[^1]: This is the footnote content.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Mermaid Diagrams
|
||||||
|
|
||||||
|
Create diagrams using Mermaid syntax:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[MCP Client] --> B[MCP Server]
|
||||||
|
B --> C[FreeCAD Bridge]
|
||||||
|
C --> D[FreeCAD]
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
**Renders as:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[MCP Client] --> B[MCP Server]
|
||||||
|
B --> C[FreeCAD Bridge]
|
||||||
|
C --> D[FreeCAD]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported diagram types:** flowchart, sequence, class, state, ER, gantt, pie, journey
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
### Git Revision Date
|
||||||
|
|
||||||
|
Pages automatically show "Last updated X ago" at the bottom. No action needed.
|
||||||
|
|
||||||
|
### Image Lightbox (GLightbox)
|
||||||
|
|
||||||
|
All images are automatically zoomable. Click any image to open in a lightbox overlay.
|
||||||
|
|
||||||
|
To exclude an image from lightbox:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{ .off-glb }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minification
|
||||||
|
|
||||||
|
HTML is automatically minified in production builds. No action needed.
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
Full-text search is enabled. Features:
|
||||||
|
|
||||||
|
- Search suggestions as you type
|
||||||
|
- Search result highlighting
|
||||||
|
- Shareable search URLs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Macros Plugin
|
||||||
|
|
||||||
|
### Custom Delimiters
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD046 -->
|
||||||
|
!!! warning "Important"
|
||||||
|
We use **custom delimiters** to avoid conflicts with Python dict literals in code blocks.
|
||||||
|
|
||||||
|
- Variables: `{` `{@` and `@}` `}`
|
||||||
|
- Blocks: `{%` `@` and `@` `%}`
|
||||||
|
|
||||||
|
Standard Jinja2 `{` `{` `}` `}` syntax will NOT work.
|
||||||
|
<!-- markdownlint-enable MD046 -->
|
||||||
|
|
||||||
|
### Using Variables
|
||||||
|
|
||||||
|
Variables are defined in `docs/variables.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docs/variables.yaml
|
||||||
|
project_name: FreeCAD MCP Server
|
||||||
|
package_name: freecad-robust-mcp
|
||||||
|
xmlrpc_port: 9875
|
||||||
|
socket_port: 9876
|
||||||
|
```
|
||||||
|
|
||||||
|
Use in Markdown:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
The default XML-RPC port is {{@ xmlrpc_port @}}.
|
||||||
|
Install with: `pip install {{@ package_name @}}`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Variables
|
||||||
|
|
||||||
|
| Variable | Value | Description |
|
||||||
|
|----------|-------|-------------|
|
||||||
|
| `project_name` | FreeCAD MCP Server | Display name |
|
||||||
|
| `package_name` | freecad-robust-mcp | PyPI package name |
|
||||||
|
| `docker_image` | spkane/freecad-robust-mcp | Docker image |
|
||||||
|
| `xmlrpc_port` | 9875 | XML-RPC server port |
|
||||||
|
| `socket_port` | 9876 | Socket server port |
|
||||||
|
| `paths.macos.macro` | ~/Library/... | macOS macro path |
|
||||||
|
| `paths.linux.macro` | ~/.local/share/... | Linux macro path |
|
||||||
|
| `paths.windows.macro` | %APPDATA%/... | Windows macro path |
|
||||||
|
|
||||||
|
### Built-in Macros
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Current time: {{@ now() @}}
|
||||||
|
Page URL: {{@ page.url @}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Documentation (mkdocstrings)
|
||||||
|
|
||||||
|
Auto-generate API docs from Python docstrings:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
::: freecad_mcp.server
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
heading_level: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
This renders the module's docstrings, classes, and functions automatically.
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `show_source: true` - Show source code
|
||||||
|
- `heading_level: 3` - Start headings at h3
|
||||||
|
- `members: [func1, func2]` - Only show specific members
|
||||||
|
- `filters: ["!^_"]` - Exclude private members
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
### Features Enabled
|
||||||
|
|
||||||
|
- **Instant navigation** - Pages load without full refresh
|
||||||
|
- **Navigation tabs** - Top-level sections as tabs
|
||||||
|
- **Section index pages** - Click section to see overview
|
||||||
|
- **Back to top** - Button appears when scrolling
|
||||||
|
- **Table of contents** - Follows scroll position
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
### Basic Image
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
### Image with Caption
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
<figure markdown>
|
||||||
|

|
||||||
|
<figcaption>This is the caption</figcaption>
|
||||||
|
</figure>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Alignment
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{ align=left }
|
||||||
|
{ align=right }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Size
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{ width="300" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
### Internal Links
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[Installation Guide](../getting-started/installation.md)
|
||||||
|
[Config section](configuration.md#environment-variables)
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Links
|
||||||
|
|
||||||
|
External links automatically open in new tab (Material theme default).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[FreeCAD](https://www.freecad.org/)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tables
|
||||||
|
|
||||||
|
Standard Markdown tables with sorting enabled:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| Column 1 | Column 2 | Column 3 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| Data 1 | Data 2 | Data 3 |
|
||||||
|
| Data 4 | Data 5 | Data 6 |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/
|
||||||
|
├── index.md # Home page
|
||||||
|
├── variables.yaml # Macros variables
|
||||||
|
├── overrides/ # Theme customizations
|
||||||
|
│ └── .gitkeep
|
||||||
|
├── assets/ # Images, favicon, etc.
|
||||||
|
├── getting-started/
|
||||||
|
│ ├── installation.md
|
||||||
|
│ ├── configuration.md
|
||||||
|
│ └── quickstart.md
|
||||||
|
├── guide/
|
||||||
|
│ └── ...
|
||||||
|
├── api/
|
||||||
|
│ └── ...
|
||||||
|
└── development/
|
||||||
|
├── contributing.md
|
||||||
|
├── architecture.md
|
||||||
|
├── releasing.md
|
||||||
|
└── mkdocs-guide.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Adding a New Page
|
||||||
|
|
||||||
|
1. Create the markdown file in the appropriate directory
|
||||||
|
2. Add to `nav:` section in `mkdocs.yaml`
|
||||||
|
|
||||||
|
### Adding Redirects
|
||||||
|
|
||||||
|
If you move/rename a page, add a redirect in `mkdocs.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
- redirects:
|
||||||
|
redirect_maps:
|
||||||
|
'old-page.md': 'new-location/new-page.md'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom CSS
|
||||||
|
|
||||||
|
Add custom styles in `docs/overrides/stylesheets/extra.css` and reference in `mkdocs.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extra_css:
|
||||||
|
- overrides/stylesheets/extra.css
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom JavaScript
|
||||||
|
|
||||||
|
Add scripts in `docs/overrides/javascripts/extra.js` and reference in `mkdocs.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extra_javascript:
|
||||||
|
- overrides/javascripts/extra.js
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Macros Not Rendering
|
||||||
|
|
||||||
|
Remember to use custom delimiters: `{{@ variable @}}` not `{{ variable }}`.
|
||||||
|
|
||||||
|
### Git Revision Date Warnings
|
||||||
|
|
||||||
|
New files not yet committed will show warnings. Commit the file to fix.
|
||||||
|
|
||||||
|
### Build Errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for syntax errors
|
||||||
|
uv run mkdocs build --strict
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
uv run mkdocs build -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serve Not Auto-Reloading
|
||||||
|
|
||||||
|
Some changes (like `mkdocs.yaml`) require restarting the server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference Links
|
||||||
|
|
||||||
|
- [MkDocs Documentation](https://www.mkdocs.org/)
|
||||||
|
- [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)
|
||||||
|
- [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/)
|
||||||
|
- [mkdocstrings](https://mkdocstrings.github.io/)
|
||||||
|
- [mkdocs-macros-plugin](https://mkdocs-macros-plugin.readthedocs.io/)
|
||||||
@@ -0,0 +1,416 @@
|
|||||||
|
# Release Process
|
||||||
|
|
||||||
|
This project uses **component-specific versioning**. Each component (MCP Server, Workbench, Macros) has its own version and release cycle, allowing independent updates without affecting other components.
|
||||||
|
|
||||||
|
## Components and Their Release Targets
|
||||||
|
|
||||||
|
| Component | Tag Format | Releases To |
|
||||||
|
| --------- | ---------- | ----------- |
|
||||||
|
| MCP Server | `robust-mcp-server-vX.Y.Z` | PyPI, Docker Hub, GitHub Release |
|
||||||
|
| MCP Bridge Workbench | `robust-mcp-workbench-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
| Cut Object for Magnets Macro | `macro-cut-object-for-magnets-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
| Multi Export Macro | `macro-multi-export-vX.Y.Z` | GitHub Release (archive) |
|
||||||
|
|
||||||
|
## Version Format
|
||||||
|
|
||||||
|
All versions follow [Semantic Versioning 2.0](https://semver.org/):
|
||||||
|
|
||||||
|
- `X.Y.Z` - Stable release (published to PyPI for MCP Server)
|
||||||
|
- `X.Y.Z-alpha` or `X.Y.Z-alpha.N` - Alpha pre-release (TestPyPI only)
|
||||||
|
- `X.Y.Z-beta` or `X.Y.Z-beta.N` - Beta pre-release (PyPI)
|
||||||
|
- `X.Y.Z-rc.N` - Release candidate (PyPI)
|
||||||
|
|
||||||
|
## Release Workflow Overview
|
||||||
|
|
||||||
|
Releases follow a **two-step process**:
|
||||||
|
|
||||||
|
1. **Bump**: Update version in all source files locally, then commit
|
||||||
|
2. **Tag**: Create and push the git tag (triggers CI/CD)
|
||||||
|
|
||||||
|
This ensures the version in source files matches the git tag on the same commit.
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 1. just release::bump-<component> X.Y.Z │
|
||||||
|
│ └── Updates version in all relevant files │
|
||||||
|
│ │
|
||||||
|
│ 2. git add -A && git commit -m "chore: bump ... to X.Y.Z" │
|
||||||
|
│ └── Commit the version changes │
|
||||||
|
│ │
|
||||||
|
│ 3. just release::tag-<component> X.Y.Z │
|
||||||
|
│ └── Verifies versions match, then creates & pushes tag │
|
||||||
|
│ │
|
||||||
|
│ 4. GitHub Actions workflow runs automatically │
|
||||||
|
│ └── Verifies versions, builds, and publishes release │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pre-Release Checklist
|
||||||
|
|
||||||
|
Before creating any release, complete these steps:
|
||||||
|
|
||||||
|
### 1. Check Release Status
|
||||||
|
|
||||||
|
See which components have unreleased changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just release::status
|
||||||
|
```
|
||||||
|
|
||||||
|
This shows the number of commits since the last release for each component.
|
||||||
|
|
||||||
|
### 2. Review Changes
|
||||||
|
|
||||||
|
View the specific changes for the component you're releasing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For MCP Server
|
||||||
|
just release::changes-since mcp-server
|
||||||
|
|
||||||
|
# For Workbench
|
||||||
|
just release::changes-since workbench
|
||||||
|
|
||||||
|
# For Macros
|
||||||
|
just release::changes-since macro-magnets
|
||||||
|
just release::changes-since macro-export
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Run All Quality Checks
|
||||||
|
|
||||||
|
Ensure all tests and checks pass:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just all
|
||||||
|
```
|
||||||
|
|
||||||
|
For a more thorough check including integration tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just all-with-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update CHANGELOG.md
|
||||||
|
|
||||||
|
The changelog uses a multi-component structure. Each release date section contains entries for each component that was released.
|
||||||
|
|
||||||
|
#### Draft Release Notes
|
||||||
|
|
||||||
|
Use the `draft-notes` command to generate a starting point from conventional commits:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate draft notes for a component
|
||||||
|
just release::draft-notes mcp-server
|
||||||
|
just release::draft-notes workbench
|
||||||
|
just release::draft-notes macro-magnets
|
||||||
|
just release::draft-notes macro-export
|
||||||
|
```
|
||||||
|
|
||||||
|
This categorizes commits by type (feat, fix, refactor, etc.) to help you write the changelog entry.
|
||||||
|
|
||||||
|
#### Changelog Structure
|
||||||
|
|
||||||
|
Add entries under the appropriate component section in `CHANGELOG.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## YYYY-MM-DD - Release Title (optional)
|
||||||
|
|
||||||
|
### MCP Server vX.Y.Z
|
||||||
|
|
||||||
|
Brief description of the release.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- New feature description
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
- Changed behavior description
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
- Bug fix description
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MCP Bridge Workbench vX.Y.Z
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** The changelog section header format must match exactly for the GitHub Release workflow to extract it:
|
||||||
|
|
||||||
|
- MCP Server: `### MCP Server vX.Y.Z`
|
||||||
|
- Workbench: `### MCP Bridge Workbench vX.Y.Z`
|
||||||
|
- Magnets Macro: `### Cut Object for Magnets Macro vX.Y.Z`
|
||||||
|
- Export Macro: `### Multi Export Macro vX.Y.Z`
|
||||||
|
|
||||||
|
The release workflows automatically extract the changelog section and include it in the GitHub Release body.
|
||||||
|
|
||||||
|
## Releasing Each Component
|
||||||
|
|
||||||
|
### MCP Server Release
|
||||||
|
|
||||||
|
The MCP Server is the main Python package. It uses `setuptools-scm` to derive version from git tags at build time, so there's no version bump step needed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ensure all changes are committed
|
||||||
|
git status # Should show clean working tree
|
||||||
|
|
||||||
|
# 2. Update CHANGELOG.md and commit
|
||||||
|
git add CHANGELOG.md
|
||||||
|
git commit -m "docs: update changelog for MCP Server v1.0.0"
|
||||||
|
|
||||||
|
# 3. Create and push the release tag
|
||||||
|
just release::tag-mcp-server 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens automatically:**
|
||||||
|
|
||||||
|
1. GitHub Actions validates the tag format
|
||||||
|
2. Builds Python wheel and source distribution (version from tag)
|
||||||
|
3. Tests installation on Ubuntu and macOS
|
||||||
|
4. Publishes to PyPI (or TestPyPI for alpha versions)
|
||||||
|
5. Builds multi-architecture Docker image (amd64 + arm64)
|
||||||
|
6. Pushes to Docker Hub as `spkane/freecad-robust-mcp:1.0.0`
|
||||||
|
7. Creates GitHub Release with wheel and tar.gz artifacts
|
||||||
|
|
||||||
|
**Pre-release versions:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alpha (goes to TestPyPI only)
|
||||||
|
just release::tag-mcp-server 1.0.0-alpha.1
|
||||||
|
|
||||||
|
# Beta (goes to PyPI)
|
||||||
|
just release::tag-mcp-server 1.0.0-beta.1
|
||||||
|
|
||||||
|
# Release candidate (goes to PyPI)
|
||||||
|
just release::tag-mcp-server 1.0.0-rc.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCP Bridge Workbench Release
|
||||||
|
|
||||||
|
The workbench is a FreeCAD addon that provides the MCP bridge GUI.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Bump version in source files
|
||||||
|
just release::bump-workbench 1.0.0
|
||||||
|
|
||||||
|
# 2. Review and commit the changes
|
||||||
|
git diff # Review changes
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: bump workbench to 1.0.0"
|
||||||
|
|
||||||
|
# 3. Create and push the release tag
|
||||||
|
just release::tag-workbench 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files updated by `bump-workbench`:**
|
||||||
|
|
||||||
|
- `addon/FreecadRobustMCP/freecad_mcp_bridge/__init__.py` (`__version__`)
|
||||||
|
- `package.xml` (workbench section: `<version>` and `<date>`)
|
||||||
|
|
||||||
|
**What happens automatically:**
|
||||||
|
|
||||||
|
1. GitHub Actions validates the tag format
|
||||||
|
2. Verifies version in source files matches tag
|
||||||
|
3. Creates archive (tar.gz and zip)
|
||||||
|
4. Creates GitHub Release with the archives
|
||||||
|
|
||||||
|
### Macro Releases
|
||||||
|
|
||||||
|
Each macro can be released independently.
|
||||||
|
|
||||||
|
**Cut Object for Magnets Macro:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Bump version in source files
|
||||||
|
just release::bump-macro-magnets 1.0.0
|
||||||
|
|
||||||
|
# 2. Review and commit the changes
|
||||||
|
git diff # Review changes
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: bump Cut Object for Magnets macro to 1.0.0"
|
||||||
|
|
||||||
|
# 3. Create and push the release tag
|
||||||
|
just release::tag-macro-magnets 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Multi Export Macro:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Bump version in source files
|
||||||
|
just release::bump-macro-export 1.0.0
|
||||||
|
|
||||||
|
# 2. Review and commit the changes
|
||||||
|
git diff # Review changes
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: bump Multi Export macro to 1.0.0"
|
||||||
|
|
||||||
|
# 3. Create and push the release tag
|
||||||
|
just release::tag-macro-export 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files updated by macro bump commands:**
|
||||||
|
|
||||||
|
- `macros/<MacroDir>/<Macro>.FCMacro` (`__Version__` and `__Date__`)
|
||||||
|
- `macros/<MacroDir>/README-<Macro>.md` (`**Version:**`)
|
||||||
|
- `macros/<MacroDir>/wiki-source.txt` (`|Version=` and `|Date=`)
|
||||||
|
- `package.xml` (macro section: `<version>` and `<date>`)
|
||||||
|
|
||||||
|
**What happens automatically:**
|
||||||
|
|
||||||
|
1. GitHub Actions validates the tag format
|
||||||
|
2. Verifies version in source files matches tag
|
||||||
|
3. Creates archive (tar.gz and zip)
|
||||||
|
4. Creates GitHub Release with the archives
|
||||||
|
|
||||||
|
## Verifying a Release
|
||||||
|
|
||||||
|
### Check GitHub Actions
|
||||||
|
|
||||||
|
After pushing a tag, monitor the release workflow:
|
||||||
|
|
||||||
|
1. Go to [GitHub Actions](https://github.com/spkane/freecad-robust-mcp-and-more/actions)
|
||||||
|
2. Find the workflow run triggered by your tag
|
||||||
|
3. Verify all steps complete successfully
|
||||||
|
|
||||||
|
### Verify Published Artifacts
|
||||||
|
|
||||||
|
**For MCP Server:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check PyPI
|
||||||
|
pip index versions freecad-robust-mcp
|
||||||
|
|
||||||
|
# Check Docker Hub
|
||||||
|
docker pull spkane/freecad-robust-mcp:1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**For Workbench/Macros:**
|
||||||
|
|
||||||
|
- Check the GitHub Releases page for the archive downloads
|
||||||
|
- Verify the `package.xml` version was updated
|
||||||
|
|
||||||
|
## Managing Release Tags
|
||||||
|
|
||||||
|
### List All Tags
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All tags grouped by component
|
||||||
|
just release::list-tags
|
||||||
|
|
||||||
|
# Latest version of each component
|
||||||
|
just release::latest-versions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete a Tag (If Needed)
|
||||||
|
|
||||||
|
If a release has issues and needs to be removed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just release::delete-tag robust-mcp-server-v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning "Deleting Published Releases"
|
||||||
|
Deleting a tag does not unpublish from PyPI or Docker Hub. Contact the respective registries to remove published artifacts if necessary.
|
||||||
|
|
||||||
|
### Preview a Release (Dry Run)
|
||||||
|
|
||||||
|
To see what a release tag would look like without creating it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just release::dry-run-tag mcp-server 1.0.0
|
||||||
|
just release::dry-run-tag workbench 1.0.0
|
||||||
|
just release::dry-run-tag macro-magnets 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Release Workflow Failed
|
||||||
|
|
||||||
|
1. Check the GitHub Actions logs for the specific error
|
||||||
|
2. Common issues:
|
||||||
|
- Invalid version format (must be semver)
|
||||||
|
- Version mismatch between source files and tag
|
||||||
|
- Missing secrets (PyPI token, Docker credentials)
|
||||||
|
- Tests failing during the release build
|
||||||
|
|
||||||
|
### Version Mismatch Error
|
||||||
|
|
||||||
|
If the workflow fails with "Version mismatch":
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# You forgot to bump the version before tagging
|
||||||
|
# Delete the tag
|
||||||
|
just release::delete-tag <full-tag-name>
|
||||||
|
|
||||||
|
# Bump the version
|
||||||
|
just release::bump-<component> X.Y.Z
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
git add -A && git commit -m "chore: bump <component> to X.Y.Z"
|
||||||
|
|
||||||
|
# Create the tag again
|
||||||
|
just release::tag-<component> X.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag Already Exists
|
||||||
|
|
||||||
|
If you try to create a tag that already exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Delete the existing tag first
|
||||||
|
just release::delete-tag robust-mcp-server-v1.0.0
|
||||||
|
|
||||||
|
# Then create the new tag
|
||||||
|
just release::tag-mcp-server 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrong Version Released
|
||||||
|
|
||||||
|
1. Delete the tag: `just release::delete-tag <tag>`
|
||||||
|
2. Create a new release with the correct version
|
||||||
|
3. For PyPI: you cannot re-upload the same version; increment the patch version instead
|
||||||
|
|
||||||
|
## Release Cadence Recommendations
|
||||||
|
|
||||||
|
- **MCP Server**: Release when there are significant new features or important bug fixes
|
||||||
|
- **Workbench**: Release in sync with server changes that affect the bridge protocol
|
||||||
|
- **Macros**: Release independently when macro functionality changes
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what needs releasing
|
||||||
|
just release::status
|
||||||
|
|
||||||
|
# View changes for a component
|
||||||
|
just release::changes-since mcp-server
|
||||||
|
|
||||||
|
# Draft changelog entries from commits
|
||||||
|
just release::draft-notes mcp-server
|
||||||
|
just release::draft-notes workbench
|
||||||
|
just release::draft-notes macro-magnets
|
||||||
|
just release::draft-notes macro-export
|
||||||
|
|
||||||
|
# Bump versions (for workbench and macros)
|
||||||
|
just release::bump-workbench 1.0.0
|
||||||
|
just release::bump-macro-magnets 1.0.0
|
||||||
|
just release::bump-macro-export 1.0.0
|
||||||
|
|
||||||
|
# Commit version changes
|
||||||
|
git add -A && git commit -m "chore: bump <component> to X.Y.Z"
|
||||||
|
|
||||||
|
# Create releases (verifies versions, creates and pushes tag)
|
||||||
|
just release::tag-mcp-server 1.0.0
|
||||||
|
just release::tag-workbench 1.0.0
|
||||||
|
just release::tag-macro-magnets 1.0.0
|
||||||
|
just release::tag-macro-export 1.0.0
|
||||||
|
|
||||||
|
# List existing releases
|
||||||
|
just release::list-tags
|
||||||
|
just release::latest-versions
|
||||||
|
|
||||||
|
# Extract changelog for a version (used by CI)
|
||||||
|
just release::extract-changelog mcp-server 1.0.0
|
||||||
|
|
||||||
|
# Delete a tag if needed
|
||||||
|
just release::delete-tag <full-tag-name>
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Variables for mkdocs-macros-plugin
|
||||||
|
# Use in docs with {{ variable_name }}
|
||||||
|
|
||||||
|
# Project info
|
||||||
|
project_name: FreeCAD MCP Server
|
||||||
|
package_name: freecad-robust-mcp
|
||||||
|
docker_image: spkane/freecad-robust-mcp
|
||||||
|
|
||||||
|
# URLs
|
||||||
|
repo_url: https://github.com/spkane/freecad-robust-mcp-and-more
|
||||||
|
pypi_url: https://pypi.org/project/freecad-robust-mcp/
|
||||||
|
docker_url: https://hub.docker.com/r/spkane/freecad-robust-mcp
|
||||||
|
|
||||||
|
# Ports
|
||||||
|
xmlrpc_port: 9875
|
||||||
|
socket_port: 9876
|
||||||
|
|
||||||
|
# Paths (platform-specific)
|
||||||
|
paths:
|
||||||
|
macos:
|
||||||
|
macro: ~/Library/Application Support/FreeCAD/Macro/
|
||||||
|
mod: ~/Library/Application Support/FreeCAD/Mod/
|
||||||
|
linux:
|
||||||
|
macro: ~/.local/share/FreeCAD/Macro/
|
||||||
|
mod: ~/.local/share/FreeCAD/Mod/
|
||||||
|
windows:
|
||||||
|
macro: "%APPDATA%/FreeCAD/Macro/"
|
||||||
|
mod: "%APPDATA%/FreeCAD/Mod/"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Development utility commands
|
||||||
|
# Usage: just dev::clean, just dev::repl, etc.
|
||||||
|
#
|
||||||
|
# Miscellaneous development tools and utilities.
|
||||||
|
|
||||||
|
# Project root directory (justfile_directory() returns the main justfile's directory)
|
||||||
|
project_root := justfile_directory()
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Setup & Dependencies
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Install all project dependencies (Python packages + dev tools)
|
||||||
|
install-deps:
|
||||||
|
cd {{project_root}} && uv sync --all-extras
|
||||||
|
@echo ""
|
||||||
|
@echo "Dependencies installed!"
|
||||||
|
@echo ""
|
||||||
|
@echo "To run the MCP server:"
|
||||||
|
@echo " just mcp::run # stdio mode"
|
||||||
|
@echo " just mcp::run-debug # with debug logging"
|
||||||
|
@echo " just mcp::run-http # HTTP mode for remote access"
|
||||||
|
|
||||||
|
# Install pre-commit hooks (for git commit/push integration)
|
||||||
|
install-pre-commit:
|
||||||
|
cd {{project_root}} && uv run pre-commit install
|
||||||
|
cd {{project_root}} && uv run pre-commit install --hook-type commit-msg
|
||||||
|
|
||||||
|
# Update all dependencies to latest versions (uv.lock + pre-commit hooks)
|
||||||
|
update-deps:
|
||||||
|
cd {{project_root}} && uv lock --upgrade
|
||||||
|
cd {{project_root}} && uv sync --all-extras
|
||||||
|
cd {{project_root}} && uv run pre-commit autoupdate
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Development Utilities
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Clean build artifacts and caches
|
||||||
|
clean:
|
||||||
|
rm -rf {{project_root}}/.pytest_cache {{project_root}}/.mypy_cache {{project_root}}/.ruff_cache {{project_root}}/.coverage {{project_root}}/htmlcov {{project_root}}/dist {{project_root}}/build
|
||||||
|
find {{project_root}} -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
find {{project_root}} -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
# Open Python REPL with project modules available
|
||||||
|
repl:
|
||||||
|
cd {{project_root}} && uv run python -c "import freecad_mcp; print('FreeCAD MCP loaded')" && uv run python
|
||||||
|
|
||||||
|
# Show project structure (tree view, requires 'tree' command)
|
||||||
|
tree:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if ! command -v tree &> /dev/null; then
|
||||||
|
echo "The 'tree' command is not installed."
|
||||||
|
echo "Install it with: brew install tree (macOS) or apt install tree (Linux)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd "{{project_root}}" && tree -I '__pycache__|*.egg-info|.git|.mypy_cache|.pytest_cache|.ruff_cache|htmlcov|dist|build|.venv|site' -a
|
||||||
|
|
||||||
|
# Validate project configuration files (pyproject.toml, etc.)
|
||||||
|
validate:
|
||||||
|
cd {{project_root}} && uv pip check
|
||||||
|
@echo "Package dependencies are valid."
|
||||||
@@ -67,6 +67,13 @@ clean:
|
|||||||
docker rmi {{image_name}} 2>/dev/null || true
|
docker rmi {{image_name}} 2>/dev/null || true
|
||||||
docker rmi {{registry}}/{{image_name}} 2>/dev/null || true
|
docker rmi {{registry}}/{{image_name}} 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove Docker images and build cache
|
||||||
|
clean-all:
|
||||||
|
docker rmi {{image_name}} 2>/dev/null || true
|
||||||
|
docker rmi {{registry}}/{{image_name}} 2>/dev/null || true
|
||||||
|
docker builder prune -f
|
||||||
|
@echo "Docker images and build cache cleaned."
|
||||||
|
|
||||||
# Scan Docker image for vulnerabilities (warn on all severities)
|
# Scan Docker image for vulnerabilities (warn on all severities)
|
||||||
scan:
|
scan:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|||||||
+14
-9
@@ -1,25 +1,30 @@
|
|||||||
# Documentation commands
|
# Documentation commands
|
||||||
# Usage: just documentation::build, just documentation::serve, etc.
|
# Usage: just documentation::build, just documentation::serve, etc.
|
||||||
|
|
||||||
|
# Project root directory (justfile_directory() returns the main justfile's directory)
|
||||||
|
project_root := justfile_directory()
|
||||||
|
|
||||||
# Build documentation
|
# Build documentation
|
||||||
build:
|
build:
|
||||||
uv run mkdocs build
|
cd {{project_root}} && uv run mkdocs build
|
||||||
|
|
||||||
# Build documentation with strict mode (fails on warnings, for CI)
|
# Build documentation with strict mode (fails on warnings, for CI)
|
||||||
build-strict:
|
build-strict:
|
||||||
uv run mkdocs build --strict
|
cd {{project_root}} && uv run mkdocs build --strict
|
||||||
|
|
||||||
# Serve documentation locally
|
# Serve documentation locally
|
||||||
serve:
|
serve:
|
||||||
uv run mkdocs serve
|
cd {{project_root}} && uv run mkdocs serve
|
||||||
|
|
||||||
# Build and open documentation in browser
|
# Build and open documentation in browser
|
||||||
open:
|
open:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cd "{{project_root}}"
|
||||||
uv run mkdocs build
|
uv run mkdocs build
|
||||||
@if [[ "$OSTYPE" == "darwin"* ]]; then \
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
open site/index.html; \
|
open "{{project_root}}/site/index.html"
|
||||||
elif command -v xdg-open &> /dev/null; then \
|
elif command -v xdg-open &> /dev/null; then
|
||||||
xdg-open site/index.html; \
|
xdg-open "{{project_root}}/site/index.html"
|
||||||
else \
|
else
|
||||||
echo "Documentation built at: site/index.html"; \
|
echo "Documentation built at: {{project_root}}/site/index.html"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+8
-251
@@ -252,255 +252,12 @@ run-gui-custom freecad_path:
|
|||||||
echo "FreeCAD is starting with MCP bridge..."
|
echo "FreeCAD is starting with MCP bridge..."
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Macro Installation (MultiExport, CutObjectForMagnets)
|
# Deprecated Aliases (use install:: module instead)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
# These are kept for backwards compatibility but will be removed in a future version.
|
||||||
# Install the CutObjectForMagnets macro to FreeCAD's macro directory
|
# Use the new install:: module commands instead:
|
||||||
install-cut-macro:
|
# just install::macro-cut (was: just freecad::install-cut-macro)
|
||||||
#!/usr/bin/env bash
|
# just install::macro-export (was: just freecad::install-export-macro)
|
||||||
set -euo pipefail
|
# just install::macro-all (was: just freecad::install-all-macros)
|
||||||
PROJECT_DIR="{{project_root}}"
|
# just install::mcp-bridge-workbench (was: just freecad::install-workbench)
|
||||||
|
# just install::status (was: just freecad::mcp-status)
|
||||||
# Determine macro directory based on OS
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
|
||||||
else
|
|
||||||
MACRO_DIR="$APPDATA/FreeCAD/Macro"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$MACRO_DIR"
|
|
||||||
|
|
||||||
# Copy the macro file
|
|
||||||
cp "${PROJECT_DIR}/macros/Cut_Object_for_Magnets/CutObjectForMagnets.FCMacro" "$MACRO_DIR/"
|
|
||||||
|
|
||||||
# Optionally copy the icon if it exists
|
|
||||||
if [[ -f "${PROJECT_DIR}/macros/Cut_Object_for_Magnets/CutObjectForMagnets.svg" ]]; then
|
|
||||||
cp "${PROJECT_DIR}/macros/Cut_Object_for_Magnets/CutObjectForMagnets.svg" "$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-cut-macro:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
|
||||||
else
|
|
||||||
MACRO_DIR="$APPDATA/FreeCAD/Macro"
|
|
||||||
fi
|
|
||||||
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
|
|
||||||
install-export-macro:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
PROJECT_DIR="{{project_root}}"
|
|
||||||
|
|
||||||
# Determine macro directory based on OS
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
|
||||||
else
|
|
||||||
MACRO_DIR="$APPDATA/FreeCAD/Macro"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$MACRO_DIR"
|
|
||||||
|
|
||||||
# Copy the macro file
|
|
||||||
cp "${PROJECT_DIR}/macros/Multi_Export/MultiExport.FCMacro" "$MACRO_DIR/"
|
|
||||||
|
|
||||||
# Optionally copy the icon if it exists
|
|
||||||
if [[ -f "${PROJECT_DIR}/macros/Multi_Export/MultiExport.svg" ]]; then
|
|
||||||
cp "${PROJECT_DIR}/macros/Multi_Export/MultiExport.svg" "$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-export-macro:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
|
||||||
else
|
|
||||||
MACRO_DIR="$APPDATA/FreeCAD/Macro"
|
|
||||||
fi
|
|
||||||
rm -f "$MACRO_DIR/MultiExport.FCMacro"
|
|
||||||
rm -f "$MACRO_DIR/MultiExport.svg"
|
|
||||||
echo "MultiExport macro uninstalled"
|
|
||||||
|
|
||||||
# Install all macros to FreeCAD's macro directory
|
|
||||||
install-all-macros: install-cut-macro install-export-macro
|
|
||||||
@echo "All macros installed successfully!"
|
|
||||||
|
|
||||||
# Uninstall all macros from FreeCAD's macro directory
|
|
||||||
uninstall-all-macros: uninstall-cut-macro uninstall-export-macro
|
|
||||||
@echo "All macros uninstalled successfully!"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Workbench Addon Installation
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Install the FreeCAD Robust MCP workbench addon to FreeCAD's Mod directory
|
|
||||||
install-workbench:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
PROJECT_DIR="{{project_root}}"
|
|
||||||
ADDON_NAME="FreecadRobustMCP"
|
|
||||||
|
|
||||||
# Determine FreeCAD Mod directory based on OS
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MOD_DIR="$HOME/Library/Application Support/FreeCAD/Mod"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MOD_DIR="$HOME/.local/share/FreeCAD/Mod"
|
|
||||||
else
|
|
||||||
MOD_DIR="$APPDATA/FreeCAD/Mod"
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Copy the addon directory
|
|
||||||
cp -r "${PROJECT_DIR}/addon/$ADDON_NAME" "$ADDON_DEST"
|
|
||||||
|
|
||||||
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 '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-workbench:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
ADDON_NAME="FreecadRobustMCP"
|
|
||||||
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MOD_DIR="$HOME/Library/Application Support/FreeCAD/Mod"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MOD_DIR="$HOME/.local/share/FreeCAD/Mod"
|
|
||||||
else
|
|
||||||
MOD_DIR="$APPDATA/FreeCAD/Mod"
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Check workbench installation status
|
|
||||||
mcp-status:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Determine directories based on OS
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MOD_DIR="$HOME/Library/Application Support/FreeCAD/Mod"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MOD_DIR="$HOME/.local/share/FreeCAD/Mod"
|
|
||||||
else
|
|
||||||
MOD_DIR="$APPDATA/FreeCAD/Mod"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "MCP Bridge Installation Status"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check workbench
|
|
||||||
if [[ -d "$MOD_DIR/FreecadRobustMCP" ]]; then
|
|
||||||
echo "✓ Workbench addon: INSTALLED"
|
|
||||||
echo " Path: $MOD_DIR/FreecadRobustMCP"
|
|
||||||
echo ""
|
|
||||||
echo "To use:"
|
|
||||||
echo " 1. Start FreeCAD"
|
|
||||||
echo " 2. Select 'MCP Bridge' workbench"
|
|
||||||
echo " 3. Click 'Start MCP Bridge' in toolbar"
|
|
||||||
else
|
|
||||||
echo "✗ Workbench addon: NOT INSTALLED"
|
|
||||||
echo ""
|
|
||||||
echo "To install:"
|
|
||||||
echo " just freecad::install-workbench"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check for legacy installations that should be cleaned up
|
|
||||||
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 [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
|
||||||
else
|
|
||||||
MACRO_DIR="$APPDATA/FreeCAD/Macro"
|
|
||||||
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 "=========================================="
|
|
||||||
|
|
||||||
# Check if the workbench addon is installed (alias for mcp-status)
|
|
||||||
workbench-status: mcp-status
|
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
# Installation commands for users
|
||||||
|
# Usage: just install::mcp-server, just install::mcp-bridge-workbench, etc.
|
||||||
|
#
|
||||||
|
# This module installs components for end users:
|
||||||
|
# - MCP Server (as a uv tool, available system-wide)
|
||||||
|
# - 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
|
||||||
|
[private]
|
||||||
|
_freecad-dirs:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cat << 'DIRS_EOF'
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
MOD_DIR="$HOME/Library/Application Support/FreeCAD/Mod"
|
||||||
|
MACRO_DIR="$HOME/Library/Application Support/FreeCAD/Macro"
|
||||||
|
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||||
|
MOD_DIR="$HOME/.local/share/FreeCAD/Mod"
|
||||||
|
MACRO_DIR="$HOME/.local/share/FreeCAD/Macro"
|
||||||
|
else
|
||||||
|
# Windows: validate APPDATA or use fallback
|
||||||
|
if [[ -n "$APPDATA" ]]; then
|
||||||
|
FREECAD_BASE="$APPDATA"
|
||||||
|
elif [[ -n "$HOME" ]]; then
|
||||||
|
# Fallback to standard Windows location under HOME
|
||||||
|
FREECAD_BASE="$HOME/AppData/Roaming"
|
||||||
|
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
|
||||||
|
MOD_DIR="$FREECAD_BASE/FreeCAD/Mod"
|
||||||
|
MACRO_DIR="$FREECAD_BASE/FreeCAD/Macro"
|
||||||
|
fi
|
||||||
|
DIRS_EOF
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MCP Server Installation
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Install the MCP server as a user tool (available system-wide via uv)
|
||||||
|
mcp-server:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
PROJECT_DIR="{{project_root}}"
|
||||||
|
|
||||||
|
echo "Installing MCP server as a uv tool..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Install from the local project directory
|
||||||
|
uv tool install --force "$PROJECT_DIR"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "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 ""
|
||||||
|
|
||||||
|
# Uninstall the MCP server tool
|
||||||
|
uninstall-mcp-server:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Uninstalling MCP server..."
|
||||||
|
uv tool uninstall freecad-robust-mcp || echo "MCP server was not installed as a uv tool"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
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 '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"
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# 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)"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Installation Status"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check MCP Server (installed as uv tool)
|
||||||
|
if command -v freecad-mcp &> /dev/null; then
|
||||||
|
echo "✓ MCP Server: INSTALLED (as uv tool)"
|
||||||
|
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
|
||||||
|
echo "✓ MCP Server: AVAILABLE (via dev environment)"
|
||||||
|
echo " Run: just mcp::run"
|
||||||
|
echo " For system-wide install: just install::mcp-server"
|
||||||
|
else
|
||||||
|
echo "○ MCP Server: DEV SOURCE AVAILABLE (needs setup)"
|
||||||
|
echo " Setup: uv sync --all-extras"
|
||||||
|
echo " Then run: just mcp::run"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "✗ MCP Server: NOT INSTALLED"
|
||||||
|
echo " Install: just install::mcp-server"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check workbench
|
||||||
|
if [[ -d "$MOD_DIR/FreecadRobustMCP" ]]; then
|
||||||
|
echo "✓ MCP Bridge Workbench: INSTALLED"
|
||||||
|
echo " Path: $MOD_DIR/FreecadRobustMCP"
|
||||||
|
else
|
||||||
|
echo "✗ 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
|
||||||
|
echo " ✓ CutObjectForMagnets: INSTALLED"
|
||||||
|
else
|
||||||
|
echo " ✗ CutObjectForMagnets: NOT INSTALLED"
|
||||||
|
echo " Install: just install::macro-cut"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$MACRO_DIR/MultiExport.FCMacro" ]]; then
|
||||||
|
echo " ✓ MultiExport: INSTALLED"
|
||||||
|
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 "=========================================="
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# MCP Server commands
|
||||||
|
# Usage: just mcp::run, just mcp::run-debug, etc.
|
||||||
|
#
|
||||||
|
# These commands run the MCP server that connects to FreeCAD.
|
||||||
|
# Note: FreeCAD must be running with the MCP bridge for the server to connect.
|
||||||
|
# Start FreeCAD with: just freecad::run-gui or just freecad::run-headless
|
||||||
|
|
||||||
|
# Run the MCP server (stdio mode - default for Claude Code integration)
|
||||||
|
run:
|
||||||
|
uv run freecad-mcp
|
||||||
|
|
||||||
|
# Run the MCP server with debug logging
|
||||||
|
run-debug:
|
||||||
|
FREECAD_MCP_LOG_LEVEL=DEBUG uv run freecad-mcp
|
||||||
|
|
||||||
|
# Run in HTTP mode for remote access (useful for testing or remote clients)
|
||||||
|
run-http port="8000":
|
||||||
|
FREECAD_MCP_TRANSPORT=http FREECAD_MCP_PORT={{port}} uv run freecad-mcp
|
||||||
+24
-21
@@ -4,6 +4,9 @@
|
|||||||
# Note: Tools installed via mise (gitleaks, markdownlint-cli2, etc.) use `mise exec`.
|
# Note: Tools installed via mise (gitleaks, markdownlint-cli2, etc.) use `mise exec`.
|
||||||
# Python tools use `uv run`. This ensures commands work even without mise shell activation.
|
# Python tools use `uv run`. This ensures commands work even without mise shell activation.
|
||||||
|
|
||||||
|
# Project root directory (justfile_directory() returns the main justfile's directory)
|
||||||
|
project_root := justfile_directory()
|
||||||
|
|
||||||
# Run all pre-commit checks
|
# Run all pre-commit checks
|
||||||
check: _check-safety-auth
|
check: _check-safety-auth
|
||||||
uv run pre-commit run --all-files
|
uv run pre-commit run --all-files
|
||||||
@@ -31,56 +34,56 @@ _check-safety-auth:
|
|||||||
|
|
||||||
# Format code with ruff
|
# Format code with ruff
|
||||||
format:
|
format:
|
||||||
uv run ruff format src tests
|
uv run ruff format {{project_root}}/src {{project_root}}/tests
|
||||||
uv run ruff check --fix src tests
|
uv run ruff check --fix {{project_root}}/src {{project_root}}/tests
|
||||||
|
|
||||||
# Run linting
|
# Run linting
|
||||||
lint:
|
lint:
|
||||||
uv run ruff check src tests
|
uv run ruff check {{project_root}}/src {{project_root}}/tests
|
||||||
|
|
||||||
# Run type checking
|
# Run type checking
|
||||||
typecheck:
|
typecheck:
|
||||||
uv run mypy src
|
cd {{project_root}} && uv run mypy src
|
||||||
|
|
||||||
# Run security scanning (code vulnerabilities)
|
# Run security scanning (code vulnerabilities)
|
||||||
security:
|
security:
|
||||||
uv run bandit -c pyproject.toml -r src
|
uv run bandit -c {{project_root}}/pyproject.toml -r {{project_root}}/src
|
||||||
uv run safety scan --detailed-output
|
uv run safety scan --detailed-output
|
||||||
|
|
||||||
# Run spell checking
|
# Run spell checking
|
||||||
spellcheck:
|
spellcheck:
|
||||||
uv run codespell src tests docs
|
uv run codespell {{project_root}}/src {{project_root}}/tests {{project_root}}/docs
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Secrets Scanning
|
# Secrets Scanning (quality::scan-* commands)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Run all secrets scanners
|
# Run all secrets scanners
|
||||||
secrets: secrets-gitleaks secrets-detect secrets-trufflehog # pragma: allowlist secret
|
scan: scan-gitleaks scan-detect scan-trufflehog # pragma: allowlist secret
|
||||||
@echo "All secrets scans complete!"
|
@echo "All secrets scans complete!"
|
||||||
|
|
||||||
# Run gitleaks secrets scanner (installed via mise)
|
# Run gitleaks secrets scanner (installed via mise)
|
||||||
secrets-gitleaks:
|
scan-gitleaks:
|
||||||
mise exec -- gitleaks detect --config .gitleaks.toml --verbose
|
mise exec -- gitleaks detect --source {{project_root}} --config {{project_root}}/.gitleaks.toml --verbose
|
||||||
|
|
||||||
# Run gitleaks on git history
|
# Run gitleaks on git history
|
||||||
secrets-gitleaks-history:
|
scan-gitleaks-history:
|
||||||
mise exec -- gitleaks detect --config .gitleaks.toml --verbose --log-opts="--all"
|
mise exec -- gitleaks detect --source {{project_root}} --config {{project_root}}/.gitleaks.toml --verbose --log-opts="--all"
|
||||||
|
|
||||||
# Run detect-secrets scanner (installed via uv)
|
# Run detect-secrets scanner (installed via uv)
|
||||||
secrets-detect:
|
scan-detect:
|
||||||
uv run detect-secrets scan --baseline .secrets.baseline
|
uv run detect-secrets scan --baseline {{project_root}}/.secrets.baseline
|
||||||
|
|
||||||
# Audit detect-secrets baseline (interactive)
|
# Audit detect-secrets baseline (interactive)
|
||||||
secrets-audit:
|
scan-audit:
|
||||||
uv run detect-secrets audit .secrets.baseline
|
uv run detect-secrets audit {{project_root}}/.secrets.baseline
|
||||||
|
|
||||||
# Update detect-secrets baseline with new findings
|
# Update detect-secrets baseline with new findings
|
||||||
secrets-baseline-update:
|
scan-baseline-update:
|
||||||
uv run detect-secrets scan --baseline .secrets.baseline --update
|
uv run detect-secrets scan --baseline {{project_root}}/.secrets.baseline --update
|
||||||
|
|
||||||
# Run trufflehog for verified secrets (via pre-commit - not installed standalone)
|
# Run trufflehog for verified secrets (via pre-commit - not installed standalone)
|
||||||
secrets-trufflehog:
|
scan-trufflehog:
|
||||||
uv run pre-commit run trufflehog --all-files
|
uv run pre-commit run trufflehog --all-files
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -89,8 +92,8 @@ secrets-trufflehog:
|
|||||||
|
|
||||||
# Lint all markdown files (markdownlint-cli2 installed via mise)
|
# Lint all markdown files (markdownlint-cli2 installed via mise)
|
||||||
markdown-lint:
|
markdown-lint:
|
||||||
mise exec -- markdownlint-cli2 "**/*.md"
|
cd {{project_root}} && mise exec -- markdownlint-cli2 "**/*.md" "#.venv" "#.pytest_cache" "#node_modules" "#site" "#htmlcov"
|
||||||
|
|
||||||
# Lint and fix markdown files
|
# Lint and fix markdown files
|
||||||
markdown-fix:
|
markdown-fix:
|
||||||
mise exec -- markdownlint-cli2 --fix "**/*.md"
|
cd {{project_root}} && mise exec -- markdownlint-cli2 --fix "**/*.md" "#.venv" "#.pytest_cache" "#node_modules" "#site" "#htmlcov"
|
||||||
|
|||||||
@@ -0,0 +1,732 @@
|
|||||||
|
# Release commands for component-specific versioning
|
||||||
|
# Usage: just release::bump-workbench 1.0.0, just release::tag-workbench 1.0.0, etc.
|
||||||
|
#
|
||||||
|
# Release Process (two steps):
|
||||||
|
# 1. Bump version: just release::bump-<component> <version>
|
||||||
|
# - Updates all version strings in source files
|
||||||
|
# - Commit the changes: git add -A && git commit -m "chore: bump <component> to <version>"
|
||||||
|
# 2. Create tag: just release::tag-<component> <version>
|
||||||
|
# - Verifies versions match the tag
|
||||||
|
# - Creates and pushes the git tag
|
||||||
|
# - Tag triggers GitHub Actions workflow
|
||||||
|
#
|
||||||
|
# This project uses component-specific git tags for releases:
|
||||||
|
# - robust-mcp-server-vX.Y.Z (triggers PyPI, Docker, GitHub release)
|
||||||
|
# - robust-mcp-workbench-vX.Y.Z (triggers workbench archive release)
|
||||||
|
# - macro-cut-object-for-magnets-vX.Y.Z
|
||||||
|
# - macro-multi-export-vX.Y.Z
|
||||||
|
#
|
||||||
|
# Version Format (SemVer 2.0):
|
||||||
|
# - X.Y.Z - Stable release
|
||||||
|
# - X.Y.Z-alpha - Alpha (TestPyPI only for MCP server)
|
||||||
|
# - X.Y.Z-alpha.N - Alpha with number
|
||||||
|
# - X.Y.Z-beta - Beta
|
||||||
|
# - X.Y.Z-beta.N - Beta with number
|
||||||
|
# - X.Y.Z-rc.N - Release candidate
|
||||||
|
|
||||||
|
# Project root directory
|
||||||
|
project_root := justfile_directory()
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Version Bump Commands
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Bump the MCP Bridge workbench version in all source files
|
||||||
|
bump-workbench version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="{{version}}"
|
||||||
|
TODAY=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
# Validate version format (SemVer 2.0)
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "ERROR: Version '$VERSION' is not valid semver (X.Y.Z or X.Y.Z-prerelease)"
|
||||||
|
echo "Examples: 1.0.0, 1.0.0-alpha, 1.0.0-beta.1, 1.0.0-rc.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Bumping MCP Bridge Workbench to version: $VERSION (date: $TODAY)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Update __version__ in the bridge module's __init__.py
|
||||||
|
INIT_FILE="{{project_root}}/addon/FreecadRobustMCP/freecad_mcp_bridge/__init__.py"
|
||||||
|
if [ -f "$INIT_FILE" ]; then
|
||||||
|
sed 's/^__version__ = "[^"]*"/__version__ = "'"$VERSION"'"/' "$INIT_FILE" > "$INIT_FILE.tmp" && mv "$INIT_FILE.tmp" "$INIT_FILE"
|
||||||
|
echo "Updated $INIT_FILE:"
|
||||||
|
grep "__version__" "$INIT_FILE"
|
||||||
|
else
|
||||||
|
echo "ERROR: File not found: $INIT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update the workbench version in package.xml
|
||||||
|
PACKAGE_XML="{{project_root}}/package.xml"
|
||||||
|
if [ -f "$PACKAGE_XML" ]; then
|
||||||
|
# Use awk for precise XML editing within the workbench section
|
||||||
|
awk -v version="$VERSION" -v date="$TODAY" '
|
||||||
|
/<workbench>/ { in_workbench=1 }
|
||||||
|
/<\/workbench>/ { in_workbench=0 }
|
||||||
|
in_workbench && /<version>/ {
|
||||||
|
gsub(/<version>[^<]*<\/version>/, "<version>" version "</version>")
|
||||||
|
}
|
||||||
|
in_workbench && /<date>/ {
|
||||||
|
gsub(/<date>[^<]*<\/date>/, "<date>" date "</date>")
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$PACKAGE_XML" > "$PACKAGE_XML.tmp" && mv "$PACKAGE_XML.tmp" "$PACKAGE_XML"
|
||||||
|
echo ""
|
||||||
|
echo "Updated $PACKAGE_XML (workbench section):"
|
||||||
|
grep -A3 '<workbench>' "$PACKAGE_XML" | head -5
|
||||||
|
else
|
||||||
|
echo "ERROR: File not found: $PACKAGE_XML"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Version bump complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review changes: git diff"
|
||||||
|
echo " 2. Commit: git add -A && git commit -m 'chore: bump workbench to $VERSION'"
|
||||||
|
echo " 3. Tag and release: just release::tag-workbench $VERSION"
|
||||||
|
|
||||||
|
# Private helper recipe for bumping macro versions
|
||||||
|
# Parameters: macro_dir, macro_name, macro_file_basename, readme_basename, tag_command, version
|
||||||
|
[private]
|
||||||
|
_bump-macro macro_dir macro_name macro_file_basename readme_basename tag_command version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="{{version}}"
|
||||||
|
TODAY=$(date +%Y-%m-%d)
|
||||||
|
MACRO_DIR="{{project_root}}/macros/{{macro_dir}}"
|
||||||
|
MACRO_NAME="{{macro_name}}"
|
||||||
|
|
||||||
|
# Validate version format (SemVer 2.0)
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "ERROR: Version '$VERSION' is not valid semver (X.Y.Z or X.Y.Z-prerelease)"
|
||||||
|
echo "Examples: 1.0.0, 1.0.0-alpha, 1.0.0-beta.1, 1.0.0-rc.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Bumping $MACRO_NAME Macro to version: $VERSION (date: $TODAY)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Update __Version__ and __Date__ in .FCMacro file
|
||||||
|
MACRO_FILE="$MACRO_DIR/{{macro_file_basename}}"
|
||||||
|
if [ -f "$MACRO_FILE" ]; then
|
||||||
|
sed "s/^__Version__ = [\"'].*[\"']/__Version__ = \"${VERSION}\"/" "$MACRO_FILE" > "$MACRO_FILE.tmp" && mv "$MACRO_FILE.tmp" "$MACRO_FILE"
|
||||||
|
sed "s/^__Date__ = [\"'].*[\"']/__Date__ = \"${TODAY}\"/" "$MACRO_FILE" > "$MACRO_FILE.tmp" && mv "$MACRO_FILE.tmp" "$MACRO_FILE"
|
||||||
|
echo "Updated $MACRO_FILE:"
|
||||||
|
grep -E "^__(Version|Date)__" "$MACRO_FILE"
|
||||||
|
else
|
||||||
|
echo "ERROR: File not found: $MACRO_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update README version line
|
||||||
|
README_FILE="$MACRO_DIR/{{readme_basename}}"
|
||||||
|
if [ -f "$README_FILE" ]; then
|
||||||
|
sed "s/^\*\*Version:\*\* .*/\*\*Version:\*\* ${VERSION}/" "$README_FILE" > "$README_FILE.tmp" && mv "$README_FILE.tmp" "$README_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "Updated $README_FILE:"
|
||||||
|
grep "Version:" "$README_FILE" | head -1
|
||||||
|
else
|
||||||
|
echo "WARNING: File not found: $README_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update wiki-source.txt version and date
|
||||||
|
WIKI_FILE="$MACRO_DIR/wiki-source.txt"
|
||||||
|
if [ -f "$WIKI_FILE" ]; then
|
||||||
|
sed "s/|Version=.*/|Version=${VERSION}/" "$WIKI_FILE" > "$WIKI_FILE.tmp" && mv "$WIKI_FILE.tmp" "$WIKI_FILE"
|
||||||
|
sed "s/|Date=.*/|Date=${TODAY}/" "$WIKI_FILE" > "$WIKI_FILE.tmp" && mv "$WIKI_FILE.tmp" "$WIKI_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "Updated $WIKI_FILE:"
|
||||||
|
grep -E "^\|Version=|\|Date=" "$WIKI_FILE"
|
||||||
|
else
|
||||||
|
echo "WARNING: File not found: $WIKI_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update package.xml macro version
|
||||||
|
PACKAGE_XML="{{project_root}}/package.xml"
|
||||||
|
if [ -f "$PACKAGE_XML" ]; then
|
||||||
|
awk -v name="$MACRO_NAME" -v version="$VERSION" -v date="$TODAY" '
|
||||||
|
/<macro>/ { in_macro=1 }
|
||||||
|
/<\/macro>/ { in_macro=0; found_name=0 }
|
||||||
|
in_macro && /<name>.*<\/name>/ {
|
||||||
|
if (index($0, name) > 0) found_name=1
|
||||||
|
}
|
||||||
|
in_macro && found_name && /<version>/ {
|
||||||
|
gsub(/<version>[^<]*<\/version>/, "<version>" version "</version>")
|
||||||
|
}
|
||||||
|
in_macro && found_name && /<date>/ {
|
||||||
|
gsub(/<date>[^<]*<\/date>/, "<date>" date "</date>")
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$PACKAGE_XML" > "$PACKAGE_XML.tmp" && mv "$PACKAGE_XML.tmp" "$PACKAGE_XML"
|
||||||
|
echo ""
|
||||||
|
echo "Updated $PACKAGE_XML ($MACRO_NAME section):"
|
||||||
|
grep -A4 ">$MACRO_NAME<" "$PACKAGE_XML"
|
||||||
|
else
|
||||||
|
echo "ERROR: File not found: $PACKAGE_XML"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Version bump complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review changes: git diff"
|
||||||
|
echo " 2. Commit: git add -A && git commit -m 'chore: bump $MACRO_NAME macro to $VERSION'"
|
||||||
|
echo " 3. Tag and release: just release::{{tag_command}} $VERSION"
|
||||||
|
|
||||||
|
# Bump the Cut Object for Magnets macro version in all source files
|
||||||
|
bump-macro-magnets version: (_bump-macro "Cut_Object_for_Magnets" "Cut Object for Magnets" "CutObjectForMagnets.FCMacro" "README-CutObjectForMagnets.md" "tag-macro-magnets" version)
|
||||||
|
|
||||||
|
# Bump the Multi Export macro version in all source files
|
||||||
|
bump-macro-export version: (_bump-macro "Multi_Export" "Multi Export" "MultiExport.FCMacro" "README-MultiExport.md" "tag-macro-export" version)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Tag Creation Commands
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Create and push a release tag for the MCP server (triggers PyPI + Docker release)
|
||||||
|
# Note: MCP server uses setuptools-scm, so version is derived from git tag at build time
|
||||||
|
tag-mcp-server version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
TAG="robust-mcp-server-v{{version}}"
|
||||||
|
|
||||||
|
# Validate version format
|
||||||
|
if [[ ! "{{version}}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "ERROR: Version '{{version}}' is not valid semver (X.Y.Z or X.Y.Z-prerelease)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for uncommitted changes
|
||||||
|
if ! git diff --quiet HEAD; then
|
||||||
|
echo "ERROR: You have uncommitted changes. Please commit or stash them first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating tag: $TAG"
|
||||||
|
echo ""
|
||||||
|
echo "This will trigger:"
|
||||||
|
echo " - PyPI release (beta/rc/stable) or TestPyPI (alpha)"
|
||||||
|
echo " - Docker Hub release"
|
||||||
|
echo " - GitHub release with wheel and tar.gz"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git tag -a "$TAG" -m "Release MCP Server v{{version}}"
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo ""
|
||||||
|
echo "Tag $TAG created and pushed!"
|
||||||
|
echo "Watch the release at: https://github.com/spkane/freecad-robust-mcp-and-more/actions"
|
||||||
|
|
||||||
|
# Create and push a release tag for the MCP Bridge workbench
|
||||||
|
tag-workbench version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
TAG="robust-mcp-workbench-v{{version}}"
|
||||||
|
VERSION="{{version}}"
|
||||||
|
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "ERROR: Version '$VERSION' is not valid semver"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for uncommitted changes
|
||||||
|
if ! git diff --quiet HEAD; then
|
||||||
|
echo "ERROR: You have uncommitted changes. Please commit or stash them first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify version in source files matches tag version
|
||||||
|
echo "Verifying version in source files..."
|
||||||
|
|
||||||
|
# Check __init__.py
|
||||||
|
INIT_FILE="{{project_root}}/addon/FreecadRobustMCP/freecad_mcp_bridge/__init__.py"
|
||||||
|
INIT_VERSION=$(grep -o '__version__ = "[^"]*"' "$INIT_FILE" | cut -d'"' -f2)
|
||||||
|
if [ "$INIT_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $INIT_FILE"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $INIT_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Run 'just release::bump-workbench $VERSION' first, then commit the changes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check package.xml
|
||||||
|
PACKAGE_XML="{{project_root}}/package.xml"
|
||||||
|
PKG_VERSION=$(awk '/<workbench>/,/<\/workbench>/' "$PACKAGE_XML" | grep -o '<version>[^<]*</version>' | head -1 | sed 's/<[^>]*>//g')
|
||||||
|
if [ "$PKG_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $PACKAGE_XML (workbench section)"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $PKG_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Run 'just release::bump-workbench $VERSION' first, then commit the changes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Version verification passed!"
|
||||||
|
echo ""
|
||||||
|
echo "Creating tag: $TAG"
|
||||||
|
echo ""
|
||||||
|
echo "This will trigger:"
|
||||||
|
echo " - GitHub release with workbench archive"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git tag -a "$TAG" -m "Release MCP Bridge Workbench v{{version}}"
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo ""
|
||||||
|
echo "Tag $TAG created and pushed!"
|
||||||
|
echo "Watch the release at: https://github.com/spkane/freecad-robust-mcp-and-more/actions"
|
||||||
|
|
||||||
|
# Private helper recipe for tagging macro releases
|
||||||
|
# Parameters: macro_dir, macro_name, macro_file_basename, tag_prefix, bump_command, tag_message, version
|
||||||
|
[private]
|
||||||
|
_tag-macro macro_dir macro_name macro_file_basename tag_prefix bump_command tag_message version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="{{version}}"
|
||||||
|
TAG="{{tag_prefix}}v{{version}}"
|
||||||
|
MACRO_DIR="{{project_root}}/macros/{{macro_dir}}"
|
||||||
|
MACRO_NAME="{{macro_name}}"
|
||||||
|
|
||||||
|
# Validate version format (SemVer 2.0)
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "ERROR: Version '$VERSION' is not valid semver"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for uncommitted changes
|
||||||
|
if ! git diff --quiet HEAD; then
|
||||||
|
echo "ERROR: You have uncommitted changes. Please commit or stash them first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify version in source files matches tag version
|
||||||
|
echo "Verifying version in source files..."
|
||||||
|
|
||||||
|
# Check .FCMacro file
|
||||||
|
MACRO_FILE="$MACRO_DIR/{{macro_file_basename}}"
|
||||||
|
MACRO_VERSION=$(grep -o '__Version__ = "[^"]*"' "$MACRO_FILE" | cut -d'"' -f2)
|
||||||
|
if [ "$MACRO_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $MACRO_FILE"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $MACRO_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Run 'just release::{{bump_command}} $VERSION' first, then commit the changes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check package.xml
|
||||||
|
PACKAGE_XML="{{project_root}}/package.xml"
|
||||||
|
PKG_VERSION=$(awk -v name="$MACRO_NAME" '
|
||||||
|
/<macro>/ { in_macro=1 }
|
||||||
|
/<\/macro>/ { in_macro=0; found_name=0 }
|
||||||
|
in_macro && index($0, name) > 0 { found_name=1 }
|
||||||
|
in_macro && found_name && /<version>/ {
|
||||||
|
gsub(/.*<version>/, ""); gsub(/<\/version>.*/, ""); print; exit
|
||||||
|
}
|
||||||
|
' "$PACKAGE_XML")
|
||||||
|
if [ "$PKG_VERSION" != "$VERSION" ]; then
|
||||||
|
echo "ERROR: Version mismatch in $PACKAGE_XML ($MACRO_NAME section)"
|
||||||
|
echo " Expected: $VERSION"
|
||||||
|
echo " Found: $PKG_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Run 'just release::{{bump_command}} $VERSION' first, then commit the changes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Version verification passed!"
|
||||||
|
echo ""
|
||||||
|
echo "Creating tag: $TAG"
|
||||||
|
echo ""
|
||||||
|
echo "This will trigger:"
|
||||||
|
echo " - GitHub release with macro archive"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git tag -a "$TAG" -m "{{tag_message}} v{{version}}"
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo ""
|
||||||
|
echo "Tag $TAG created and pushed!"
|
||||||
|
echo "Watch the release at: https://github.com/spkane/freecad-robust-mcp-and-more/actions"
|
||||||
|
|
||||||
|
# Create and push a release tag for the Cut Object for Magnets macro
|
||||||
|
tag-macro-magnets version: (_tag-macro "Cut_Object_for_Magnets" "Cut Object for Magnets" "CutObjectForMagnets.FCMacro" "macro-cut-object-for-magnets-" "bump-macro-magnets" "Release Cut Object for Magnets Macro" version)
|
||||||
|
|
||||||
|
# Create and push a release tag for the Multi Export macro
|
||||||
|
tag-macro-export version: (_tag-macro "Multi_Export" "Multi Export" "MultiExport.FCMacro" "macro-multi-export-" "bump-macro-export" "Release Multi Export Macro" version)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Tag Information Commands
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# List all release tags grouped by component
|
||||||
|
list-tags:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "=== MCP Server Releases ==="
|
||||||
|
git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -10
|
||||||
|
echo ""
|
||||||
|
echo "=== MCP Workbench Releases ==="
|
||||||
|
git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -10
|
||||||
|
echo ""
|
||||||
|
echo "=== Cut Object for Magnets Macro Releases ==="
|
||||||
|
git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -10
|
||||||
|
echo ""
|
||||||
|
echo "=== Multi Export Macro Releases ==="
|
||||||
|
git tag -l 'macro-multi-export-v*' --sort=-v:refname | head -10
|
||||||
|
|
||||||
|
# Show the latest version of each component
|
||||||
|
latest-versions:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "Latest versions:"
|
||||||
|
echo ""
|
||||||
|
SERVER_TAG=$(git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -n1)
|
||||||
|
WORKBENCH_TAG=$(git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -n1)
|
||||||
|
MAGNETS_TAG=$(git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -n1)
|
||||||
|
EXPORT_TAG=$(git tag -l 'macro-multi-export-v*' --sort=-v:refname | head -n1)
|
||||||
|
echo " MCP Server: ${SERVER_TAG:-none}"
|
||||||
|
echo " MCP Workbench: ${WORKBENCH_TAG:-none}"
|
||||||
|
echo " Macro Magnets: ${MAGNETS_TAG:-none}"
|
||||||
|
echo " Macro Export: ${EXPORT_TAG:-none}"
|
||||||
|
|
||||||
|
# Show commits since the last release of a component
|
||||||
|
changes-since component:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "{{component}}" in
|
||||||
|
mcp-server|server)
|
||||||
|
PREFIX="robust-mcp-server-v"
|
||||||
|
PATHS="src/freecad_mcp pyproject.toml Dockerfile"
|
||||||
|
;;
|
||||||
|
workbench)
|
||||||
|
PREFIX="robust-mcp-workbench-v"
|
||||||
|
PATHS="addon/FreecadRobustMCP"
|
||||||
|
;;
|
||||||
|
macro-magnets|magnets)
|
||||||
|
PREFIX="macro-cut-object-for-magnets-v"
|
||||||
|
PATHS="macros/Cut_Object_for_Magnets"
|
||||||
|
;;
|
||||||
|
macro-export|export)
|
||||||
|
PREFIX="macro-multi-export-v"
|
||||||
|
PATHS="macros/Multi_Export"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown component: {{component}}"
|
||||||
|
echo "Valid: mcp-server, workbench, macro-magnets, macro-export"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
LATEST_TAG=$(git tag -l "${PREFIX}*" --sort=-v:refname | head -1)
|
||||||
|
|
||||||
|
if [ -z "$LATEST_TAG" ]; then
|
||||||
|
echo "No previous releases found for {{component}}"
|
||||||
|
echo "Showing all commits for relevant paths:"
|
||||||
|
git log --oneline -- $PATHS | head -20
|
||||||
|
else
|
||||||
|
echo "Changes since $LATEST_TAG:"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$LATEST_TAG"..HEAD -- $PATHS
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Tag Management
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Delete a release tag (local and remote)
|
||||||
|
delete-tag tag:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "This will delete the tag '{{tag}}' both locally and from the remote."
|
||||||
|
echo ""
|
||||||
|
read -p "Are you sure? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete local tag
|
||||||
|
if git tag -l '{{tag}}' | grep -F -q '{{tag}}'; then
|
||||||
|
git tag -d '{{tag}}'
|
||||||
|
echo "Deleted local tag: {{tag}}"
|
||||||
|
else
|
||||||
|
echo "Local tag '{{tag}}' not found (may already be deleted)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete remote tag
|
||||||
|
if git ls-remote --tags origin | grep -F -q 'refs/tags/{{tag}}'; then
|
||||||
|
git push origin --delete '{{tag}}'
|
||||||
|
echo "Deleted remote tag: {{tag}}"
|
||||||
|
else
|
||||||
|
echo "Remote tag '{{tag}}' not found (may already be deleted)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Tag '{{tag}}' deleted."
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Release Status
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Show unreleased changes across all components
|
||||||
|
status:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Release Status - Unreleased Changes"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Helper function to count changes
|
||||||
|
count_changes() {
|
||||||
|
local prefix="$1"
|
||||||
|
local paths="$2"
|
||||||
|
local latest_tag=$(git tag -l "${prefix}*" --sort=-v:refname | head -1)
|
||||||
|
|
||||||
|
if [ -z "$latest_tag" ]; then
|
||||||
|
# No releases yet, count all commits
|
||||||
|
git log --oneline -- $paths 2>/dev/null | wc -l | tr -d ' '
|
||||||
|
else
|
||||||
|
git log --oneline "$latest_tag"..HEAD -- $paths 2>/dev/null | wc -l | tr -d ' '
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# MCP Server
|
||||||
|
SERVER_CHANGES=$(count_changes "robust-mcp-server-v" "src/freecad_mcp pyproject.toml Dockerfile")
|
||||||
|
SERVER_TAG=$(git tag -l 'robust-mcp-server-v*' --sort=-v:refname | head -1)
|
||||||
|
if [ "$SERVER_CHANGES" -gt 0 ]; then
|
||||||
|
echo "MCP Server: $SERVER_CHANGES unreleased commit(s)"
|
||||||
|
echo " Latest: ${SERVER_TAG:-none}"
|
||||||
|
echo " View: just release::changes-since mcp-server"
|
||||||
|
else
|
||||||
|
echo "MCP Server: up to date (${SERVER_TAG:-no releases})"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Workbench
|
||||||
|
WORKBENCH_CHANGES=$(count_changes "robust-mcp-workbench-v" "addon/FreecadRobustMCP")
|
||||||
|
WORKBENCH_TAG=$(git tag -l 'robust-mcp-workbench-v*' --sort=-v:refname | head -1)
|
||||||
|
if [ "$WORKBENCH_CHANGES" -gt 0 ]; then
|
||||||
|
echo "MCP Workbench: $WORKBENCH_CHANGES unreleased commit(s)"
|
||||||
|
echo " Latest: ${WORKBENCH_TAG:-none}"
|
||||||
|
echo " View: just release::changes-since workbench"
|
||||||
|
else
|
||||||
|
echo "MCP Workbench: up to date (${WORKBENCH_TAG:-no releases})"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Macro Magnets
|
||||||
|
MAGNETS_CHANGES=$(count_changes "macro-cut-object-for-magnets-v" "macros/Cut_Object_for_Magnets")
|
||||||
|
MAGNETS_TAG=$(git tag -l 'macro-cut-object-for-magnets-v*' --sort=-v:refname | head -1)
|
||||||
|
if [ "$MAGNETS_CHANGES" -gt 0 ]; then
|
||||||
|
echo "Macro (Cut Object for Magnets): $MAGNETS_CHANGES unreleased commit(s)"
|
||||||
|
echo " Latest: ${MAGNETS_TAG:-none}"
|
||||||
|
echo " View: just release::changes-since macro-magnets"
|
||||||
|
else
|
||||||
|
echo "Macro (Cut Object for Magnets): up to date (${MAGNETS_TAG:-no releases})"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Macro Export
|
||||||
|
EXPORT_CHANGES=$(count_changes "macro-multi-export-v" "macros/Multi_Export")
|
||||||
|
EXPORT_TAG=$(git tag -l 'macro-multi-export-v*' --sort=-v:refname | head -1)
|
||||||
|
if [ "$EXPORT_CHANGES" -gt 0 ]; then
|
||||||
|
echo "Macro (Multi Export): $EXPORT_CHANGES unreleased commit(s)"
|
||||||
|
echo " Latest: ${EXPORT_TAG:-none}"
|
||||||
|
echo " View: just release::changes-since macro-export"
|
||||||
|
else
|
||||||
|
echo "Macro (Multi Export): up to date (${EXPORT_TAG:-no releases})"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Changelog / Release Notes Helpers
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Draft release notes for a component by extracting conventional commits since last release
|
||||||
|
draft-notes component:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "{{component}}" in
|
||||||
|
mcp-server|server)
|
||||||
|
PREFIX="robust-mcp-server-v"
|
||||||
|
PATHS="src/freecad_mcp pyproject.toml Dockerfile"
|
||||||
|
COMPONENT_NAME="MCP Server"
|
||||||
|
;;
|
||||||
|
workbench)
|
||||||
|
PREFIX="robust-mcp-workbench-v"
|
||||||
|
PATHS="addon/FreecadRobustMCP"
|
||||||
|
COMPONENT_NAME="MCP Bridge Workbench"
|
||||||
|
;;
|
||||||
|
macro-magnets|magnets)
|
||||||
|
PREFIX="macro-cut-object-for-magnets-v"
|
||||||
|
PATHS="macros/Cut_Object_for_Magnets"
|
||||||
|
COMPONENT_NAME="Cut Object for Magnets Macro"
|
||||||
|
;;
|
||||||
|
macro-export|export)
|
||||||
|
PREFIX="macro-multi-export-v"
|
||||||
|
PATHS="macros/Multi_Export"
|
||||||
|
COMPONENT_NAME="Multi Export Macro"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown component: {{component}}"
|
||||||
|
echo "Valid: mcp-server, workbench, macro-magnets, macro-export"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
LATEST_TAG=$(git tag -l "${PREFIX}*" --sort=-v:refname | head -1)
|
||||||
|
|
||||||
|
echo "# Draft Release Notes for $COMPONENT_NAME"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -z "$LATEST_TAG" ]; then
|
||||||
|
echo "No previous releases found. Showing all commits for component paths."
|
||||||
|
echo ""
|
||||||
|
REV_RANGE="HEAD"
|
||||||
|
else
|
||||||
|
echo "Changes since: $LATEST_TAG"
|
||||||
|
echo ""
|
||||||
|
REV_RANGE="${LATEST_TAG}..HEAD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get commits and categorize by conventional commit type
|
||||||
|
echo "## Added"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ feat(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "## Changed"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ (refactor|perf|style)(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "## Fixed"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ fix(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "## Documentation"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -iE "^[a-f0-9]+ docs(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "## Other Changes"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | grep -ivE "^[a-f0-9]+ (feat|fix|refactor|perf|style|docs|test|ci|build|chore)(\(|:)" | sed -E 's/^[a-f0-9]+ /- /' || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
echo "## All Commits (chronological)"
|
||||||
|
echo ""
|
||||||
|
git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | sed -E 's/^[a-f0-9]+ /- /' || echo "(no commits)"
|
||||||
|
|
||||||
|
# Extract changelog section for a specific component version (for GitHub Release body)
|
||||||
|
extract-changelog component version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Match header exactly as it appears in CHANGELOG.md
|
||||||
|
case "{{component}}" in
|
||||||
|
mcp-server|server)
|
||||||
|
HEADER="### MCP Server v{{version}}"
|
||||||
|
;;
|
||||||
|
workbench)
|
||||||
|
HEADER="### MCP Bridge Workbench v{{version}}"
|
||||||
|
;;
|
||||||
|
macro-magnets|magnets)
|
||||||
|
HEADER="### Cut Object for Magnets Macro v{{version}}"
|
||||||
|
;;
|
||||||
|
macro-export|export)
|
||||||
|
HEADER="### Multi Export Macro v{{version}}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown component: {{component}}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CHANGELOG="{{project_root}}/CHANGELOG.md"
|
||||||
|
|
||||||
|
# Extract section between this version header and the next component header or separator
|
||||||
|
# Only exit on: "---" separator OR "### " followed by component name (capital letter)
|
||||||
|
# This allows #### Added, #### Changed, etc. to be included
|
||||||
|
awk -v header="$HEADER" '
|
||||||
|
BEGIN { found=0 }
|
||||||
|
$0 == header || $0 == header " " { found=1; next }
|
||||||
|
found && /^---$/ { exit }
|
||||||
|
found && /^### [A-Z]/ { exit }
|
||||||
|
found { print }
|
||||||
|
' "$CHANGELOG"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Dry Run Commands (preview without pushing)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Preview what a release tag would look like (no actual tag created)
|
||||||
|
dry-run-tag component version:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Validate version format (SemVer 2.0)
|
||||||
|
if [[ ! "{{version}}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo "Error: Invalid version format '{{version}}'"
|
||||||
|
echo "Expected: X.Y.Z or X.Y.Z-prerelease (e.g., 1.0.0, 1.0.0-alpha, 1.0.0-beta.1)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "{{component}}" in
|
||||||
|
mcp-server|server)
|
||||||
|
TAG="robust-mcp-server-v{{version}}"
|
||||||
|
echo "Would create tag: $TAG"
|
||||||
|
echo "Triggers: PyPI, Docker Hub, GitHub Release"
|
||||||
|
;;
|
||||||
|
workbench)
|
||||||
|
TAG="robust-mcp-workbench-v{{version}}"
|
||||||
|
echo "Would create tag: $TAG"
|
||||||
|
echo "Triggers: GitHub Release with workbench archive"
|
||||||
|
;;
|
||||||
|
macro-magnets|magnets)
|
||||||
|
TAG="macro-cut-object-for-magnets-v{{version}}"
|
||||||
|
echo "Would create tag: $TAG"
|
||||||
|
echo "Triggers: GitHub Release with macro archive"
|
||||||
|
;;
|
||||||
|
macro-export|export)
|
||||||
|
TAG="macro-multi-export-v{{version}}"
|
||||||
|
echo "Would create tag: $TAG"
|
||||||
|
echo "Triggers: GitHub Release with macro archive"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown component: {{component}}"
|
||||||
|
echo "Valid: mcp-server, workbench, macro-magnets, macro-export"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
+15
-8
@@ -1,32 +1,39 @@
|
|||||||
# Testing commands
|
# Testing commands
|
||||||
# Usage: just testing::unit, just testing::integration, etc.
|
# Usage: just testing::unit, just testing::integration, etc.
|
||||||
|
|
||||||
|
# Project root directory (justfile_directory() returns the main justfile's directory)
|
||||||
|
project_root := justfile_directory()
|
||||||
|
|
||||||
# Run unit tests only (excludes integration tests)
|
# Run unit tests only (excludes integration tests)
|
||||||
unit:
|
unit:
|
||||||
uv run pytest --ignore=tests/integration
|
uv run pytest {{project_root}}/tests/unit
|
||||||
|
|
||||||
# Run tests with coverage (excludes integration tests)
|
# Run tests with coverage (excludes integration tests)
|
||||||
cov:
|
cov:
|
||||||
uv run pytest --ignore=tests/integration --cov=src/freecad_mcp --cov-report=term-missing --cov-report=html
|
cd {{project_root}} && uv run pytest tests/unit --cov=freecad_mcp --cov-report=term-missing --cov-report=html:{{project_root}}/htmlcov
|
||||||
|
|
||||||
# Run tests without slow markers (excludes integration tests)
|
# Run tests without slow markers (excludes integration tests)
|
||||||
fast:
|
fast:
|
||||||
uv run pytest --ignore=tests/integration -m "not slow"
|
uv run pytest {{project_root}}/tests/unit -m "not slow"
|
||||||
|
|
||||||
# Run only integration tests (requires running FreeCAD MCP bridge)
|
# Run only integration tests (requires running FreeCAD MCP bridge)
|
||||||
integration:
|
integration:
|
||||||
uv run pytest tests/integration -v
|
uv run pytest {{project_root}}/tests/integration -v
|
||||||
|
|
||||||
# Run tests with verbose output (excludes integration tests)
|
# Run tests with verbose output (excludes integration tests)
|
||||||
verbose:
|
verbose:
|
||||||
uv run pytest --ignore=tests/integration -v --tb=long
|
uv run pytest {{project_root}}/tests/unit -v --tb=long
|
||||||
|
|
||||||
# Run all tests including integration (requires running FreeCAD MCP bridge)
|
# Run all tests including integration (requires running FreeCAD MCP bridge)
|
||||||
all:
|
all:
|
||||||
uv run pytest
|
uv run pytest {{project_root}}/tests
|
||||||
|
|
||||||
|
# Run tests in watch mode (re-runs on file changes)
|
||||||
|
watch:
|
||||||
|
uv run pytest-watch {{project_root}}/tests/unit
|
||||||
|
|
||||||
# Run integration tests with automatic FreeCAD headless startup
|
# Run integration tests with automatic FreeCAD headless startup
|
||||||
integration-auto:
|
integration-freecad:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "Starting FreeCAD headless server for integration tests..."
|
echo "Starting FreeCAD headless server for integration tests..."
|
||||||
@@ -59,7 +66,7 @@ integration-auto:
|
|||||||
|
|
||||||
# Run integration tests
|
# Run integration tests
|
||||||
TEST_EXIT_CODE=0
|
TEST_EXIT_CODE=0
|
||||||
uv run pytest tests/integration -v || TEST_EXIT_CODE=$?
|
uv run pytest "{{project_root}}/tests/integration" -v || TEST_EXIT_CODE=$?
|
||||||
|
|
||||||
# Stop FreeCAD
|
# Stop FreeCAD
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
# FreeCAD MCP Server - Development Workflow Commands
|
# FreeCAD MCP Server - Development Workflow Commands
|
||||||
# https://just.systems/
|
# https://just.systems/
|
||||||
#
|
#
|
||||||
# Commands are organized into modules:
|
# Commands are organized into modules. Run `just` to see top-level commands,
|
||||||
# just docker::build - Docker commands
|
# or `just list-<module>` to see commands in a specific module:
|
||||||
# just quality::check - Code quality commands
|
# just list-mcp - MCP server commands
|
||||||
# just testing::unit - Testing commands
|
# just list-freecad - FreeCAD plugin/macro commands
|
||||||
# just freecad::run-gui - FreeCAD commands
|
# just list-install - Installation commands
|
||||||
# just documentation::build - Documentation commands
|
# just list-quality - Code quality commands
|
||||||
# just coderabbit::review - AI code review commands
|
# just list-testing - Test commands
|
||||||
|
# just list-docker - Docker build/run commands
|
||||||
|
# just list-documentation - Documentation commands
|
||||||
|
# just list-dev - Development utilities
|
||||||
|
# just list-release - Release and tagging commands
|
||||||
|
# just list-coderabbit - AI code review commands
|
||||||
#
|
#
|
||||||
# Or use the shortcut commands defined below.
|
# Or use `just --list --list-submodules` to see everything at once.
|
||||||
|
|
||||||
# Import modules
|
# Import modules
|
||||||
mod docker 'just/docker.just'
|
|
||||||
mod quality 'just/quality.just'
|
|
||||||
mod testing 'just/testing.just'
|
|
||||||
mod freecad 'just/freecad.just'
|
|
||||||
mod documentation 'just/documentation.just'
|
|
||||||
mod coderabbit 'just/coderabbit.just'
|
mod coderabbit 'just/coderabbit.just'
|
||||||
|
mod dev 'just/dev.just'
|
||||||
|
mod docker 'just/docker.just'
|
||||||
|
mod documentation 'just/documentation.just'
|
||||||
|
mod freecad 'just/freecad.just'
|
||||||
|
mod install 'just/install.just'
|
||||||
|
mod mcp 'just/mcp.just'
|
||||||
|
mod quality 'just/quality.just'
|
||||||
|
mod release 'just/release.just'
|
||||||
|
mod testing 'just/testing.just'
|
||||||
|
|
||||||
# Default recipe - show available commands
|
# Default recipe - show top-level commands and available modules
|
||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
@@ -27,156 +36,70 @@ default:
|
|||||||
# Setup & Installation
|
# Setup & Installation
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Install all dependencies
|
# Full development environment setup (installs deps + pre-commit hooks)
|
||||||
install:
|
setup: (dev::install-deps) (dev::install-pre-commit)
|
||||||
uv sync --all-extras
|
|
||||||
|
|
||||||
# Install pre-commit hooks
|
|
||||||
setup-hooks:
|
|
||||||
uv run pre-commit install
|
|
||||||
uv run pre-commit install --hook-type commit-msg
|
|
||||||
|
|
||||||
# Full development environment setup
|
|
||||||
setup: install setup-hooks
|
|
||||||
@echo "Development environment ready!"
|
@echo "Development environment ready!"
|
||||||
|
|
||||||
# Update all dependencies to latest versions
|
|
||||||
update-deps:
|
|
||||||
uv lock --upgrade
|
|
||||||
uv sync --all-extras
|
|
||||||
uv run pre-commit autoupdate
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Shortcut Commands (aliases to module commands)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Run all pre-commit checks (shortcut for quality::check)
|
|
||||||
check: (quality::check)
|
|
||||||
|
|
||||||
# Format code with ruff (shortcut for quality::format)
|
|
||||||
format: (quality::format)
|
|
||||||
|
|
||||||
# Run linting (shortcut for quality::lint)
|
|
||||||
lint: (quality::lint)
|
|
||||||
|
|
||||||
# Run type checking (shortcut for quality::typecheck)
|
|
||||||
typecheck: (quality::typecheck)
|
|
||||||
|
|
||||||
# Run security scanning (shortcut for quality::security)
|
|
||||||
security: (quality::security)
|
|
||||||
|
|
||||||
# Run spell checking (shortcut for quality::spellcheck)
|
|
||||||
spellcheck: (quality::spellcheck)
|
|
||||||
|
|
||||||
# Run all secrets scanners (shortcut for quality::secrets)
|
|
||||||
secrets: (quality::secrets)
|
|
||||||
|
|
||||||
# Lint markdown files (shortcut for quality::markdown-lint)
|
|
||||||
markdown-lint: (quality::markdown-lint)
|
|
||||||
|
|
||||||
# Fix markdown files (shortcut for quality::markdown-fix)
|
|
||||||
markdown-fix: (quality::markdown-fix)
|
|
||||||
|
|
||||||
# Run unit tests (shortcut for testing::unit)
|
|
||||||
test: (testing::unit)
|
|
||||||
|
|
||||||
# Run tests with coverage (shortcut for testing::cov)
|
|
||||||
test-cov: (testing::cov)
|
|
||||||
|
|
||||||
# Run fast tests (shortcut for testing::fast)
|
|
||||||
test-fast: (testing::fast)
|
|
||||||
|
|
||||||
# Run integration tests (shortcut for testing::integration)
|
|
||||||
test-integration: (testing::integration)
|
|
||||||
|
|
||||||
# Run tests with verbose output (shortcut for testing::verbose)
|
|
||||||
test-verbose: (testing::verbose)
|
|
||||||
|
|
||||||
# Run all tests (shortcut for testing::all)
|
|
||||||
test-all: (testing::all)
|
|
||||||
|
|
||||||
# Build documentation (shortcut for documentation::build)
|
|
||||||
docs: (documentation::build)
|
|
||||||
|
|
||||||
# Build documentation with strict mode (shortcut for documentation::build-strict)
|
|
||||||
docs-strict: (documentation::build-strict)
|
|
||||||
|
|
||||||
# Serve documentation (shortcut for documentation::serve)
|
|
||||||
docs-serve: (documentation::serve)
|
|
||||||
|
|
||||||
# AI code review of staged changes (shortcut for coderabbit::review)
|
|
||||||
review: (coderabbit::review)
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Running the MCP Server
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Run the MCP server (stdio mode)
|
|
||||||
run:
|
|
||||||
uv run freecad-mcp
|
|
||||||
|
|
||||||
# Run the MCP server with debug logging
|
|
||||||
run-debug:
|
|
||||||
FREECAD_MCP_LOG_LEVEL=DEBUG uv run freecad-mcp
|
|
||||||
|
|
||||||
# Run in HTTP mode for remote access
|
|
||||||
run-http port="8000":
|
|
||||||
FREECAD_MCP_TRANSPORT=http FREECAD_MCP_PORT={{port}} uv run freecad-mcp
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# FreeCAD Shortcuts (aliases to freecad module)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Run FreeCAD GUI with MCP bridge (shortcut for freecad::run-gui)
|
|
||||||
run-gui: (freecad::run-gui)
|
|
||||||
|
|
||||||
# Run FreeCAD headless with MCP bridge (shortcut for freecad::run-headless)
|
|
||||||
run-headless: (freecad::run-headless)
|
|
||||||
|
|
||||||
# Install the CutObjectForMagnets macro (shortcut for freecad::install-cut-macro)
|
|
||||||
install-cut-macro: (freecad::install-cut-macro)
|
|
||||||
|
|
||||||
# Uninstall the CutObjectForMagnets macro (shortcut for freecad::uninstall-cut-macro)
|
|
||||||
uninstall-cut-macro: (freecad::uninstall-cut-macro)
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Combined Workflows
|
# Combined Workflows
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Run all quality checks and unit tests (use before committing)
|
# Run all quality checks and unit tests (use before committing)
|
||||||
all: check test
|
all: (quality::check) (testing::unit)
|
||||||
@echo "All checks passed!"
|
@echo "All checks passed!"
|
||||||
|
|
||||||
# Run integration tests with automatic FreeCAD headless startup
|
|
||||||
integration: (testing::integration-auto)
|
|
||||||
|
|
||||||
# Run all quality checks and ALL tests including integration
|
# Run all quality checks and ALL tests including integration
|
||||||
all-integration: check test integration
|
all-with-integration: (quality::check) (testing::unit) (testing::integration-freecad)
|
||||||
@echo "All checks and integration tests passed!"
|
@echo "All checks and integration tests passed!"
|
||||||
|
|
||||||
# Full CI pipeline (unit tests only)
|
# Full CI pipeline (pre-commit checks + unit tests with coverage)
|
||||||
ci: check test-cov
|
ci: (quality::check) (testing::cov)
|
||||||
@echo "CI pipeline complete!"
|
@echo "CI pipeline complete!"
|
||||||
|
|
||||||
# Clean build artifacts
|
|
||||||
clean:
|
|
||||||
rm -rf .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov dist build
|
|
||||||
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
||||||
find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Development Helpers
|
# Module Listings (use these to explore available commands)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Open Python REPL with project modules available
|
# List MCP server commands
|
||||||
repl:
|
list-mcp:
|
||||||
uv run python -c "import freecad_mcp; print('FreeCAD MCP loaded')" && uv run python
|
@just --list mcp
|
||||||
|
|
||||||
# Show project structure
|
# List FreeCAD plugin and macro commands
|
||||||
tree:
|
list-freecad:
|
||||||
tree -I '__pycache__|*.egg-info|.git|.mypy_cache|.pytest_cache|.ruff_cache|htmlcov|dist|build' -a
|
@just --list freecad
|
||||||
|
|
||||||
# Validate project configuration
|
# List installation commands
|
||||||
validate:
|
list-install:
|
||||||
uv pip check
|
@just --list install
|
||||||
uv run validate-pyproject pyproject.toml
|
|
||||||
|
# List code quality commands
|
||||||
|
list-quality:
|
||||||
|
@just --list quality
|
||||||
|
|
||||||
|
# List testing commands
|
||||||
|
list-testing:
|
||||||
|
@just --list testing
|
||||||
|
|
||||||
|
# List Docker build/run commands
|
||||||
|
list-docker:
|
||||||
|
@just --list docker
|
||||||
|
|
||||||
|
# List documentation commands
|
||||||
|
list-documentation:
|
||||||
|
@just --list documentation
|
||||||
|
|
||||||
|
# List development utility commands
|
||||||
|
list-dev:
|
||||||
|
@just --list dev
|
||||||
|
|
||||||
|
# List release and tagging commands
|
||||||
|
list-release:
|
||||||
|
@just --list release
|
||||||
|
|
||||||
|
# List AI code review commands
|
||||||
|
list-coderabbit:
|
||||||
|
@just --list coderabbit
|
||||||
|
|
||||||
|
# List ALL commands from all modules
|
||||||
|
list-all:
|
||||||
|
@just --list --list-submodules
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 0.5.0-beta
|
||||||
**FreeCAD Version:** 0.19 or later
|
**FreeCAD Version:** 0.19 or later
|
||||||
**License:** MIT
|
**License:** MIT
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ This ensures holes only go where they're safe!
|
|||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
|
|
||||||
### v1.0.0 (2025-01-01)
|
### v0.5.0-beta (2026-01-05)
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
- XY, XZ, YZ preset plane support
|
- XY, XZ, YZ preset plane support
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 0.5.0-beta
|
||||||
**FreeCAD Version:** 0.19 or later
|
**FreeCAD Version:** 0.19 or later
|
||||||
**License:** MIT
|
**License:** MIT
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ When multiple objects are selected:
|
|||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
|
|
||||||
### v1.0.0 (2025)
|
### v0.5.0-beta (2026-01-05)
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
- 8 export format support (STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF)
|
- 8 export format support (STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF)
|
||||||
|
|||||||
+67
-9
@@ -6,33 +6,42 @@ repo_name: spkane/freecad-robust-mcp-and-more
|
|||||||
|
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
|
custom_dir: docs/overrides
|
||||||
palette:
|
palette:
|
||||||
- scheme: default
|
# Dark mode as default
|
||||||
primary: blue
|
|
||||||
accent: blue
|
|
||||||
toggle:
|
|
||||||
icon: material/brightness-7
|
|
||||||
name: Switch to dark mode
|
|
||||||
- scheme: slate
|
- scheme: slate
|
||||||
primary: blue
|
primary: deep purple
|
||||||
accent: blue
|
accent: purple
|
||||||
toggle:
|
toggle:
|
||||||
icon: material/brightness-4
|
icon: material/brightness-4
|
||||||
name: Switch to light mode
|
name: Switch to light mode
|
||||||
|
- scheme: default
|
||||||
|
primary: deep purple
|
||||||
|
accent: purple
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: Switch to dark mode
|
||||||
features:
|
features:
|
||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.code.annotate
|
- content.code.annotate
|
||||||
|
- content.tabs.link
|
||||||
- navigation.instant
|
- navigation.instant
|
||||||
- navigation.tracking
|
- navigation.tracking
|
||||||
- navigation.tabs
|
- navigation.tabs
|
||||||
- navigation.sections
|
- navigation.sections
|
||||||
- toc.integrate
|
- navigation.top
|
||||||
|
- toc.follow
|
||||||
- search.suggest
|
- search.suggest
|
||||||
- search.highlight
|
- search.highlight
|
||||||
|
- search.share
|
||||||
|
icon:
|
||||||
|
repo: fontawesome/brands/github
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- pymdownx.highlight:
|
- pymdownx.highlight:
|
||||||
anchor_linenums: true
|
anchor_linenums: true
|
||||||
|
line_spans: __span
|
||||||
|
pygments_lang_class: true
|
||||||
- pymdownx.superfences:
|
- pymdownx.superfences:
|
||||||
custom_fences:
|
custom_fences:
|
||||||
- name: mermaid
|
- name: mermaid
|
||||||
@@ -40,13 +49,28 @@ markdown_extensions:
|
|||||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||||
- pymdownx.inlinehilite
|
- pymdownx.inlinehilite
|
||||||
- pymdownx.snippets
|
- pymdownx.snippets
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- pymdownx.tasklist:
|
||||||
|
custom_checkbox: true
|
||||||
|
- pymdownx.emoji:
|
||||||
|
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||||
|
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||||
|
- pymdownx.keys
|
||||||
|
- pymdownx.mark
|
||||||
|
- pymdownx.critic
|
||||||
|
- pymdownx.caret
|
||||||
|
- pymdownx.tilde
|
||||||
- admonition
|
- admonition
|
||||||
- pymdownx.details
|
- pymdownx.details
|
||||||
- attr_list
|
- attr_list
|
||||||
- md_in_html
|
- md_in_html
|
||||||
|
- def_list
|
||||||
|
- footnotes
|
||||||
- tables
|
- tables
|
||||||
- toc:
|
- toc:
|
||||||
permalink: true
|
permalink: true
|
||||||
|
toc_depth: 3
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
@@ -58,6 +82,37 @@ plugins:
|
|||||||
show_source: true
|
show_source: true
|
||||||
show_root_heading: true
|
show_root_heading: true
|
||||||
show_category_heading: true
|
show_category_heading: true
|
||||||
|
- git-revision-date-localized:
|
||||||
|
enable_creation_date: true
|
||||||
|
type: timeago
|
||||||
|
- minify:
|
||||||
|
minify_html: true
|
||||||
|
- glightbox:
|
||||||
|
touchNavigation: true
|
||||||
|
loop: false
|
||||||
|
effect: zoom
|
||||||
|
slide_effect: slide
|
||||||
|
width: 100%
|
||||||
|
height: auto
|
||||||
|
zoomable: true
|
||||||
|
draggable: true
|
||||||
|
- macros:
|
||||||
|
include_yaml:
|
||||||
|
- docs/variables.yaml
|
||||||
|
# Use different delimiters to avoid conflicts with Python dicts in code blocks
|
||||||
|
j2_variable_start_string: "{{@"
|
||||||
|
j2_variable_end_string: "@}}"
|
||||||
|
j2_block_start_string: "{%@"
|
||||||
|
j2_block_end_string: "@%}"
|
||||||
|
|
||||||
|
extra:
|
||||||
|
social:
|
||||||
|
- icon: fontawesome/brands/github
|
||||||
|
link: https://github.com/spkane/freecad-robust-mcp-and-more
|
||||||
|
- icon: fontawesome/brands/docker
|
||||||
|
link: https://hub.docker.com/r/spkane/freecad-robust-mcp
|
||||||
|
- icon: fontawesome/brands/python
|
||||||
|
link: https://pypi.org/project/freecad-robust-mcp/
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
@@ -82,3 +137,6 @@ nav:
|
|||||||
- Development:
|
- Development:
|
||||||
- Contributing: development/contributing.md
|
- Contributing: development/contributing.md
|
||||||
- Architecture: development/architecture.md
|
- Architecture: development/architecture.md
|
||||||
|
- Detailed Architecture: development/architecture-detailed.md
|
||||||
|
- Release Process: development/releasing.md
|
||||||
|
- MkDocs Guide: development/mkdocs-guide.md
|
||||||
|
|||||||
+8
-2
@@ -5,8 +5,8 @@
|
|||||||
<description>A collection of FreeCAD macros including an MCP (Model Context Protocol) bridge for AI assistant integration, multi-format export utilities, and a tool for cutting objects with aligned magnet holes for 3D printing.</description>
|
<description>A collection of FreeCAD macros including an MCP (Model Context Protocol) bridge for AI assistant integration, multi-format export utilities, and a tool for cutting objects with aligned magnet holes for 3D printing.</description>
|
||||||
<icon>addon/FreecadRobustMCP/FreecadRobustMCP.svg</icon>
|
<icon>addon/FreecadRobustMCP/FreecadRobustMCP.svg</icon>
|
||||||
|
|
||||||
<version>0.5.0-beta</version>
|
<!-- Note: Each component has its own version below. This top-level date is for reference only. -->
|
||||||
<date>2026-01-05</date>
|
<date>2026-01-07</date>
|
||||||
|
|
||||||
<maintainer email="spkane@gmail.com">Sean P. Kane</maintainer>
|
<maintainer email="spkane@gmail.com">Sean P. Kane</maintainer>
|
||||||
<author email="spkane@gmail.com">Sean P. Kane</author>
|
<author email="spkane@gmail.com">Sean P. Kane</author>
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
|
|
||||||
<workbench>
|
<workbench>
|
||||||
<name>MCP Bridge</name>
|
<name>MCP Bridge</name>
|
||||||
|
<version>0.5.0-beta</version>
|
||||||
|
<date>2026-01-07</date>
|
||||||
<description>MCP (Model Context Protocol) bridge workbench for AI assistant integration with FreeCAD. Works in both GUI mode (with full visual features including screenshots, colors, and camera control) and headless mode (for automation and CI/CD). Provides toolbar commands to start, stop, and monitor the MCP bridge server. Supports XML-RPC and JSON-RPC protocols. Connect Claude Code, Cursor, or other MCP-compatible AI assistants to control FreeCAD programmatically with 82+ CAD tools.</description>
|
<description>MCP (Model Context Protocol) bridge workbench for AI assistant integration with FreeCAD. Works in both GUI mode (with full visual features including screenshots, colors, and camera control) and headless mode (for automation and CI/CD). Provides toolbar commands to start, stop, and monitor the MCP bridge server. Supports XML-RPC and JSON-RPC protocols. Connect Claude Code, Cursor, or other MCP-compatible AI assistants to control FreeCAD programmatically with 82+ CAD tools.</description>
|
||||||
<classname>FreecadRobustMCPWorkbench</classname>
|
<classname>FreecadRobustMCPWorkbench</classname>
|
||||||
<subdirectory>./addon/FreecadRobustMCP/</subdirectory>
|
<subdirectory>./addon/FreecadRobustMCP/</subdirectory>
|
||||||
@@ -46,6 +48,8 @@
|
|||||||
|
|
||||||
<macro>
|
<macro>
|
||||||
<name>Multi Export</name>
|
<name>Multi Export</name>
|
||||||
|
<version>0.5.0-beta</version>
|
||||||
|
<date>2026-01-07</date>
|
||||||
<description>Export selected bodies to multiple file formats (STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF) simultaneously with configurable mesh options.</description>
|
<description>Export selected bodies to multiple file formats (STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF) simultaneously with configurable mesh options.</description>
|
||||||
<subdirectory>./macros/Multi_Export/</subdirectory>
|
<subdirectory>./macros/Multi_Export/</subdirectory>
|
||||||
<file>MultiExport.FCMacro</file>
|
<file>MultiExport.FCMacro</file>
|
||||||
@@ -54,6 +58,8 @@
|
|||||||
|
|
||||||
<macro>
|
<macro>
|
||||||
<name>Cut Object for Magnets</name>
|
<name>Cut Object for Magnets</name>
|
||||||
|
<version>0.5.0-beta</version>
|
||||||
|
<date>2026-01-07</date>
|
||||||
<description>Cut an object along a plane and add aligned magnet holes with surface collision detection. Perfect for creating 3D printed parts that snap together with embedded magnets.</description>
|
<description>Cut an object along a plane and add aligned magnet holes with surface collision detection. Perfect for creating 3D printed parts that snap together with embedded magnets.</description>
|
||||||
<subdirectory>./macros/Cut_Object_for_Magnets/</subdirectory>
|
<subdirectory>./macros/Cut_Object_for_Magnets/</subdirectory>
|
||||||
<file>CutObjectForMagnets.FCMacro</file>
|
<file>CutObjectForMagnets.FCMacro</file>
|
||||||
|
|||||||
+21
-2
@@ -66,6 +66,7 @@ dev = [
|
|||||||
"pytest-asyncio>=0.25.0",
|
"pytest-asyncio>=0.25.0",
|
||||||
"pytest-cov>=6.0.0",
|
"pytest-cov>=6.0.0",
|
||||||
"pytest-timeout>=2.3.0",
|
"pytest-timeout>=2.3.0",
|
||||||
|
"pytest-watch>=4.2.0",
|
||||||
# Linting and formatting
|
# Linting and formatting
|
||||||
"mypy>=1.14.0",
|
"mypy>=1.14.0",
|
||||||
"ruff>=0.8.0",
|
"ruff>=0.8.0",
|
||||||
@@ -73,12 +74,18 @@ dev = [
|
|||||||
"bandit[toml]>=1.8.0",
|
"bandit[toml]>=1.8.0",
|
||||||
"safety>=3.2.0",
|
"safety>=3.2.0",
|
||||||
"detect-secrets>=1.5.0",
|
"detect-secrets>=1.5.0",
|
||||||
# Pre-commit
|
# Pre-commit and commit conventions
|
||||||
"pre-commit>=4.0.0",
|
"pre-commit>=4.0.0",
|
||||||
|
"commitizen>=4.1.0",
|
||||||
# Documentation
|
# Documentation
|
||||||
"mkdocs>=1.6.0",
|
"mkdocs>=1.6.0",
|
||||||
"mkdocs-material>=9.5.0",
|
"mkdocs-material>=9.7.1",
|
||||||
"mkdocstrings[python]>=0.27.0",
|
"mkdocstrings[python]>=0.27.0",
|
||||||
|
"mkdocs-git-revision-date-localized-plugin>=1.2.0",
|
||||||
|
"mkdocs-minify-plugin>=0.8.0",
|
||||||
|
"mkdocs-redirects>=1.2.0",
|
||||||
|
"mkdocs-glightbox>=0.4.0",
|
||||||
|
"mkdocs-macros-plugin>=1.0.0",
|
||||||
"md-toc>=9.0.0",
|
"md-toc>=9.0.0",
|
||||||
"codespell>=2.3.0",
|
"codespell>=2.3.0",
|
||||||
# Publishing
|
# Publishing
|
||||||
@@ -308,3 +315,15 @@ skips = [
|
|||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
skip = ".git,*.lock,*.toml"
|
skip = ".git,*.lock,*.toml"
|
||||||
ignore-words-list = "crate"
|
ignore-words-list = "crate"
|
||||||
|
|
||||||
|
[tool.commitizen]
|
||||||
|
name = "cz_conventional_commits"
|
||||||
|
version_scheme = "semver2"
|
||||||
|
major_version_zero = true
|
||||||
|
# Note: This project uses component-specific tags for releases:
|
||||||
|
# - robust-mcp-server-vX.Y.Z (MCP server: PyPI, Docker, GitHub release)
|
||||||
|
# - robust-mcp-workbench-vX.Y.Z (FreeCAD workbench)
|
||||||
|
# - macro-cut-object-for-magnets-vX.Y.Z
|
||||||
|
# - macro-multi-export-vX.Y.Z
|
||||||
|
# Use `just release::tag-<component> <version>` to create release tags.
|
||||||
|
# Commitizen is used for commit message validation and changelog generation.
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ The FreeCAD MCP bridge server is not running. To fix this:
|
|||||||
- Click "Start MCP Bridge" in the toolbar
|
- Click "Start MCP Bridge" in the toolbar
|
||||||
|
|
||||||
Option B: From source (for developers)
|
Option B: From source (for developers)
|
||||||
- Run: just run-gui
|
- Run: just freecad::run-gui
|
||||||
|
|
||||||
3. You should see: "MCP Bridge started!"
|
3. You should see: "MCP Bridge started!"
|
||||||
- XML-RPC: localhost:{self._port}
|
- XML-RPC: localhost:{self._port}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ def pytest_collection_modifyitems(
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check bridge connection once
|
# Check bridge connection once
|
||||||
is_available, error, instance_id = _check_bridge_connection()
|
is_available, error, _instance_id = _check_bridge_connection()
|
||||||
|
|
||||||
if not is_available:
|
if not is_available:
|
||||||
# Apply skip marker to all integration tests
|
# Apply skip marker to all integration tests
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Test Organization:
|
|||||||
- TestEdgeCases: Edge case tests (single hole, many holes)
|
- TestEdgeCases: Edge case tests (single hole, many holes)
|
||||||
|
|
||||||
Note: These tests require a running FreeCAD MCP bridge.
|
Note: These tests require a running FreeCAD MCP bridge.
|
||||||
Start it with: just run-gui or just run-headless
|
Start it with: just freecad::run-gui or just freecad::run-headless
|
||||||
|
|
||||||
Note: PartDesign::Hole has a CADKernelError bug in some FreeCAD headless
|
Note: PartDesign::Hole has a CADKernelError bug in some FreeCAD headless
|
||||||
environments (especially AppImage on Linux CI) where it fails with
|
environments (especially AppImage on Linux CI) where it fails with
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ in GUI mode (with full graphical interface). They test GUI-specific features
|
|||||||
like visibility, display modes, colors, and camera operations.
|
like visibility, display modes, colors, and camera operations.
|
||||||
|
|
||||||
Note: These tests require a running FreeCAD GUI server.
|
Note: These tests require a running FreeCAD GUI server.
|
||||||
Start it with: just run-gui
|
Start it with: just freecad::run-gui
|
||||||
|
|
||||||
To run these tests:
|
To run these tests:
|
||||||
pytest tests/integration/test_gui_mode.py -v
|
pytest tests/integration/test_gui_mode.py -v
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ in headless mode (without GUI). They test object creation, manipulation, and
|
|||||||
export functionality.
|
export functionality.
|
||||||
|
|
||||||
Note: These tests require a running FreeCAD headless server.
|
Note: These tests require a running FreeCAD headless server.
|
||||||
Start it with: just run-headless
|
Start it with: just freecad::run-headless
|
||||||
|
|
||||||
To run these tests:
|
To run these tests:
|
||||||
pytest tests/integration/test_headless_mode.py -v
|
pytest tests/integration/test_headless_mode.py -v
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ These tests verify the MultiExporter class functionality including:
|
|||||||
- Handling mesh tolerance settings
|
- Handling mesh tolerance settings
|
||||||
|
|
||||||
Note: These tests require a running FreeCAD MCP bridge.
|
Note: These tests require a running FreeCAD MCP bridge.
|
||||||
Start it with: just run-gui or just run-headless
|
Start it with: just freecad::run-gui or just freecad::run-headless
|
||||||
|
|
||||||
To run these tests:
|
To run these tests:
|
||||||
pytest tests/integration/test_multi_export.py -v
|
pytest tests/integration/test_multi_export.py -v
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argcomplete"
|
||||||
|
version = "3.6.3"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "25.4.0"
|
version = "25.4.0"
|
||||||
@@ -297,6 +306,29 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "commitizen"
|
||||||
|
version = "4.11.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "argcomplete" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "colorama" },
|
||||||
|
{ name = "decli" },
|
||||||
|
{ name = "deprecated" },
|
||||||
|
{ name = "jinja2" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "prompt-toolkit" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "questionary" },
|
||||||
|
{ name = "termcolor" },
|
||||||
|
{ name = "tomlkit" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c4/aa/e0188faa6eecd4293a1d4213ea2b2ddcbed7f8f248ef41594662a78bba92/commitizen-4.11.0.tar.gz", hash = "sha256:d311297a0165ef9f30e0877e04608b786d5fd69760f32245fbf1c21e793e91df", size = 57939, upload-time = "2025-12-29T11:26:36.67Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/80/4a/a466d904f0b218dd357669b8585540c0b086ff335fd8182bf35456f4f41b/commitizen-4.11.0-py3-none-any.whl", hash = "sha256:a30fdf326bb0913b7b25f8b30530c899159b1757efa9969b0aa1808dad102c02", size = 82851, upload-time = "2025-12-29T11:26:34.718Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.13.1"
|
version = "7.13.1"
|
||||||
@@ -451,6 +483,33 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csscompressor"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f1/2a/8c3ac3d8bc94e6de8d7ae270bb5bc437b210bb9d6d9e46630c98f4abd20c/csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05", size = 237808, upload-time = "2017-11-26T21:13:08.238Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "decli"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0c/59/d4ffff1dee2c8f6f2dd8f87010962e60f7b7847504d765c91ede5a466730/decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656", size = 7564, upload-time = "2025-06-01T15:23:41.25Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3", size = 7989, upload-time = "2025-06-01T15:23:40.228Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deprecated"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "wrapt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "detect-secrets"
|
name = "detect-secrets"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -473,6 +532,12 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "docopt"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "docutils"
|
name = "docutils"
|
||||||
version = "0.22.4"
|
version = "0.22.4"
|
||||||
@@ -526,10 +591,16 @@ dev = [
|
|||||||
{ name = "bandit" },
|
{ name = "bandit" },
|
||||||
{ name = "build" },
|
{ name = "build" },
|
||||||
{ name = "codespell" },
|
{ name = "codespell" },
|
||||||
|
{ name = "commitizen" },
|
||||||
{ name = "detect-secrets" },
|
{ name = "detect-secrets" },
|
||||||
{ name = "md-toc" },
|
{ name = "md-toc" },
|
||||||
{ name = "mkdocs" },
|
{ name = "mkdocs" },
|
||||||
|
{ name = "mkdocs-git-revision-date-localized-plugin" },
|
||||||
|
{ name = "mkdocs-glightbox" },
|
||||||
|
{ name = "mkdocs-macros-plugin" },
|
||||||
{ name = "mkdocs-material" },
|
{ name = "mkdocs-material" },
|
||||||
|
{ name = "mkdocs-minify-plugin" },
|
||||||
|
{ name = "mkdocs-redirects" },
|
||||||
{ name = "mkdocstrings", extra = ["python"] },
|
{ name = "mkdocstrings", extra = ["python"] },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "pre-commit" },
|
{ name = "pre-commit" },
|
||||||
@@ -537,6 +608,7 @@ dev = [
|
|||||||
{ name = "pytest-asyncio" },
|
{ name = "pytest-asyncio" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
{ name = "pytest-timeout" },
|
{ name = "pytest-timeout" },
|
||||||
|
{ name = "pytest-watch" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "safety" },
|
{ name = "safety" },
|
||||||
{ name = "twine" },
|
{ name = "twine" },
|
||||||
@@ -547,11 +619,17 @@ requires-dist = [
|
|||||||
{ name = "bandit", extras = ["toml"], marker = "extra == 'dev'", specifier = ">=1.8.0" },
|
{ name = "bandit", extras = ["toml"], marker = "extra == 'dev'", specifier = ">=1.8.0" },
|
||||||
{ name = "build", marker = "extra == 'dev'", specifier = ">=1.2.0" },
|
{ name = "build", marker = "extra == 'dev'", specifier = ">=1.2.0" },
|
||||||
{ name = "codespell", marker = "extra == 'dev'", specifier = ">=2.3.0" },
|
{ name = "codespell", marker = "extra == 'dev'", specifier = ">=2.3.0" },
|
||||||
|
{ name = "commitizen", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
||||||
{ name = "detect-secrets", marker = "extra == 'dev'", specifier = ">=1.5.0" },
|
{ name = "detect-secrets", marker = "extra == 'dev'", specifier = ">=1.5.0" },
|
||||||
{ name = "mcp", specifier = ">=1.25.0" },
|
{ name = "mcp", specifier = ">=1.25.0" },
|
||||||
{ name = "md-toc", marker = "extra == 'dev'", specifier = ">=9.0.0" },
|
{ name = "md-toc", marker = "extra == 'dev'", specifier = ">=9.0.0" },
|
||||||
{ name = "mkdocs", marker = "extra == 'dev'", specifier = ">=1.6.0" },
|
{ name = "mkdocs", marker = "extra == 'dev'", specifier = ">=1.6.0" },
|
||||||
{ name = "mkdocs-material", marker = "extra == 'dev'", specifier = ">=9.5.0" },
|
{ name = "mkdocs-git-revision-date-localized-plugin", marker = "extra == 'dev'", specifier = ">=1.2.0" },
|
||||||
|
{ name = "mkdocs-glightbox", marker = "extra == 'dev'", specifier = ">=0.4.0" },
|
||||||
|
{ name = "mkdocs-macros-plugin", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
||||||
|
{ name = "mkdocs-material", marker = "extra == 'dev'", specifier = ">=9.7.1" },
|
||||||
|
{ name = "mkdocs-minify-plugin", marker = "extra == 'dev'", specifier = ">=0.8.0" },
|
||||||
|
{ name = "mkdocs-redirects", marker = "extra == 'dev'", specifier = ">=1.2.0" },
|
||||||
{ name = "mkdocstrings", extras = ["python"], marker = "extra == 'dev'", specifier = ">=0.27.0" },
|
{ name = "mkdocstrings", extras = ["python"], marker = "extra == 'dev'", specifier = ">=0.27.0" },
|
||||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.14.0" },
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.14.0" },
|
||||||
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0.0" },
|
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0.0" },
|
||||||
@@ -561,6 +639,7 @@ requires-dist = [
|
|||||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.25.0" },
|
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.25.0" },
|
||||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
||||||
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.3.0" },
|
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.3.0" },
|
||||||
|
{ name = "pytest-watch", marker = "extra == 'dev'", specifier = ">=4.2.0" },
|
||||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" },
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" },
|
||||||
{ name = "safety", marker = "extra == 'dev'", specifier = ">=3.2.0" },
|
{ name = "safety", marker = "extra == 'dev'", specifier = ">=3.2.0" },
|
||||||
{ name = "twine", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
{ name = "twine", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
||||||
@@ -579,6 +658,30 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitdb"
|
||||||
|
version = "4.0.12"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "smmap" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitpython"
|
||||||
|
version = "3.1.46"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "gitdb" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffe"
|
name = "griffe"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@@ -600,6 +703,23 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hjson"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "htmlmin2"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/be/31/a76f4bfa885f93b8167cb4c85cf32b54d1f64384d0b897d45bc6d19b7b45/htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2", size = 34486, upload-time = "2023-03-14T21:28:30.388Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpcore"
|
name = "httpcore"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@@ -754,6 +874,12 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsmin"
|
||||||
|
version = "3.0.1"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925, upload-time = "2022-01-16T20:35:59.13Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "4.25.1"
|
version = "4.25.1"
|
||||||
@@ -1073,6 +1199,54 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-git-revision-date-localized-plugin"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "babel" },
|
||||||
|
{ name = "gitpython" },
|
||||||
|
{ name = "mkdocs" },
|
||||||
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0f/c5/1d3c4e6ddae6230b89d09105cb79de711655e3ebd6745f7a92efea0f5160/mkdocs_git_revision_date_localized_plugin-1.5.0.tar.gz", hash = "sha256:17345ccfdf69a1905dc96fb1070dce82d03a1eb6b0d48f958081a7589ce3c248", size = 460697, upload-time = "2025-10-31T16:11:34.44Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/bc/51/fe0e3fdb16f6eed65c9459d12bae6a4e1f0bb4e2228cb037e7907b002678/mkdocs_git_revision_date_localized_plugin-1.5.0-py3-none-any.whl", hash = "sha256:933f9e35a8c135b113f21bb57610d82e9b7bcc71dd34fb06a029053c97e99656", size = 26153, upload-time = "2025-10-31T16:11:32.987Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-glightbox"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "selectolax" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8d/26/c793459622da8e31f954c6f5fb51e8f098143fdfc147b1e3c25bf686f4aa/mkdocs_glightbox-0.5.2.tar.gz", hash = "sha256:c7622799347c32310878e01ccf14f70648445561010911c80590cec0353370ac", size = 510586, upload-time = "2025-10-23T14:55:18.909Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/4e/ca/03624e017e5ee2d7ce8a08d89f81c1e535eb3c30d7b2dc4a435ea3fbbeae/mkdocs_glightbox-0.5.2-py3-none-any.whl", hash = "sha256:23a431ea802b60b1030c73323db2eed6ba859df1a0822ce575afa43e0ea3f47e", size = 26458, upload-time = "2025-10-23T14:55:17.43Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-macros-plugin"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "hjson" },
|
||||||
|
{ name = "jinja2" },
|
||||||
|
{ name = "mkdocs" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pathspec" },
|
||||||
|
{ name = "python-dateutil" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "super-collections" },
|
||||||
|
{ name = "termcolor" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "9.7.1"
|
version = "9.7.1"
|
||||||
@@ -1104,6 +1278,33 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-minify-plugin"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "csscompressor" },
|
||||||
|
{ name = "htmlmin2" },
|
||||||
|
{ name = "jsmin" },
|
||||||
|
{ name = "mkdocs" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/52/67/fe4b77e7a8ae7628392e28b14122588beaf6078b53eb91c7ed000fd158ac/mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d", size = 8366, upload-time = "2024-01-29T16:11:32.982Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1b/cd/2e8d0d92421916e2ea4ff97f10a544a9bd5588eb747556701c983581df13/mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6", size = 6723, upload-time = "2024-01-29T16:11:31.851Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-redirects"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mkdocs" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings"
|
name = "mkdocstrings"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1315,6 +1516,18 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
version = "3.0.51"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "wcwidth" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.23"
|
version = "2.23"
|
||||||
@@ -1550,6 +1763,18 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-watch"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama" },
|
||||||
|
{ name = "docopt" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "watchdog" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/36/47/ab65fc1d682befc318c439940f81a0de1026048479f732e84fe714cd69c0/pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9", size = 16340, upload-time = "2018-05-20T19:52:16.194Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.9.0.post0"
|
version = "2.9.0.post0"
|
||||||
@@ -1675,6 +1900,18 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "questionary"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "prompt-toolkit" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "readme-renderer"
|
name = "readme-renderer"
|
||||||
version = "44.0"
|
version = "44.0"
|
||||||
@@ -2053,6 +2290,59 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "selectolax"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/fb/c5/959b8661d200d9fd3cf52061ce6f1d7087ec94823bb324fe1ca76c80b250/selectolax-0.4.6.tar.gz", hash = "sha256:bd9326cfc9bbd5bfcda980b0452b9761b3cf134015154e95d83fa32cbfbb751b", size = 4793248, upload-time = "2025-12-06T12:35:48.513Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a0/76/9b5358d82353b68c5828cc8ceae4ff1073495462979d2035c1089f4421dd/selectolax-0.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:751f727c9963584fcfa101b2696e0dd31236142901bbe7866a8e39f2ec346cc4", size = 2052057, upload-time = "2025-12-06T12:34:24.448Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e3/d1/9f8c8841f414a1b72174acef916d4ed38cc0fa041d3e933013e2b3213f30/selectolax-0.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6f45737b638836b9fe2a4e7ccfbcd48a315ebb96da76a79a04b8227c1a967ee", size = 2050379, upload-time = "2025-12-06T12:34:26.383Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/bc/c4/8e5ab9275a185a31d06b5ea58e3ba61d57e57700964cbd56fe074dbe458c/selectolax-0.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15a22c5cd9c23f09227b2b9c227d986396125a9b0eb21be655f22fe4ccc5679a", size = 2238005, upload-time = "2025-12-06T12:34:28.2Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ea/af/f4299d931a8e11ba1998cac70d407c5338436978325232eb252ac7f5aba2/selectolax-0.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e64f771807f1a7353f4d6878c303bfbd6c6fe58897e301aa6d0de7e5e60cef61", size = 2272927, upload-time = "2025-12-06T12:34:29.955Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7b/5e/9afbb0e8495846bf97fa7725a6f97622ad85efc654cb3cbf4c881bd345de/selectolax-0.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0144a37fbf01695ccf2d0d24caaa058a28449cecb2c754bb9e616f339468d74", size = 2250901, upload-time = "2025-12-06T12:34:31.442Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d5/6b/914cc9c977fd21949af5776d25b9c011b6e72fb38569161ab6ca83d6130a/selectolax-0.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c9bdd4a9b3f71f28a0ee558a105451debd33cbe1ed350ebba7ad6b68d62815c", size = 2279842, upload-time = "2025-12-06T12:34:32.739Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e1/30/b32d79d31c893cf5ccea5a5be4565db1eda9bbf458ddfe402559267f2d31/selectolax-0.4.6-cp311-cp311-win32.whl", hash = "sha256:b91c34b854fdd5d21c8353f130899f8413b7104a96078acbca59c8b2d57e9352", size = 1730462, upload-time = "2025-12-06T12:34:34.435Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8f/f1/c7ba048d4fcc4c8d5857233c59d07f18e60b21400a8ad8e8d7da670024c2/selectolax-0.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:901064121e706bc86ed13f6e0dbe478398ad05ab112f5efbc8d722320a087b93", size = 1831442, upload-time = "2025-12-06T12:34:35.846Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d5/55/b33853f66b1f875bbbbfc2294ce7a4065774621ab6ebf20e8abf19965846/selectolax-0.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:609c6c19f5b7cb669a6321a1d4133d2e2b443f23f7d454de76904118a91236a6", size = 1781850, upload-time = "2025-12-06T12:34:37.175Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ee/81/1fdf6633df840afd9d7054a3441f04cfb1fc9d098c6c9f3bd46a64fb632e/selectolax-0.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20615062d6062b197b61fd646e667591e987be3a894e8a8408d2a482ccddc747", size = 2051021, upload-time = "2025-12-06T12:34:38.495Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/cc/54/d59738d090cb4df3a3a6297b7ec216b86d3ba7f320013c4bc8d4841c9f5d/selectolax-0.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c436006e2af6ade80b96411cf46652c11ced4f230032e25e1f5210b7522a4fe3", size = 2047409, upload-time = "2025-12-06T12:34:39.875Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/fc/67/3b163ec18a128df3a3b59ce676a2dacfb26e714da81ba8a98e184b4ef187/selectolax-0.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:705a70b6f4e553e8c5299881269d3735a7df8a23711927a33caa16b4eaef580f", size = 2237052, upload-time = "2025-12-06T12:34:41.24Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f0/04/c3ae4a77e8cfa647b9177e727a7e80f64b160b65ad0db0dcb3738a4ef4a0/selectolax-0.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c04fd180689ed9450ad2453a3cba74cff2475a4281f76db9e18a658b7823e594", size = 2275615, upload-time = "2025-12-06T12:34:43.114Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/12/de/aaa016c44e63a1efd5525f6da6eac807388a06c70671091c735d93f13b74/selectolax-0.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb33eb0809e70ba4a475105d164c3f90a4bb711744ca69e20037298256b8e9d7", size = 2249186, upload-time = "2025-12-06T12:34:44.84Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/76/9a/a9cf9f0158b0804c7ea404d790af031830eb6452a4948853f7582eea6c51/selectolax-0.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:97f30b7c731f9f3328e9c6aef7ca3c17fbcbc4495e286a2cdad9a77bcadfadf1", size = 2282041, upload-time = "2025-12-06T12:34:46.19Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/4c/ea/85de7ab8a9fc0301d1b428e69dc0bced9c1cd7379872d576a2b88eb91933/selectolax-0.4.6-cp312-cp312-win32.whl", hash = "sha256:f4375b352b609508e4a6980431dc6cc1812b97658ad1aa8caa61e01565de0d7d", size = 1727544, upload-time = "2025-12-06T12:34:47.541Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/50/70/4aac2df64920112672cda846941d85c90b8152b2eddc9cf2615181551957/selectolax-0.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:1d02637a6746bf1ba7de1dfc00a0344ffb30bedd1b5d4e61727c960225bf6ce0", size = 1827825, upload-time = "2025-12-06T12:34:49.283Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8d/b0/09648383afed1a10df97ce30527d30714edc4072086915b4bb1a0d81a728/selectolax-0.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:bb0b371c3e2a94e6658ba4b5af88fc35aaf44f57f5a066ecaf96b4875a47aec4", size = 1775233, upload-time = "2025-12-06T12:34:51.576Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/4d/87/7ed053cce7de8b29746c602851c67654287e25ec404d575911c6f40b671f/selectolax-0.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8303af0eeef6ab2f06f2b18e3c825df4f6984405a862c8e9e654621c26558aca", size = 2050412, upload-time = "2025-12-06T12:34:53.086Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/44/74/e8cd9b9b53da0e849b27a2ef68580915321ee52a662f015275a1cf6cca85/selectolax-0.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d8b1fb5507d90e719de705d93eaa3bdd5f2d69facf012fb67b7da8a9cd20ea6b", size = 2046513, upload-time = "2025-12-06T12:34:54.583Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a0/ca/c1cc7f03b681d272dbb0dc47cf9d0df857ae156d7ea54d88cc25ec23c8e9/selectolax-0.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30ac51bd5bfcd6bffe58720b1fc5f97666681f0793d79d292069b3a3f8599ef0", size = 2234404, upload-time = "2025-12-06T12:34:55.971Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/df/00/a6e5c4d65243147fbd8837951267f098d9bee66ada4dc0c99d97259e052c/selectolax-0.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfee4d1269fd462256641abdf6c2ee4dd799ba82c578acab0bcde07547637826", size = 2272678, upload-time = "2025-12-06T12:34:57.3Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ab/a2/05351e0f0da62d1bc01b7820a990f1e4dec688206e0278ee8faeeb878940/selectolax-0.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fce09eeb5dd19fba672a829f63e3c40238af4a929ce1e5fd16dcbc4fd253e300", size = 2247471, upload-time = "2025-12-06T12:34:59.048Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ea/95/1ab9e3ad2d53dbcd7141af149c7259c693dc2dc46e27d72b7a68f17f3364/selectolax-0.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4e97ca71d545cab9c57ba3b18358cbc96ef0dcd01920ea903a531386ca1f849", size = 2278271, upload-time = "2025-12-06T12:35:00.487Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/41/ca80ca68fb9990cb46d753363d8dc0771225bc9bb9a77247a1987e52d2d5/selectolax-0.4.6-cp313-cp313-win32.whl", hash = "sha256:98fce08839cb8fd5d8788cbed2724cd892c78182cd923e246ef586d552a29a94", size = 1727514, upload-time = "2025-12-06T12:35:02.081Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f0/47/f048994ab32773fda8eda57a874afb30d0b2bf30952c009006e81737e07e/selectolax-0.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:cdc8e7833d2456331e519fe901d1c8844d120a26b21b6429f47ae946e65b0c04", size = 1829070, upload-time = "2025-12-06T12:35:03.857Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/50/94/ab0d86b1a719c090e2bde9f7d9037439900e86fb50c258b5fd9f6530521b/selectolax-0.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:d6f8c976ad067a6607b2a052b141149ae23584819b73288c32f08a1ece23d60a", size = 1774935, upload-time = "2025-12-06T12:35:05.323Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d2/38/93b4907c596487e13f8261129b1663559c96fc37ea2c973c98a393307484/selectolax-0.4.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:961c037ac41a12bf9db35a95d6076461f9b127d361c9ef26a77f76c45b1056eb", size = 2069131, upload-time = "2025-12-06T12:35:06.737Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f6/04/d29769a8313ccb9db6278401840e79662f341b716d268f7468b6270d15e1/selectolax-0.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f2be076a69a12657a38933b99d31c604d95d63911e0799f89305da8e89918874", size = 2066134, upload-time = "2025-12-06T12:35:08.163Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/29/a2/1c26b4cc6a708d234e39199bd75acdc1cfdcf4f3138e16d25e3e7aa0295d/selectolax-0.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25ae5dd7c20035312211de43952934981e8ff4e1502467ce78665f57bc5eaf7f", size = 2240810, upload-time = "2025-12-06T12:35:09.556Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1f/de/b021342d306bd08c92d0d9e9072a3ed6a3038f78387187d37fb775196fa0/selectolax-0.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe1274930affe2b986fd20cbf2916ddf4a760edf30a7eeb9151b31e8cbe6027", size = 2274401, upload-time = "2025-12-06T12:35:11.023Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/29/77/b2816be2f4878f4e86fabca5932610401dc88d956948d97a82947e27d8bc/selectolax-0.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e52174a75236c1321f0f0353b5d97ba575c61428f16c2d78159cb154fa407e97", size = 2271054, upload-time = "2025-12-06T12:35:12.878Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1f/3a/ae48b9febf2af21a0fdd4fba07861ae3e13bd7235df3fa39918d33004058/selectolax-0.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5032187ed8f8d77da6dee5467a87fe9b1553c10c63671fe830e87a2d347ef8ae", size = 2297773, upload-time = "2025-12-06T12:35:14.383Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f9/8c/4e120b2b6dc55543bf2ffc43e78589022e59c02fa93dea0b4d22dfe3fc27/selectolax-0.4.6-cp314-cp314-win32.whl", hash = "sha256:8a4f50eb37abffe118730c062827a204a732cc1b9cd28b5dbf40752c371e9339", size = 1838289, upload-time = "2025-12-06T12:35:16.278Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c4/2f/51f15b06f4fcf9d5845d6bba534f7f2ed531f148c77ed8fff105cd770aa5/selectolax-0.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:11527cd430cb6d1d1a422209d87aec5767befff424f2affaa3daa2789878cf9f", size = 1938201, upload-time = "2025-12-06T12:35:17.694Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f3/8d/c099dcbb385e7d2145c4f19da183639bf4e429e1524dcba31ea311c5d276/selectolax-0.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:254d7f59580631dac1fcb861bb01f45039e3ac53187f07d8ccc3519110bacad0", size = 1886188, upload-time = "2025-12-06T12:35:19.538Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/54/ce/f3fa904517745ecd289b6ce18845ae968cf7d0b17e65ab781259f2746254/selectolax-0.4.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:48a9bce389c281fec8bf3b5a36bd376f1ad1f36ff072dcedaa977188b3882be1", size = 2088107, upload-time = "2025-12-06T12:35:21.061Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/4f/18/359da9723d3ec1235819cea0958bc417ce6a12699977920854b300ad4529/selectolax-0.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff1632c1767f6005adc2dff7b72ea9d0b6493702e86e04ee5cf934ab630172aa", size = 2090836, upload-time = "2025-12-06T12:35:22.421Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e2/20/199f2a05ca8dfd9bc146af03fbfaa1e2050601d3141267070a2a023ea68f/selectolax-0.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64a56dd91c22e809305e127bbd4cd75ad1016dd329a4e945a176c6f3376d00e2", size = 2248608, upload-time = "2025-12-06T12:35:23.946Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f9/67/261c06cdae29fad287008d39f51a80431f3fce66c2865b880e61d04cdfd2/selectolax-0.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:223471d2c9095406d69faf461aa217782575d62362d36809e350f6d3c2f69f4e", size = 2275653, upload-time = "2025-12-06T12:35:25.423Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/10/80/f2519487a86b5366acadd9e07c212b38fb657bb62b9e01de9fb24c3ada27/selectolax-0.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c1043e61df29f4f8ef49f9f623b3d710f0c545d9a7939eee52c49987b06aef7", size = 2283495, upload-time = "2025-12-06T12:35:26.843Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/66/cc/35a0fd896371e0b5a84365b03f88c7dbe8984862e34ce32baed81ee6f486/selectolax-0.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b323ad4ebcf2edad2488022402fbc73ee158ffe81607ec1ce5eb1039eab94086", size = 2300207, upload-time = "2025-12-06T12:35:28.23Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/51/b2/dc83cce2f38a3c72d6bf9268ef6c1708ab1b4d546074384e7e0d097bf4f6/selectolax-0.4.6-cp314-cp314t-win32.whl", hash = "sha256:f5017f6e2408160604c87fb21490d9af802d09dbc1b91ac89acd9922b7b04d31", size = 1890595, upload-time = "2025-12-06T12:35:29.625Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/87/f9/8da49637643b1dffbcadc8972f1fee519c126978acaf5df59405e48424f4/selectolax-0.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:56adb0f014ab55627f20f53888a7bf1ec53aac8189fe344aec3d5077a7ad9889", size = 2005252, upload-time = "2025-12-06T12:35:31.116Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/94/7f/f783e2254db082df4f6bc00fe3b32b9dd27c3b7302a44c8c37728bb67fb7/selectolax-0.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:66558cfb1c7402fed0f47b9a2692eed53e3e2f345526314b493b5093cb951e21", size = 1906079, upload-time = "2025-12-06T12:35:32.951Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shellingham"
|
name = "shellingham"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
@@ -2071,6 +2361,15 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smmap"
|
||||||
|
version = "5.0.2"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sse-starlette"
|
name = "sse-starlette"
|
||||||
version = "3.1.2"
|
version = "3.1.2"
|
||||||
@@ -2106,6 +2405,18 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "super-collections"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "hjson" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tenacity"
|
name = "tenacity"
|
||||||
version = "9.1.2"
|
version = "9.1.2"
|
||||||
@@ -2115,6 +2426,15 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -2241,6 +2561,15 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.3"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.6.2"
|
version = "2.6.2"
|
||||||
@@ -2304,6 +2633,96 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wrapt"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/98/60/553997acf3939079dab022e37b67b1904b5b0cc235503226898ba573b10c/wrapt-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e17283f533a0d24d6e5429a7d11f250a58d28b4ae5186f8f47853e3e70d2590", size = 77480, upload-time = "2025-11-07T00:43:30.573Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/2d/50/e5b3d30895d77c52105c6d5cbf94d5b38e2a3dd4a53d22d246670da98f7c/wrapt-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85df8d92158cb8f3965aecc27cf821461bb5f40b450b03facc5d9f0d4d6ddec6", size = 60690, upload-time = "2025-11-07T00:43:31.594Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f0/40/660b2898703e5cbbb43db10cdefcc294274458c3ca4c68637c2b99371507/wrapt-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1be685ac7700c966b8610ccc63c3187a72e33cab53526a27b2a285a662cd4f7", size = 61578, upload-time = "2025-11-07T00:43:32.918Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5b/36/825b44c8a10556957bc0c1d84c7b29a40e05fcf1873b6c40aa9dbe0bd972/wrapt-2.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0b6d3b95932809c5b3fecc18fda0f1e07452d05e2662a0b35548985f256e28", size = 114115, upload-time = "2025-11-07T00:43:35.605Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/83/73/0a5d14bb1599677304d3c613a55457d34c344e9b60eda8a737c2ead7619e/wrapt-2.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da7384b0e5d4cae05c97cd6f94faaf78cc8b0f791fc63af43436d98c4ab37bb", size = 116157, upload-time = "2025-11-07T00:43:37.058Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/01/22/1c158fe763dbf0a119f985d945711d288994fe5514c0646ebe0eb18b016d/wrapt-2.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ec65a78fbd9d6f083a15d7613b2800d5663dbb6bb96003899c834beaa68b242c", size = 112535, upload-time = "2025-11-07T00:43:34.138Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5c/28/4f16861af67d6de4eae9927799b559c20ebdd4fe432e89ea7fe6fcd9d709/wrapt-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7de3cc939be0e1174969f943f3b44e0d79b6f9a82198133a5b7fc6cc92882f16", size = 115404, upload-time = "2025-11-07T00:43:39.214Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a0/8b/7960122e625fad908f189b59c4aae2d50916eb4098b0fb2819c5a177414f/wrapt-2.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fb1a5b72cbd751813adc02ef01ada0b0d05d3dcbc32976ce189a1279d80ad4a2", size = 111802, upload-time = "2025-11-07T00:43:40.476Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/3e/73/7881eee5ac31132a713ab19a22c9e5f1f7365c8b1df50abba5d45b781312/wrapt-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3fa272ca34332581e00bf7773e993d4f632594eb2d1b0b162a9038df0fd971dd", size = 113837, upload-time = "2025-11-07T00:43:42.921Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/45/00/9499a3d14e636d1f7089339f96c4409bbc7544d0889f12264efa25502ae8/wrapt-2.0.1-cp311-cp311-win32.whl", hash = "sha256:fc007fdf480c77301ab1afdbb6ab22a5deee8885f3b1ed7afcb7e5e84a0e27be", size = 58028, upload-time = "2025-11-07T00:43:47.369Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/70/5d/8f3d7eea52f22638748f74b102e38fdf88cb57d08ddeb7827c476a20b01b/wrapt-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:47434236c396d04875180171ee1f3815ca1eada05e24a1ee99546320d54d1d1b", size = 60385, upload-time = "2025-11-07T00:43:44.34Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/14/e2/32195e57a8209003587bbbad44d5922f13e0ced2a493bb46ca882c5b123d/wrapt-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:837e31620e06b16030b1d126ed78e9383815cbac914693f54926d816d35d8edf", size = 58893, upload-time = "2025-11-07T00:43:46.161Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ad/fe/41af4c46b5e498c90fc87981ab2972fbd9f0bccda597adb99d3d3441b94b/wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9", size = 78132, upload-time = "2025-11-07T00:44:04.628Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1c/92/d68895a984a5ebbbfb175512b0c0aad872354a4a2484fbd5552e9f275316/wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f", size = 61211, upload-time = "2025-11-07T00:44:05.626Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e8/26/ba83dc5ae7cf5aa2b02364a3d9cf74374b86169906a1f3ade9a2d03cf21c/wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218", size = 61689, upload-time = "2025-11-07T00:44:06.719Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/cf/67/d7a7c276d874e5d26738c22444d466a3a64ed541f6ef35f740dbd865bab4/wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9", size = 121502, upload-time = "2025-11-07T00:44:09.557Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0f/6b/806dbf6dd9579556aab22fc92908a876636e250f063f71548a8660382184/wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c", size = 123110, upload-time = "2025-11-07T00:44:10.64Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e5/08/cdbb965fbe4c02c5233d185d070cabed2ecc1f1e47662854f95d77613f57/wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db", size = 117434, upload-time = "2025-11-07T00:44:08.138Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/2d/d1/6aae2ce39db4cb5216302fa2e9577ad74424dfbe315bd6669725569e048c/wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233", size = 121533, upload-time = "2025-11-07T00:44:12.142Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/79/35/565abf57559fbe0a9155c29879ff43ce8bd28d2ca61033a3a3dd67b70794/wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2", size = 116324, upload-time = "2025-11-07T00:44:13.28Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/e1/e0/53ff5e76587822ee33e560ad55876d858e384158272cd9947abdd4ad42ca/wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b", size = 120627, upload-time = "2025-11-07T00:44:14.431Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/7c/7b/38df30fd629fbd7612c407643c63e80e1c60bcc982e30ceeae163a9800e7/wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7", size = 58252, upload-time = "2025-11-07T00:44:17.814Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/85/64/d3954e836ea67c4d3ad5285e5c8fd9d362fd0a189a2db622df457b0f4f6a/wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3", size = 60500, upload-time = "2025-11-07T00:44:15.561Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/89/4e/3c8b99ac93527cfab7f116089db120fef16aac96e5f6cdb724ddf286086d/wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8", size = 58993, upload-time = "2025-11-07T00:44:16.65Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f9/f4/eff2b7d711cae20d220780b9300faa05558660afb93f2ff5db61fe725b9a/wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3", size = 82028, upload-time = "2025-11-07T00:44:18.944Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0c/67/cb945563f66fd0f61a999339460d950f4735c69f18f0a87ca586319b1778/wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1", size = 62949, upload-time = "2025-11-07T00:44:20.074Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ec/ca/f63e177f0bbe1e5cf5e8d9b74a286537cd709724384ff20860f8f6065904/wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d", size = 63681, upload-time = "2025-11-07T00:44:21.345Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/39/a1/1b88fcd21fd835dca48b556daef750952e917a2794fa20c025489e2e1f0f/wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7", size = 152696, upload-time = "2025-11-07T00:44:24.318Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/62/1c/d9185500c1960d9f5f77b9c0b890b7fc62282b53af7ad1b6bd779157f714/wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3", size = 158859, upload-time = "2025-11-07T00:44:25.494Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/91/60/5d796ed0f481ec003220c7878a1d6894652efe089853a208ea0838c13086/wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b", size = 146068, upload-time = "2025-11-07T00:44:22.81Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/04/f8/75282dd72f102ddbfba137e1e15ecba47b40acff32c08ae97edbf53f469e/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10", size = 155724, upload-time = "2025-11-07T00:44:26.634Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/5a/27/fe39c51d1b344caebb4a6a9372157bdb8d25b194b3561b52c8ffc40ac7d1/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf", size = 144413, upload-time = "2025-11-07T00:44:27.939Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/83/2b/9f6b643fe39d4505c7bf926d7c2595b7cb4b607c8c6b500e56c6b36ac238/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e", size = 150325, upload-time = "2025-11-07T00:44:29.29Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/bb/b6/20ffcf2558596a7f58a2e69c89597128781f0b88e124bf5a4cadc05b8139/wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c", size = 59943, upload-time = "2025-11-07T00:44:33.211Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/87/6a/0e56111cbb3320151eed5d3821ee1373be13e05b376ea0870711f18810c3/wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92", size = 63240, upload-time = "2025-11-07T00:44:30.935Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1d/54/5ab4c53ea1f7f7e5c3e7c1095db92932cc32fd62359d285486d00c2884c3/wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f", size = 60416, upload-time = "2025-11-07T00:44:32.002Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/73/81/d08d83c102709258e7730d3cd25befd114c60e43ef3891d7e6877971c514/wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1", size = 78290, upload-time = "2025-11-07T00:44:34.691Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f6/14/393afba2abb65677f313aa680ff0981e829626fed39b6a7e3ec807487790/wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55", size = 61255, upload-time = "2025-11-07T00:44:35.762Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c4/10/a4a1f2fba205a9462e36e708ba37e5ac95f4987a0f1f8fd23f0bf1fc3b0f/wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0", size = 61797, upload-time = "2025-11-07T00:44:37.22Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/12/db/99ba5c37cf1c4fad35349174f1e38bd8d992340afc1ff27f526729b98986/wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509", size = 120470, upload-time = "2025-11-07T00:44:39.425Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/30/3f/a1c8d2411eb826d695fc3395a431757331582907a0ec59afce8fe8712473/wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1", size = 122851, upload-time = "2025-11-07T00:44:40.582Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b3/8d/72c74a63f201768d6a04a8845c7976f86be6f5ff4d74996c272cefc8dafc/wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970", size = 117433, upload-time = "2025-11-07T00:44:38.313Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c7/5a/df37cf4042cb13b08256f8e27023e2f9b3d471d553376616591bb99bcb31/wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c", size = 121280, upload-time = "2025-11-07T00:44:41.69Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/54/34/40d6bc89349f9931e1186ceb3e5fbd61d307fef814f09fbbac98ada6a0c8/wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41", size = 116343, upload-time = "2025-11-07T00:44:43.013Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/70/66/81c3461adece09d20781dee17c2366fdf0cb8754738b521d221ca056d596/wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed", size = 119650, upload-time = "2025-11-07T00:44:44.523Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/46/3a/d0146db8be8761a9e388cc9cc1c312b36d583950ec91696f19bbbb44af5a/wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0", size = 58701, upload-time = "2025-11-07T00:44:48.277Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1a/38/5359da9af7d64554be63e9046164bd4d8ff289a2dd365677d25ba3342c08/wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c", size = 60947, upload-time = "2025-11-07T00:44:46.086Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/aa/3f/96db0619276a833842bf36343685fa04f987dd6e3037f314531a1e00492b/wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e", size = 59359, upload-time = "2025-11-07T00:44:47.164Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/71/49/5f5d1e867bf2064bf3933bc6cf36ade23505f3902390e175e392173d36a2/wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b", size = 82031, upload-time = "2025-11-07T00:44:49.4Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/2b/89/0009a218d88db66ceb83921e5685e820e2c61b59bbbb1324ba65342668bc/wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec", size = 62952, upload-time = "2025-11-07T00:44:50.74Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/ae/18/9b968e920dd05d6e44bcc918a046d02afea0fb31b2f1c80ee4020f377cbe/wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa", size = 63688, upload-time = "2025-11-07T00:44:52.248Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/a6/7d/78bdcb75826725885d9ea26c49a03071b10c4c92da93edda612910f150e4/wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815", size = 152706, upload-time = "2025-11-07T00:44:54.613Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/dd/77/cac1d46f47d32084a703df0d2d29d47e7eb2a7d19fa5cbca0e529ef57659/wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa", size = 158866, upload-time = "2025-11-07T00:44:55.79Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8a/11/b521406daa2421508903bf8d5e8b929216ec2af04839db31c0a2c525eee0/wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef", size = 146148, upload-time = "2025-11-07T00:44:53.388Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/0c/c0/340b272bed297baa7c9ce0c98ef7017d9c035a17a6a71dce3184b8382da2/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747", size = 155737, upload-time = "2025-11-07T00:44:56.971Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/f3/93/bfcb1fb2bdf186e9c2883a4d1ab45ab099c79cbf8f4e70ea453811fa3ea7/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f", size = 144451, upload-time = "2025-11-07T00:44:58.515Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/d2/6b/dca504fb18d971139d232652656180e3bd57120e1193d9a5899c3c0b7cdd/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349", size = 150353, upload-time = "2025-11-07T00:44:59.753Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1d/f6/a1de4bd3653afdf91d250ca5c721ee51195df2b61a4603d4b373aa804d1d/wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c", size = 60609, upload-time = "2025-11-07T00:45:03.315Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/01/3a/07cd60a9d26fe73efead61c7830af975dfdba8537632d410462672e4432b/wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395", size = 64038, upload-time = "2025-11-07T00:45:00.948Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/41/99/8a06b8e17dddbf321325ae4eb12465804120f699cd1b8a355718300c62da/wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad", size = 60634, upload-time = "2025-11-07T00:45:02.087Z" },
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
version = "3.23.0"
|
version = "3.23.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user