"""Generate the wood-* CmdForge tools: the documented woodworking command vocabulary. Each is a thin wrapper over the `woodshop` CLI so the logic lives in one place; pa-load-tools turns these into Claude function schemas.""" import os import stat from pathlib import Path import yaml CMDFORGE_PY = "/home/rob/.local/share/pipx/venvs/cmdforge/bin/python" CMDFORGE_DIR = Path.home() / ".cmdforge" BIN_DIR = Path.home() / ".local" / "bin" WS = 'ws = os.path.expanduser("~/PycharmProjects/woodshop/.venv/bin/woodshop")' PLACE = f'''import subprocess, os {WS} r = subprocess.run([ws, "place", stock, length], capture_output=True, text=True) out = (r.stdout + r.stderr).strip() ''' JOIN = f'''import subprocess, os {WS} cmd = [ws, "join", part_b] if to: cmd += ["--to", to] if angle: cmd += ["--angle", str(angle)] if offset: cmd += ["--offset", offset] if anchor: cmd += ["--anchor", anchor] r = subprocess.run(cmd, capture_output=True, text=True) out = (r.stdout + r.stderr).strip() ''' SAND = f'''import subprocess, os {WS} cmd = [ws, "sand"] + ([part] if part else []) r = subprocess.run(cmd, capture_output=True, text=True) out = (r.stdout + r.stderr).strip() ''' DELETE = f'''import subprocess, os {WS} cmd = [ws, "delete"] + ([part] if part else []) r = subprocess.run(cmd, capture_output=True, text=True) out = (r.stdout + r.stderr).strip() ''' UNDO = f'''import subprocess, os {WS} r = subprocess.run([ws, "undo"], capture_output=True, text=True) out = (r.stdout + r.stderr).strip() ''' TOOLS = { "wood-place": { "description": "Place a new board of dimensional lumber into the scene. Use for any 'place', 'add', 'put', 'grab', 'cut me a' board command.", "arguments": [ {"flag": "--stock", "variable": "stock", "description": "Nominal lumber size, e.g. 2x4, 2x6, 1x4, 4x4"}, {"flag": "--length", "variable": "length", "description": "Length with units, e.g. '6 ft', '72 in', '3 ft 6 in'"}, ], "code": PLACE, }, "wood-join": { "description": "Attach/join one board to another at an angle, optionally offset along the target board. Use for 'attach', 'join', 'connect', 'fasten', 'screw to'.", "arguments": [ {"flag": "--part-b", "variable": "part_b", "description": "Id of the board being attached, e.g. p2"}, {"flag": "--to", "variable": "to", "default": "", "description": "Id of the board to attach to, e.g. p1. Omit to use the most recently touched board."}, {"flag": "--angle", "variable": "angle", "default": "90", "description": "Angle in degrees between the two boards (default 90)"}, {"flag": "--offset", "variable": "offset", "default": "", "description": "Distance from the anchor end, e.g. '10 in'. Omit to attach at the very end."}, {"flag": "--anchor", "variable": "anchor", "default": "end_b", "description": "Measure offset from 'end_a' (start) or 'end_b' (far end)"}, ], "code": JOIN, }, "wood-sand": { "description": "Sand a board smooth. Use for 'sand', 'smooth', 'finish'.", "arguments": [ {"flag": "--part", "variable": "part", "default": "", "description": "Id of the board to sand, e.g. p1. Omit to sand the most recently touched board ('it')."}, ], "code": SAND, }, "wood-delete": { "description": "Remove a board from the scene. Use for 'delete', 'remove', 'get rid of', 'scrap'.", "arguments": [ {"flag": "--part", "variable": "part", "default": "", "description": "Id of the board to delete, e.g. p2. Omit for the most recently touched board."}, ], "code": DELETE, }, "wood-undo": { "description": "Undo the last operation. Use for 'undo', 'never mind', 'take that back', 'go back'.", "arguments": [], "code": UNDO, }, } WRAPPER = '''#!/bin/bash # CmdForge wrapper for '{name}' # Auto-generated - do not edit exec "{py}" -m cmdforge.runner "{name}" "$@" ''' for name, spec in TOOLS.items(): tool_dir = CMDFORGE_DIR / name tool_dir.mkdir(parents=True, exist_ok=True) config = { "name": name, "description": spec["description"], "category": "Other", "version": "0.1.0", "arguments": spec["arguments"], "steps": [{"type": "code", "code": spec["code"], "output_var": "out"}], "output": "{out}", } (tool_dir / "config.yaml").write_text(yaml.safe_dump(config, sort_keys=False)) wrapper = BIN_DIR / name wrapper.write_text(WRAPPER.format(name=name, py=CMDFORGE_PY)) wrapper.chmod(wrapper.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH) print(f"created {name}: {tool_dir/'config.yaml'} + {wrapper}")