DOCS
How to write it
Declare ports, then connect them. That is the whole idea. Every diagram below was compiled at build time by the published package from the source beside it — none was redrawn.
01Install
For the command line, install the CLI.
pnpm add @love-rox/kumihimo-cli
kumihimo build studio.khm -o studio.svg # draw it
kumihimo check studio.khm # validate only
kumihimo build studio.khm --watch # redraw on save
kumihimo export studio.khm drawio # editable draw.io file
kumihimo export studio.khm cable --stdout # cable schedule as TSVIt installs as both kumihimo and khm. Warnings do not fail the build unless you pass --strict.
02Your first diagram
Declare the equipment, then wire port to port. The extension is .khm.
device pc "ノートPC" as computer { out HDMI : hdmi }
device mon "モニター" as display { in HDMI : hdmi }
pc.HDMI -> mon.HDMI : hdmi 5m "V-01"In device <id> "<label>" as <kind>, the <id> is how connections refer to the device and is not drawn. The "<label>" is what appears on the diagram.
03Devices and ports
device <id> "<label>" as <kind> {
in <port spec> : <signal> # input
out <port spec> : <signal> # output
io <port spec> : <signal> # bidirectional (Dante, Ethernet …)
@model "HyperDeck Studio HD Mini"
}A port spec takes four forms, so a sixteen-channel desk is not sixteen lines.
| Form | Example | Expands to |
|---|---|---|
| Single | SDI | SDI |
| List | L, R | L, R |
| Numeric range | 1..4 | 1, 2, 3, 4 |
| Prefixed range | CH[1..16] | CH1 … CH16 |
device mixer "Yamaha DM3" as mixer {
in CH[1..16] : xlr
out L, R : xlr
io DANTE : dante
}
device amp "アンプ" as amplifier { in IN_L, IN_R : trs }
mixer.(L, R) -> amp.(IN_L, IN_R) : trs 3m "A-10"Declaration order is preserved and drawn. IN 1 never ends up below IN 2. Position within a device is meaningful information, so nothing gets reordered.
Sixteen connectors drawn at one pitch read as sixteen of the same thing. Real equipment is not like that: four HDMI inputs, then four SDI, then a pair of analogue jacks, and the panel says so by leaving room between them. A gap line leaves that room above whatever is declared next.
device sw "ATEM Mini Extreme" as switcher {
in 1..4 : hdmi
gap
in 5..8 : sdi
gap 2
in AUDIO_L, AUDIO_R : trs
out PGM : sdi
gap
out STREAM : lan
}One gap is half a port pitch, so gap 2 is a whole one. Consecutive gaps add up. A declaration that expands into many ports gets the space once, before the first of them: gap above in CH[1..16] is one space before CH1, not sixteen down the strip.
This is presentation only. No port, connection or schedule changes — the same diagram with every gap removed describes the same system.
The kind picks the shape: camera switcher mixer recorder player display projector speaker microphone amplifier computer converter matrix patchbay router interface generic. It defaults to generic.
Any @-prefixed line is metadata. It is not drawn, but it is carried into the equipment schedule and the exports.
04Connections
<device>.<port> <arrow> <device>.<port> : <signal> <modifier>*| Arrow | Meaning |
|---|---|
-> | one way, in the direction signal flows |
<-> | two way (Dante, Ethernet, control …) |
-- | no direction (power …) |
Modifiers may appear in any order and any may be omitted.
| Syntax | Meaning | Example |
|---|---|---|
<length> | cable length | 10m 30cm 2.5m 3ft |
"<label>" | cable number or name | "V-01" |
via "<part>" | adapter or converting lead | via "HDMI-DVI cable" |
[k=v, …] | arbitrary attributes | [connector=BNC, color=blue] |
device mixer "卓" as mixer { out L, R : xlr io DANTE : dante }
device amp "アンプ" as amplifier { in IN_L, IN_R : trs in AC : ac }
device netsw "スイッチ" as router { io 1 : dante }
device pdu "電源" as generic { out 1 : ac }
mixer.(L, R) -> amp.(IN_L, IN_R) : trs 3m "A-10" [color=青]
mixer.DANTE <-> netsw.1 : dante 10m "N-01"
pdu.1 -- amp.AC : ac 2mWhen both ends name the same number of ports, they pair up in order. mixer.(L, R) -> amp.(IN_L, IN_R) is the same as writing two lines.
[color=…] is the cable jacket, overriding the signal type default. It is not decoration: it is how a run gets identified on site — "the blue one into 1" — so the colour follows through into the cable schedule. Colour names work in English or Japanese, and hex such as #0af works too. Anything else becomes a diagnostic and never reaches the drawing.
Length is a bare token of a number and a unit. # is reserved for comments, so it never prefixes a length.
05Groups
A frame around a location, a rack or a subsystem. Nesting is one level in v0.1.
diagram "会場" { direction: LR }
group stage "ステージ" {
device cam1 "FX3" as camera { out SDI : sdi }
device mic1 "SM58" as microphone { out OUT : xlr }
}
group rack "ラック" {
device sw "ATEM" as switcher { in 1..2 : sdi out PGM : sdi }
device mixer "DM3" as mixer { in CH[1..2] : xlr out L, R : xlr }
}
cam1.SDI -> sw.1 : sdi 30m "V-01"
mic1.OUT -> mixer.CH1 : xlr 20m "A-01"06Signals and compatibility
This is where it stops being a drawing tool. Every connection is judged on whether it can physically work.
The verdict is reached from what the two ports themselves declare. The : <signal> on a connection describes the cable, and only fills in for an end that declares nothing. Get that backwards and a type is compared against itself, so no mismatch can ever be found.
| Verdict | Meaning | Treated as |
|---|---|---|
ok | a normal connection | silent |
lossy | works, but something is given up or a part is needed | warning |
incompatible | will not work without active conversion | reported |
The faults worth catching are the ones where the plug seats, the drawing looks right, and no signal arrives. Both cables below merely share a connector.
device ext "HDBaseT受信器" as interface { out CAT : hdbaset }
device netsw "L2スイッチ" as router { in 1 : lan }
device cam "カメラ" as camera { out SDI : sdi }
device fs "フレームシンクロナイザ" as generic { in REF : genlock }
ext.CAT -> netsw.1 : hdbaset 20m "N-01"
cam.SDI -> fs.REF : sdi 5m "V-90"- warning
signal-mismatchext.CAT → netsw.1: HDBaseT は Cat ケーブルと RJ45 を使うが Ethernet ではない。スイッチには挿せない - warning
signal-mismatchcam.SDI → fs.REF: BNC を共有するだけ。同期基準入力に映像を入れてもロックしない
Every verdict carries its reason, and the reason follows through into the cable schedule. The same trap exists for dmx↔xlr, rca↔spdif, adat↔spdif, composite↔component and wordclock↔sdi.
A site standard can be stated once, with its reason. The reason reaches the diagnostic and the schedule, so why a connection was allowed is never lost from the drawing.
compat aes -> xlr : ok "house standard: under 10m"
compat xlr -> rca : lossy "always through a DI"The diagnostics you will meet most. Severities are configurable.
| Code | Meaning | Default |
|---|---|---|
signal-mismatch | the two ends disagree about the signal | warning |
adapter-required | an adapter is needed but not declared | warning |
adapter-insufficient | via declared but no cable can bridge this | error |
direction-mismatch | output to output, or input to input | error |
port-overbooked | more than one source into one input | error |
implicit-device | referred to an undeclared device | warning |
unconnected-port | a declared port wired to nothing | off |
Nothing throws. Every stage collects diagnostics and returns a best-effort result, so a faulty diagram still renders — a picture of a flawed system is exactly what an author needs in order to see the flaw.
07Adapters
Declares that a passive adapter or converting lead sits in the run.
device pc "PC" as computer { out HDMI : hdmi }
device mon "モニター" as display { in DVI : dvi }
pc.HDMI -> mon.DVI : hdmi 2m "V-01" via "HDMI-DVI変換ケーブル"via is not a way to silence a warning. It is a declaration that puts a part on the schedule. The link gets a conversion mark and the adapter appears as a line item.
For pairings a cable can genuinely bridge (HDMI↔DVI, DP→HDMI …), declaring via clears the diagnostic; leaving it out is still reported, with the required part named. For pairings no cable can bridge (SDI→HDMI …), via does not clear it. Those need a powered box, which belongs in the diagram as a device rather than as a property of a cable.
# Wrong: no cable turns SDI into HDMI
cam.SDI -> mon.HDMI : sdi via "SDI-HDMI converter"
# Right: the converter is a device
device conv "BMD Mini Converter SDI-HDMI" as converter {
in SDI : sdi
out HDMI : hdmi
}
cam.SDI -> conv.SDI : sdi
conv.HDMI -> mon.HDMI : hdmi08Wireless
A radio path is not a cable, so it carries a frequency or a channel where a cable carries a length. It is drawn dashed.
device mic "ワイヤレスマイク" as microphone { out RF : uhf }
device rx "受信機" as interface {
in RF : uhf
out CH1 : xlr
}
device desk "卓" as mixer { in CH1 : xlr }
mic.RF -> rx.RF : uhf [ch=38]
rx.CH1 -> desk.CH1 : xlr 3m "A-01"Wire a microphone straight into the desk with no receiver and you get told. The boundary between wireless and wired is judged too.
09Equipment libraries
A desk's sixteen channels do not get rewritten per drawing. Define it once with model, instantiate as many as you like with device … from.
model dm3 "Yamaha DM3" as mixer {
in CH[1..16] : xlr
out L, R : xlr
@vendor "Yamaha"
}
device foh from dm3
device mon from dm3 "モニター卓"Definitions in another file are pulled in with use. If an imported file also held devices or connections they are ignored, with a warning: a library is where definitions live, not drawings.
use "lib/yamaha.khm"
device foh from dm3 "FOH desk"10Appearance
diagram "Studio A" {
direction: LR # LR (left to right, default) | TB (top to bottom)
theme: light # light (default) | dark | mono | blueprint
spacing: 60 # gap between nodes, px
}| Theme | For |
|---|---|
light | Default. Screen and colour print |
dark | Dark screens |
mono | Black and white print and photocopies |
blueprint | Blueprint colouring, as facility drawings use |
mono uses no colour at all. Signals are distinguished by line style instead, and a jacket colour given with [color=…] is ignored — pretending a colour survived a photocopy helps nobody.
diagram "白黒印刷用" { theme: mono }
device cam "カメラ" as camera { out SDI : sdi }
device sw "スイッチャー" as switcher { in 1 : sdi out PGM : sdi }
device rec "レコーダー" as recorder { in SDI : sdi }
cam.SDI -> sw.1 : sdi 30m "V-01" [color=青]
sw.PGM -> rec.SDI : sdi 2m "V-10"A theme can also be passed with -t/--theme, but a diagram { theme: … } in the source wins: the drawing knows how it is meant to look, the caller only knows a default.
11Output and embedding
Besides SVG, it exports an editable draw.io file and TSV cable and equipment schedules.
Markdown takes a kumihimo code fence directly; it becomes SVG at build time.
```kumihimo
cam.SDI -> sw.1 : sdi 30m "V-01"
```
import rehypeKumihimo from '@love-rox/kumihimo-rehype';
unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeKumihimo, { theme: "dark", onDiagnostics: report })
.use(rehypeStringify);There are React, Vue and Astro components too. Compiling is asynchronous, so the previous diagram stays on screen while a new one is produced, and a slow earlier compile can never overwrite a newer one.
import { Kumihimo, useKumihimo } from '@love-rox/kumihimo-react';
<Kumihimo source={src} theme="dark" onDiagnostics={report} />;
const { svg, diagram, diagnostics, pending, error } = useKumihimo(src);Use onDiagnostics if you would rather not publish a nice-looking picture of faulty wiring.