@horu2day/pure-cad-cli
v0.1.1
Published
Headless pure-cad CLI — AutoCAD-style accoremgd analog. Self-contained QuickJS-WASM core; no source code dependency.
Downloads
126
Maintainers
Readme
@horu2day/pure-cad-cli
Headless pure-cad CLI. AutoCAD accoremgd.dll analog — drives the full CAD engine in a QuickJS-WASM sandbox, without exposing source code or a GUI.
Pipes natural-language → script → DXF/DWG for AI agents (Claude Code, Antigravity, GPT, ...).
Install
Public npm package. No token or .npmrc needed — Node.js 20+ only.
# Permanent install
npm i -g @horu2day/pure-cad-cli
# One-shot
npx @horu2day/pure-cad-cli new --output empty.dxfCLI
pure-cad run --script s.js [--input base.dwg] --output out.dwg
pure-cad query --input drawing.dwg
pure-cad export --input file.dwg --output file.dxf
pure-cad new --output empty.dwgAll commands print one JSON line to stdout. Errors → stderr with { ok:false, error, code }.
Exit 0 = success, 1 = failure.
Script API (inside --script files)
cad.cmd(NAME, ...args) feeds the arguments to the command's prompts in
order. A command that still wants input after the last argument draws nothing,
so the argument count matters — see Warnings.
// Drawing
cad.cmd('LINE', '0,0', '100,100'); cad.confirm(); // points…, then confirm
cad.cmd('CIRCLE', '50,50', '25'); // center, radius
cad.cmd('ARC', '0,0', '50,50', '100,0'); // start, mid, end
cad.cmd('RECT', '0,0', '100,50'); // 2 diagonal corners
cad.cmd('PLINE', '0,0', '100,0', '100,100', 'C'); // points…, 'C' closes
cad.cmd('POLYGON', '6', '50,50', 'I', '30'); // sides, center, I/C, radius
cad.cmd('ELLIPSE', '50,50', '100,50', '0.5'); // center, major-end, ratio ≤ 1
cad.cmd('TEXT', '10,10', '5', '0', 'Hello'); // pt, height, ROTATION, string
cad.cmd('MTEXT', '10,10', '90,60', 'Multi-line'); // 2 corners, string
// Modify — these select first. Pick with 'L' (last) or 'ALL', close the
// selection with cad.confirm(), then answer the rest with cad.input().
cad.cmd('MOVE', 'L'); cad.confirm(); cad.input('0,0'); cad.input('50,50');
cad.cmd('COPY', 'L'); cad.confirm(); cad.input('0,0'); cad.input('100,0'); cad.confirm();
cad.cmd('ROTATE', 'L'); cad.confirm(); cad.input('0,0'); cad.input('45');
cad.cmd('SCALE', 'L'); cad.confirm(); cad.input('0,0'); cad.input('2');
cad.cmd('MIRROR', 'L'); cad.confirm(); cad.input('0,0'); cad.input('0,100'); cad.input('N');
cad.cmd('ERASE', 'L'); cad.confirm();
cad.cmd('OFFSET', 'L'); cad.confirm(); cad.input('5'); // or a point on the side
cad.cmd('HATCH', 'L'); cad.confirm(); cad.input('ANSI31'); // boundary must be closed
// Dimensions
cad.cmd('DIMLINEAR', '0,0', '100,0', '50,-15');
cad.dim.radius('50,50', 30, '85,80');
cad.dim.angular('0,0', '100,0', '0,100', '40,40');
// History
cad.history.beginGroup(); /* ... */ cad.history.endGroup();
cad.history.undo();
// Inspect
cad.doc.entities();
cad.doc.summary();Warnings
run returns a warnings array (also printed to stderr as [warn] …) naming
every command that ended without drawing:
{"ok":true,"output":"out/plan.dxf","entityCount":3,
"warnings":["TEXT: cancelled by the next command while still waiting for input after 3 argument(s) — nothing was drawn. Check the argument list for this command."]}An agent should treat a non-empty warnings as a failed script and fix the
argument list — ok:true only means the file was written.
Full reference: see docs/ai/SYSTEM_PROMPT.md in the parent repo.
Architecture
┌─ Host (Node.js)
│ bin/pure-cad.js → src/index.js → src/commands/{run,query,export,new}.js
│ └─ fs.readFileSync / fs.writeFileSync (file I/O)
│ └─ src/wasm-parse.js → io/pkg-node WASM ← DWG parsing (host-side, Rust)
│ ↓ DWG: parseResult JSON injected · DXF: base64 bytes
└─ Guest VM (QuickJS-WASM, sandboxed)
└─ dist/core.bundle.js
├─ CadDocument, HistoryManager, CommandBridge
├─ commands/* (LINE, CIRCLE, MOVE, DIM*, ...)
└─ io/DxfReader, DxfWriter, DwgWriter (pure JS) — no JS DWG readerNo source code escape: Core JS lives inside the WASM-backed VM. CLI binary contains only the bundle and a thin host shim.
Source protection (W-5)
The DWG reverse-engineering algorithm (multi-version read: 2004+ LZ77, 2007+
Reed–Solomon, bitstream decode, handle resolution) is not shipped as
plaintext. DWG files are parsed host-side by the compiled Rust WASM parser
(io/pkg-node), and only the resulting parseResult (JSON) is injected into the
VM. The Core bundle keeps the JS DXF reader (a public, documented format) and
the DWG/DXF writers, but carries no JS DWG reader.
Two build-time gates enforce this (npm run build):
build/build-core.mjs— an esbuild guard that fails the build if any DWG-reader module (DwgReader,io/internal/{entities,tables,structure,compression,header},ObjectMap/HandleResolver,BitReader,LcgXorMask/ReedSolomon) is re-imported into the bundle. Writer shared primitives (BitWriter,Crc,EntityCommon) stay allowed.build/guard-bundle.mjs— greps the built bundle for reader symbols and fails if any leak.
Scope (honest boundary): this seals the reader algorithm. R2000 write
knowledge remains reconstructible from the bundled DwgWriter until that writer
is ported to Rust (roadmap item T-1). Loud-fail: a DWG the Rust parser
cannot read surfaces WASM_PARSE_FAILED — there is no silent JS fallback.
Requires the local Rust build
io/pkg-node(npm run io:build:nodeat the repo root) present alongside the package during DWG operations.
License
UNLICENSED — internal use only.
