Writing designs with Claude Code
The workbench loads your designs alongside the built-ins. Each .py file in
your designs folder becomes one antenna under “Your designs” (the user.*
namespace) — drop a file in, refresh the page, and it shows up. Because each
design is just a small Python class, Claude Code
can write or edit one for you from a contract that’s seeded right into the folder.
Your designs folder
Section titled “Your designs folder”On first run the app creates ~/.antennaknobs/designs/ (override with the
ANTENNAKNOBS_USER_DIR environment variable) and seeds two reference files into
it:
TEMPLATE.py— a complete, working example dipole. Copy it, rename it, edit it.CLAUDE.md— the authoring contract, written for Claude Code. It’s the context that lets Claude write a correct design on the first try.
Both are refreshed from the package on every startup (they’re documentation, not
your content), so an upgrade brings the latest authoring guidance. Your own
*.py files are never touched.
A design is also a first-class CLI antenna: once my_dipole.py is in the folder,
antennaknobs draw --builder user.my_dipole (or sweep, pattern, …) works,
and antennaknobs list shows it.
The workflow
Section titled “The workflow”- Open Claude Code in the folder:
Claude reads the seeded
Terminal window cd ~/.antennaknobs/designsclaudeCLAUDE.mdas context automatically. - Ask for an antenna in plain language — for example:
- “Make me a 40-meter off-center-fed dipole fed 1/3 from one end.”
- “Design a 2-element 20-meter quad loop — driven element plus a reflector.”
- “Take my_dipole.py and add an adjustable height-above-ground slider.”
- “My design loads but resonates at 32 MHz — shorten it to hit 28.5 MHz.”
- Refresh the web page. The antenna appears under “Your designs.” If it doesn’t, the “designs that failed to load” panel shows the file, the error, and the line number — paste that back to Claude and iterate.
- Tune it with the knobs, the SWR curve, and the impedance readout until the resonance and pattern look right.
The contract (what Claude follows)
Section titled “The contract (what Claude follows)”The seeded CLAUDE.md is authoritative; in brief, a design file must:
- Be named
lowercase_with_underscores.py— the name becomes the antenna’s name (my_dipole.py→user.my_dipole). - Define a class
BuildersubclassingAntennaBuilder. - Provide a
default_paramsmapping (every key becomes a knob, read asself.<key>in the build) and abuild_wires(self)method returning(start, end, n_segments, feed)tuples — exactly one segment carries the feed1 + 0j, the restNone. - Import only from
antennaknobsand the standard library.
from antennaknobs import AntennaBuilder
class Builder(AntennaBuilder): default_params = { "design_freq": 14.1, # MHz — the band this is cut for "freq": 14.1, # MHz — measurement frequency "length_factor": 0.96, "height": 10.0, # metres "ui_params": {"default_view": "xz"}, }
def build_wires(self): wavelength = 299.792458 / self.design_freq h = (wavelength / 4.0) * self.length_factor z, eps = self.height, 0.01 arm = self.nominal_nsegs return [ ((0.0, -h, z), (0.0, -eps, z), arm, None), # left arm ((0.0, eps, z), (0.0, h, z), arm, None), # right arm ((0.0, -eps, z), (0.0, eps, z), 1, 1 + 0j), # driven feed gap ]For path-shaped geometry (loops, vees, rhombics), build_wires can fly a
Drone instead of computing corners by hand —
return drone.wires(). Arrays of identical elements have dedicated builders
(Array1x2Builder, Array2x2Builder, …); reach for those only once a single
element works.