Install
openclaw skills install @chanmeng666/archlangUse when the user wants to create, edit, or inspect an architectural floor plan / building layout as code — e.g. "draw a 2-bedroom apartment", "add a bathroom to this plan", "make the bedroom 1 m wider", "lay out an office floor". ArchLang is a tiny text language that compiles a .arch file to a professional floor-plan drawing (SVG/PNG/PDF/DXF). Drive it entirely through the archlang CLI; do not hand-render.
openclaw skills install @chanmeng666/archlangArchLang turns a small .arch text file into a professional floor-plan drawing. It is built for
agents: deterministic, self-correcting (errors carry a machine code, a prose fix, and often a
machine-applicable fix archlang fix can apply), and verifiable without ever looking at an image
(archlang describe).
.arch to SVG, DXF, TXT, PDF, PNG or Plan JSON; it never reads images, so a plan from a photo is written by you.validate --strict and lint check soundness (doors on walls, reachability, clearances) and give a fix per finding.describe reports rooms, areas and adjacency; takeoff reports quantities as csv or md.fix, repair and finish rewrite the source; each takes --dry-run to preview first.--expected-hash <source_hash> on those refuses a stale file (exit 4).impact --against old.arch --lock <ids> exits 2 when a locked element moved: edit in place, never redraw.impact --set NAME=value shows what one top-level let would move.keep lines in the plan hold a relation through every edit and report a residual.level blocks give storeys; axes gives one grid; keep stacked aligns shafts.validate --intent checks a plan against a brief; score measures it.compile --from-json) and out (-f json).link prints a playground URL; preview renders a PNG.The CLI runs straight from npm — no clone, no build:
npx -y @chanmeng666/archlang --help
Or npm i -g @chanmeng666/archlang for a global archlang command. Every call below is written
archlang <cmd>; without the global install, write npx -y @chanmeng666/archlang <cmd>. Never run
a bare arch: the package installs it as an alias, but on Linux, macOS and Git Bash coreutils
owns that name, so the shell runs the wrong program.
archlang spec and read it — the entire language in one page.
(archlang context prints everything: spec + this workflow + CLI reference + error catalog; a small model starts with archlang context --level starter, one screen.)
Do this before writing any .arch..arch file (or pipe via stdin with -), preferring the placement
sugar below so you never hand-compute a coordinate.archlang compile plan.arch -o plan.svg --json. The JSON is { ok, diagnostics, summary }.ok is false, run archlang fix plan.arch --dry-run --json
to preview the machine-applicable edits (off-wall opening → attachment form, out-of-range
position clamped, …), then re-run without --dry-run to apply. Anything fix can't resolve stays
in diagnostics[].fix for you to edit by hand. Exit code 2 means a deterministic user error —
fix it, don't blindly retry (1 = IO/internal, 3 = bad usage).archlang compile plan.arch -f txt (or archlang preview plan.arch --ascii) prints a zero-dependency ASCII floor plan you can read straight from stdout.archlang describe plan.arch --json returns the rooms (areas, adjacency), what each
door connects, and totals. Confirm the room count, labels, and areas match what was asked.
Each room reports two areas: area_m2 on wall centrelines and net_area_m2, the floor no wall
stands on (the room less every wall band inside it, computed exactly, or absent when it cannot
be, and then so is the net total); totals sums both. Quote net_area_m2 as floor area, and name
which figure you report.archlang validate plan.arch --strict --json
(parse + resolve + lint). --strict makes every advisory warning fail (exit 2) — the gate a
generation pipeline runs before it ships. Add --graph g.json to also assert the intended
room-to-room adjacency ({ "living": ["kitchen","hall"], … }); a mismatch fails. Read each
diagnostics[].fix, edit, and re-run until it passes — or, if a warning is deliberate, say so.
The author's loop is compile → validate --json (fix its next first) → lint → repair, one
stage at a time: the rooms (with stairs and outdoor surfaces), then
archlang validate plan.arch --stage rooms --json until at_stage.clean is true; then the walls
(walls from rooms) and --stage walls; then doors and windows, --stage openings; then fixtures
and furniture, --stage fixtures; then the sheet and the full validate --strict. Advance when
the stage is clean, or when every remaining finding is one you accept and say so: a later stage's
findings are held back (the summary counts them), because they mean nothing until the earlier
decisions stand; errors always show. Clean is not permanent: a later stage can add or re-raise an
earlier stage's finding (walls narrow a room to its clear width, a bed makes a double, furniture
pinches a walk), and each later stage shows the earlier stages' codes again. Read the
summary (first key of lint|validate --json, first line of the text report) before anything
else: it counts the warnings and the distinct codes and leads with the commonest, so a truncated
read still tells you how much is hidden.
The order inside the loop is the stage list, brief → rooms → walls → openings → fixtures → drafting, and within a stage: columns, then the exterior walls, then the other walls, then fences;
doors, then windows, then plain openings; catalogued fixtures, then loose furniture; dimensions,
labels and the sheet last. Rooms (with stairs, voids and outdoor surfaces) come first because they
fix the program and the walls can be derived from them; the exterior goes before the interior; an
opening is cut into a wall that must already exist; drafting reads everything else. This is authoring order only: the order shapes are painted in
the SVG is a separate thing and does not change.
Review, once that loop is clean: the reviewer (not the author) runs
archlang score plan.arch --taste generic --json and reads the advice: a sourced 0–1 score
(privacy gradient, daylight, proportion, corner doors, …) whose criteria[].advice A_* codes
name what to improve. It never gates, the author does not iterate on it, and a score capped at 0.5
means a validity lint warning still stands. The reviewer then looks at
archlang preview plan.arch --review -o review.png: every warned element ringed and numbered on
the plan, a legend, and close-ups of flagged openings and pieces. On a lint-clean plan it still
shows the access graph (which rooms each door joins, which rooms the entrance reaches) and which
door is the main entrance or that the choice is a tie: check those against the brief. If no one
reviews for you, run both once after the loop is clean and report the score and its A_*
advice; do not iterate on it.
Editing a plan that already exists starts differently, before the steps above:
archlang validate plan.arch --json to record the starting state, then choose the ids the request does not name and lock
them. archlang impact plan.arch --set NAME=value --lock <ids> is the pre-edit check (it writes
nothing); archlang impact new.arch --against plan.arch --lock <ids> is the post-edit check on
an edited copy. Exit 2 (E_LOCKED_ELEMENT_CHANGED) means the edit moves something it should
not: change the edit, not the lock. Then make the edit and run steps 3 to 7 on the result.intent.json — its checkable
expectations as data (room count, per-room concepts with area/window bands, total area, optional
adjacency/reachability). A stated size binds: "about 70 m²" → grossInternalAreaM2 (inside the
shell's inner faces); a room's stated width/depth/W × H → roomDimensions. Two disciplines keep
it brief-grounded: assert an area band only where the brief gives a number ("about/~N" → ±10%; "at least N" → min only; qualitative words → nothing),
and assert the top-level room count only when the brief enumerates the rooms. Gate with
archlang validate plan.arch --intent intent.json --feedback --json: a gating miss (room
count/existence/area/total-area/window/size) fails (exit 2) with a per-violation correction prompt —
iterate on the feedback and re-run. Adjacency/reachability are advisory (reported, never fail the
gate). Use archlang score plan.arch --brief intent.json --json as a continuous satisfaction meter
(always exit 0) to watch the plan approach the brief across edits. See
/intent.schema.json. A number or relation the brief
fixes and every edit must keep (a 3.6 m clear living room, a partition on axis 2, a bathroom over a
bathroom) goes in the source as a keep (keep min clear living 3600); it moves and draws nothing, a
broken hard keep (the default) is W_KEEP_VIOLATED with a signed residual and fails --strict,
and archlang impact --set lists which keeps a change breaks, holds or heals, each with its
residual before and after.archlang repair plan.arch -o fixed.arch pushes furniture out of
walls/doorways/swing arcs (the geometric corrector; distinct from fix). It rewrites a piece in
the form you wrote it (an at point, or the inset of an in <room> anchor … placement) — and
always read unresolved: a piece it may not rewrite (one for/component statement drawing
several pieces, expression coordinates, an against wall anchor) is reported there with the fault
and why, never silently skipped. changed: false with a non-empty unresolved means you edit
the source.archlang finish plan.arch --dry-run --json, then
without --dry-run to apply. It first furnishes each rectangular or L-shaped room that holds no furniture,
by the room's use (on every storey; a furnished room is never touched — a partly furnished one is
named in unresolved with the categories it lacks, not topped up), then adds the sheet
statements the plan lacks — paper, scale, dims auto, title, schedule rooms,
legend — on the smallest sheet the drawing fits. It fills only what is missing and deletes
nothing. Read unresolved: a room it left empty or a statement it left out is named there
with the reason. A room it has no pieces for (a hall, a store, a room of no known use) is named
too, with scope: "outside-table"; that note asks nothing of you unless the room should hold
furniture, so changed: false with only such notes means the plan is finished (--only furnish/--only sheet runs one step;
--reissue lets it replace a paper/scale the drawing does not fit).archlang preview plan.arch -o plan.png renders a PNG (--install fetches the
optional renderer if missing). To hand the user the plan itself, rendered and with nothing to
install, run archlang link plan.arch (or npx -y @chanmeng666/archlang link plan.arch): it
prints a playground URL that opens the plan; --json adds the embed URL.A geometry-blind generator that emits absolute coordinates produces plans that render but are physically wrong (openings off their wall, furniture through walls). Author by relation instead — the compiler computes the coordinate, and fails loudly if the reference is ambiguous.
Start from the rooms. Write the rooms (a corridor is a room), let walls from rooms derive
every wall, and host each opening by the rooms it joins. This plan compiles and lints clean:
plan "Room graph" {
units mm
grid 100
room id=living at (0,0) size 4000x4000 uses living
room id=hall right-of living size 1500x4000 uses hall
room id=bedroom right-of hall size 3000x4000 uses bedroom
walls from rooms exterior 200 partition 100 { open between living and hall }
door on hall.bottom at center width 900 swing into hall
door between hall and bedroom at 800 from start width 800 swing into bedroom
window on living.left width 1500
window on bedroom.right width 1200
furniture bed in bedroom anchor bottom-right flush size 1500x2000
}
walls from rooms exterior 200 partition 100 derives the shell on their outline
and a partition on every shared edge, so no wall can go missing. Host openings by the rooms:
door between living and bed at 800 from end width 800 (a door must say where: center,
<mm> from start|end|<room>.<edge>, axis <n>, offsets to the centre), window on bed.top width 1500 (centred by default). A jamb too near a meeting wall is refused (E_OPENING_NIB
names the legal spot), never slid. { except <room> } leaves a room out. For an
open-plan join write open between living and kitchen in the body (an opening across the whole
shared edge, or width <mm> centred; never a door on that edge), and between a and b thickness 150 for one thicker partition. Refusals name the rooms (E_ROOMS_OVERLAP, E_ROOMS_NOT_TILING,
E_PARTITION_TOO_SHORT, E_ROOM_NOT_RECTANGLE, E_WALL_BETWEEN_NOT_ADJACENT).wall id=p partition thickness 100 between hall and bed stands on the two rooms' shared edge
and wall id=shell exterior thickness 200 around [hall bed bath] is one ring on their outline
(no hole). They keep your id, thickness and material, host doors like any wall, and replace
the derived span beside walls from rooms.r exposes r.top, r.bottom, r.left,
r.right, r.cx, r.cy, so wall partition thickness 100 { (hall.right, hall.top) (hall.right, hall.bottom) } follows the room when it grows. Use them in walls, openings, furniture and dims,
not in a let or a room's own at/size (E_DATUM_BEFORE_ROOMS).at (x,y). door on <wall> at <pos> … /
window on <wall> at <pos> … / opening on <wall> at <pos> …, where <pos> is millimetres along
the wall or a percentage (50%). swing into <room> picks the swing direction toward a named room,
swing inward|outward towards/away from the building's floor (in|out follow the wall's drawn
direction: in = right of travel on the page, so they flip on a wall drawn the other way);
hinge near start|end hinges at the segment end nearer a wall end. (Off-wall/ambiguous →
E_ATTACH_WALL_REF; past the wall → E_ATTACH_POS_RANGE.)strip. strip right at (0,0) gap 0 height 4000 { room … room … } places a row
(or column, with down/up + width) of rooms end to end — no per-room at.furniture <kind> in <room> anchor <9-point anchor> [flush] [inset <mm>] …
snaps a piece to a room corner or edge — the anchor is one of top-left, top, top-right,
left, center, right, bottom-left, bottom, bottom-right; against wall <id> backs
plumbing/kitchen fixtures onto a real wall face. Both are closed-form and never float or penetrate.rotate. furniture chair at (…) size 450x450 facing t turns
the chair to table t (and a sofa to its TV); against <room>.<edge> backs a piece onto a derived
wall; clear front 600 asks lint to keep that depth free. Lint flags a chair facing away from its
table (W_SEAT_FACING_AWAY).flush and never compute half a wall thickness. A room's rectangle runs along wall
centerlines, so a bare anchor bottom leaves the piece's back inside the solid
(W_FURNITURE_WALL_COLLISION). anchor bottom flush measures from the backing wall's inner face
instead, and flush inset 50 from 50 mm off it — so write furniture wc in r_bath anchor bottom flush size 400x700, not an inset 100 you worked out from a thickness you never named. (flush needs an
anchored edge: on centered/anchor center it is E_FURN_FLUSH.)door or cased opening on a wall of every room
(an open-plan space still needs a modeled opening), and keep furniture out of the doorway approach
(≥300 mm) and the leaf's swing.at (x,y) is the fallback, not the default — reach for it only when no attachment
expresses what you mean.See examples/two-bed.arch for a furnished two-bedroom flat authored this way, and archlang spec for the grammar.
When a building repeats — two wings, six wards, a floor of identical units — do not copy the
statements and re-add an offset to every coordinate. That is how a plan gets 40 hand-edited numbers
that drift apart the first time the brief changes. Author the piece in its own coordinates from
(0,0) and place it:
component wing() {
wall id=shell exterior thickness 300 { (0,0) (18000,0) (18000,12000) (0,12000) close }
room id=main at (0,0) size 18000x9000 label "Gallery" uses living
room id=corr at (0,9000) size 18000x3000 label "Corridor" uses circulation
}
place wing() as west at (0,0)
place wing() as east at (42000,0) mirror x
as names the instance, at places it; both are required. Ids inside become
west.main / east.main, and you address them by that dotted name everywhere a reference is
taken: wall west.shell, in west.main, archlang describe --room west.main. (A dotted name in a
declaration is E_DOTTED_DECL — the namespace belongs to the place.)rotate 0|90|180|270 and mirror x|y are exact, and a mirror is real physics: door swings,
fire exits and fixture facings all come out mirror-image. Prefer mirror x over rotate 180 for a
symmetrical pair — a 180° turn also swaps which side the corridor is on.import "wing.arch" as wing binds that file's top-level
drawable statements as a zero-argument component (its plan settings are ignored — the root plan
owns the sheet). One file, one room or wing, exactly as you would organise components in code.placed instance is also a zone, so describe().zones, archlang describe --zone west and the
grouped schedule rooms table work with no extra declaration.wing() is the OLD inline macro — caller's coordinates, caller's id counters, no
namespace. Keep it for a small parameterised motif; use place for a piece of building.wall west.shell), but
the component cannot reach out. Let the parent draw the connecting doors — that is the whole
composition contract. Verify with archlang describe --json → instances[].See examples/museum-wing.arch + examples/museum-wings.arch.
archlang compile --json returns every problem as a Diagnostic with a byte span, line/col, a
catalogued E_*/W_* code, and a prose fix. Where the correction is a mechanical text edit, the
diagnostic also carries machine-applicable fixes:
archlang fix applies them in a bounded, self-checking fixpoint — only machine-applicable by
default (--unsafe also applies maybe-incorrect; --dry-run previews; --force keeps a pass
that would otherwise roll back). Use it to clear the syntactic faults before you touch anything by
hand.archlang fix is syntactic; archlang repair is geometric. fix rewrites text where the right text is
known (e.g. an off-wall door → the attachment form); repair moves furniture to a position no
text edit could express. They compose — fix first, then repair.archlang fix also applies fix-carrying lint advisories, not only compile-stage faults — e.g.
W_ALIAS_MATCH (a room's use inferred from an indirect label alias) fixes by inserting the explicit
uses … it inferred. Before editing, archlang describe --json's freedom block tells you which
element positions were hand-authored (absolute) vs derived by the resolver
(relational/strip/attached/anchored/against-wall), so you know which numbers are safe to nudge.fix/repair never add a door or a window — where to put one is a design choice the compiler must
not make. When lint reports W_ROOM_UNREACHABLE, W_ROOM_DISCONNECTED, W_NO_ENTRANCE,
W_BATH_VIA_BEDROOM, or W_BEDROOM_NO_WINDOW, ask ArchLang for candidates:
archlang suggest plan.arch --json returns ready-to-paste door/window statements (furniture-aware
— a door candidate never opens onto a wardrobe; each references its wall by a stable ref — an
authored id or a unique category — or absolute coordinates, never a re-bindable positional id) plus a
rationale for each — for a room with no path back (W_ROOM_UNREACHABLE), a building with no way in
(W_NO_ENTRANCE), a bath reachable only through a bedroom (W_BATH_VIA_BEDROOM), or a windowless
bedroom (W_BEDROOM_NO_WINDOW). Choose one and insert it, then re-run the loop. This replaces
hand-computing coordinates.suggest offers nothing that fits): from describe().access, connect each
unreachable room in priority — (1) a new exterior entrance door on <exterior wall> at <pos>
into a living/kitchen/hall with an exterior edge (avoids routing through a bedroom); else (2) a
door on <shared wall> at <pos> to an adjacent reachable, non-bedroom room; and give a windowless
bedroom a window on <its exterior wall> at <pos> width 1200. Never make a bathroom reachable only
through a bedroom. Then archlang repair (a new door may pinch furniture) and re-gate.An existing opening
validatereports off its wall (W_DOOR_OFF_WALL/W_WINDOW_OFF_WALL/W_OPENING_OFF_WALL) is a mis-coordinate, not a missing connector — runarchlang fix(it rewrites it to the attachment form) rather than adding a new one.
A window has a kind too: fixed · casement · tilt · tilt_turn · sliding. Left out, it draws as a plain window. Only
casement and tilt_turn take hinge left|right. fixed and tilt are no way onto a balcony.
A door has a kind — hinged (the default) · sliding · barn · bifold · pocket · garage ·
double · glazed_sliding (a glass terrace door) — and it changes what the checks can say, not just the drawing. Only hinged and double
(two leaves, one per jamb) sweep arcs, so W_SWING_OBSTRUCTED cannot apply to any of the others.
A hinged leaf over 1219 mm raises W_LEAF_TOO_WIDE: a wide doorway is a double door.
That makes the kind a legitimate answer to a diagnostic, and often the right one:
W_SWING_OBSTRUCTED on a door into a tight room — a sliding or pocket leaf sweeps nothing, so
the warning goes away because the plan genuinely fixed it, not because you silenced a rule. Moving
the furniture is the alternative; pick whichever the brief actually wants.pocket earns its own check instead: W_POCKET_RUN measures the wall the panel must slide into.
Swapping one warning for the other is a real trade, so read the new one rather than assuming a win.E_DOOR_KIND_CLAUSE): hinge is
hinged-only, slide/open are sliding-family-only, and garage takes no clause at all — which
side it parks on is derived from which face has floor. A kind other than hinged or double on an
arc wall also refuses (E_DOOR_KIND_CURVED).A window has a kind too, when the source says so: fixed · casement · tilt · tilt_turn ·
sliding. It shows in describe, the schedule and the legend. hinge goes on casement and
tilt_turn only, and only when the side is known: never guess one.
archlang spec has the full grammar; examples/bungalow.arch is the worked plan.
site when the brief mentions orientationsite { street <north|south|east|west> [hemisphere …] } draws nothing. It exists so orientation
becomes a fact the tools can check, and without it two things silently cannot run:
describe --json gains a site block naming five directions — street, back, equator_side,
sunrise_side, sunset_side — and an intent's windows.facing may assert those names. With no
site declared, that assertion fails with E_INTENT_NO_SITE rather than passing vacuously.W_ROOM_NOT_EQUATOR_FACING is the one rule that reads it, flagging a room whose windows all face
away from the equator side.So: if the brief says "south-facing living room", "morning light in the kitchen", or "faces the
street", declare site first — otherwise you have written a plan whose central requirement nothing
can verify. It is a drafting heuristic for an aspect, not a daylight measurement: there is no sun
model, no latitude and no date, and _side names must not be read as more than an orientation.
examples/bungalow.arch demonstrates both this and the door kinds above.
height only when the brief gives oneheight <mm> (plan, level or wall) and sill/head on an opening are the vertical datum:
they draw nothing, because a plan is a horizontal cut, and describe --json reports them only if
the source wrote one — so adding a height you were not asked for changes what every consumer sees
for no gain. Write them when the brief names a ceiling, a parapet or a cill line; otherwise leave
them out and read the defaults from archlang manifest --json's datum block.
The CLI documents itself, and every read can be narrowed at the source — never pull a whole plan's facts into context just to filter them yourself.
archlang <cmd> --help prints that one command's flags and worked
examples — every command carries at least one copy-pasteable invocation. archlang --help lists the
commands; archlang --version prints the version. A flag a command doesn't take is a usage error
(exit 3) with a did-you-mean, never a silently-swallowed filename — so a typo fails loudly
instead of compiling the wrong thing.archlang describe --select <keys> emits only the named top-level keys (rooms, doors,
windows, openings, furniture, access, circulation, totals, freedom, caption, …);
the ok/plan/units/diagnostics envelope is always kept, so narrowing can't lose the verdict.
archlang describe --room <ids> keeps only those rooms plus the doors/windows/furniture that touch
them (whole-plan facts — bbox, totals, caption, each room's adjacent — stay whole-plan, so a
narrowed read never lies about the building). Both mark the result with filtered: true.archlang lint|validate --code <CODE,…> / --severity error|warning show only the diagnostics you
asked for. These are display filters only: ok and the exit code are always computed from the
unfiltered set, so reading less can never turn a failing plan green. A filtered result carries
filtered: true + total_diagnostics.archlang context --section spec|workflow|cli|errors prints one section of the bundle
instead of all of it (about 160 KB in all) — errors for the diagnostic catalog, cli for the
command reference.archlang fix --dry-run prints the exact unified diff it would write (to stderr; --json also
carries it as diff) and touches nothing. When you do apply in place, --backup keeps the
original bytes at <file>.bak.archlang compile plan.json --from-json -o out.svg. The schema is served at
/plan.schema.json./archlang.gbnf.archlang spec # the whole language in one page — READ THIS FIRST
archlang context # everything in one call: spec + this workflow + CLI reference + error catalog
archlang context --section errors # just one section of it (spec|workflow|cli|errors)
archlang --help # list the commands
archlang manifest --json # the whole CLI API as data: commands, flags, formats, lint rules, error codes
archlang compile plan.arch -o out.svg --json # render (also -f dxf|txt|json|pdf|png)
archlang compile plan.arch -f txt # zero-dependency ASCII text plan on stdout
archlang compile plan.json --from-json -o out.svg # compile structured Plan JSON (see /plan.schema.json)
archlang import --from openplan3d plan.openplan.json -o plan.arch --json # an OpenPlan3D JSON plan as .arch + a report of what it left out; then lint it
echo '<source>' | archlang compile - -o - -f svg # compile stdin → SVG on stdout
archlang fix plan.arch --dry-run --json # preview the machine-applicable fixes as a unified diff (drop --dry-run to apply; --backup keeps <file>.bak)
archlang suggest plan.arch --json # advisory door/window statements: unreachable room / no entrance / bath-via-bedroom / windowless bedroom
archlang describe plan.arch --json # semantic facts: rooms, areas, adjacency, door connections, circulation
archlang describe plan.arch --select rooms,totals --room kitchen --json # narrow the facts to what you actually need
archlang impact plan.arch --set W=4500 --json # change one let: what moved and why, what held, what broke or healed
archlang lint plan.arch --json # architectural soundness warnings
archlang lint plan.arch --code W_NO_ENTRANCE --json # display filter only — never changes `ok` or the exit code
archlang validate plan.arch --strict --json # parse + resolve + lint; --strict fails on warnings (the ship gate)
archlang validate plan.arch --graph g.json --json # also check interior-door adjacency against an intended graph
archlang repair plan.arch -o fixed.arch # geometric corrector: furniture out of walls/doorways/swings + change log
archlang finish plan.arch --dry-run --json # after lint is clean: preview the furniture and sheet statements it would add (drop --dry-run to apply)
archlang fmt plan.arch --write # canonical formatting
archlang batch a.arch b.arch -f svg --json # render many plans/variants at once → results[]
archlang preview plan.arch -o plan.png # render a PNG to SHOW the user (--install fetches resvg if missing)
archlang new -o plan.arch # scaffold a starter plan
archlang explain E_ROOM_SIZE --json # look up any diagnostic code
(An optional MCP server, @chanmeng666/archlang-mcp, wraps these same library functions for
MCP-native hosts — prefer the CLI when you have a shell; it costs nothing in context until called.)
archlang spec)4000); origin top-left, +x right, +y DOWN. An
optional metric suffix is exact sugar for the same mm value — 4m = 4000, 40cm = 400,
20mm = 20 — so you can write 4m instead of hand-multiplying; bare numbers are unchanged.on <wall> at <pos>) so they always sit on a segment; a raw at
that lands off any wall warns (and archlang fix rewrites it).archlang manifest --json →
fixtureCategories) renders a plan symbol and ignores label; an uncatalogued word falls
back to a labelled rectangle. Most kinds also carry a footprint, so furniture <kind> against wall <id> in <room> needs no size — offset is the piece's CENTRE along the wall run. Put
fixtures in every bath and kitchen so lint stays quiet.dims auto draws dimension strings for you (overall, rooms, walls, or all).Treat the CLI as the source of truth — author, render, and verify through it rather than reasoning about SVG by hand.
Four short skills sit beside this one in skills/, one job each
(npx skills add ChanMeng666/archlang offers them with this one). Each is a
checklist of CLI calls and holds no language reference of its own:
archlang-brief-to-plan: write a new plan from a written brief and check it against the brief.archlang-edit-plan: change an existing .arch with the smallest edit, then re-verify.archlang-export: deliver SVG, PNG, PDF or DXF files, with paper size, scale and storeys.archlang-review-plan: check a plan (lint profiles, the intent gate, suggestions) without editing it.