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.

shell
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 TSV

It 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.

first.khmclean
device pc  "ノートPC"  as computer { out HDMI : hdmi }
device mon "モニター" as display  { in  HDMI : hdmi }

pc.HDMI -> mon.HDMI : hdmi 5m "V-01"
output
V-01 5mノートPCHDMIモニターHDMI

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

syntax
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.

FormExampleExpands to
SingleSDISDI
ListL, RL, R
Numeric range1..41, 2, 3, 4
Prefixed rangeCH[1..16]CH1CH16
ports.khmclean
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"
output
A-10 3mA-10 3mYamaha DM3CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LRDANTEアンプIN_LIN_R

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.

gap.khmclean
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
}
output
ATEM Mini Extreme12345678AUDIO_LAUDIO_RPGMSTREAM

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

syntax
<device>.<port> <arrow> <device>.<port> : <signal> <modifier>*
ArrowMeaning
->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.

SyntaxMeaningExample
<length>cable length10m 30cm 2.5m 3ft
"<label>"cable number or name"V-01"
via "<part>"adapter or converting leadvia "HDMI-DVI cable"
[k=v, …]arbitrary attributes[connector=BNC, color=blue]
connect.khmclean
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    2m
output
A-10 3mA-10 3mN-01 10m2mLRDANTEアンプIN_LIN_RACスイッチ1電源1

When 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.

group.khmclean
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"
output
会場ステージラックV-01 30mA-01 20mFX3SDISM58OUTATEM12PGMDM3CH1CH2LR

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.

VerdictMeaningTreated as
oka normal connectionsilent
lossyworks, but something is given up or a part is neededwarning
incompatiblewill not work without active conversionreported

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.

faults.khm2 diagnostic
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"
output
N-01 20mV-90 5mHDBaseT受信器CATL2スイッチ1カメラSDIフレームシンクロナイザREF

Every verdict carries its reason, and the reason follows through into the cable schedule. The same trap exists for dmxxlr, rcaspdif, adatspdif, compositecomponent and wordclocksdi.

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
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.

CodeMeaningDefault
signal-mismatchthe two ends disagree about the signalwarning
adapter-requiredan adapter is needed but not declaredwarning
adapter-insufficientvia declared but no cable can bridge thiserror
direction-mismatchoutput to output, or input to inputerror
port-overbookedmore than one source into one inputerror
implicit-devicereferred to an undeclared devicewarning
unconnected-porta declared port wired to nothingoff

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.

via.khmclean
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変換ケーブル"
output
V-01 2m ⇄PCHDMIモニター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.

converter.khm
# 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 : hdmi

08Wireless

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.

wireless.khmclean
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"
output
ch 38A-01 3mワイヤレスマイクRF受信機RFCH1CH1

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.

library.khmclean
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 "モニター卓"
output
Yamaha DM3CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LRモニター卓CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LR

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.

studio.khm
use "lib/yamaha.khm"

device foh from dm3 "FOH desk"

10Appearance

diagram block
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
}
ThemeFor
lightDefault. Screen and colour print
darkDark screens
monoBlack and white print and photocopies
blueprintBlueprint 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.

mono.khmclean
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"
output
白黒印刷用V-01 30mV-10 2mカメラSDIスイッチャー1PGMレコーダーSDI

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.

markdown
```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.

React
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.