kdl language
Action kinds
Every step in a workflow starts with an action kind: a verb that names what the step does. Some take positional args, some take named props, some take both.
Input
type
Type a string into whatever has focus.
type "hello world"
type "/standup"
type "hi" delay-ms=80 // slow down to 80ms between keystrokesDefault delay between keystrokes is whatever wdotool defaults to. Bump it on apps that miss keys at full speed (some Electron apps, some terminals).
key
Press a key chord. Modifiers are super, ctrl, alt, shift; joined with +. Modifier aliases like cmd, win, option normalise on parse, so paste-from-Mac chords work too.
key Return
key ctrl+s
key super+space
key shift+Page_Downkey-down / key-up
Hold or release a key without the matching pair. Used for modifier-aware drags or holding a key during another step.
key-down shift
key Tab
key Tab
key-up shiftclick
Click the mouse at the current cursor position. Pair with move to click at a specific spot. The single positional arg is the button: 1=left, 2=middle, 3=right, 8=back, 9=forward.
move 400 300
click 1 // left click at (400, 300)
click 3 // right click at the same spot
click 2 // middle clickmouse-down / mouse-up
Press or release a mouse button without the matching pair. Pair them with move steps to drag.
move 100 100
mouse-down 1 // left button down
move 200 200
mouse-up 1 // left button up — drag completemove
Move the cursor. Absolute screen coordinates by default.
move 800 400
move 50 0 relative=true // 50px right of where we are nowscroll
Scroll wheel events. Two signed integers: dx (horizontal) and dy (vertical). Positive dy scrolls down, positive dx scrolls right.
scroll 0 5 // 5 clicks down
scroll 0 -3 // 3 clicks up
scroll 5 0 // 5 clicks rightWindow / waiting
focus
Focus a window by substring match on the window name. Takes either a positional string or a window= prop.
focus Slack
focus "Mozilla Firefox"
focus window=Ghosttywait
Pause for a duration. Bare integer is milliseconds; the string form accepts ms, s, m.
wait 500 // half a second
wait 2000 // two seconds
wait "1.5s"
wait "30s"
wait "5m"wait-window
Block until a window matching the name substring appears. Useful when launching apps that take a moment to spawn. Default timeout is 5 seconds.
shell "code ."
wait-window code timeout-ms=10000
key ctrl+P
type "README.md"System
shell
Run a shell command. Captured stdout goes to a named variable via as=name; reference it later with {{name}}.
shell "git push"
shell "git rev-parse --short HEAD" as=sha
shell "long-task" timeout-ms=30000 retries=2
shell "best-effort" on-error=continueProps:
as=name— capture stdout into a variable for later stepstimeout-ms— wall-clock limit. Default: no limit.retries— extra attempts on failure. Default: 0.backoff-ms— sleep between retries. Default: 500ms whenretries > 0.on-error—fail(default; stops the workflow) orcontinue(proceed anyway).with="/bin/bash"— pick a different shell. Default:$SHELLor/bin/sh.
notify
System notification via notify-send. Title is positional; body is the only other prop.
notify "Done"
notify "Pushed" body="branch went up clean"
notify "Build failed" body="check the journal"Urgency, hints, and icon overrides aren't exposed yet. If you need them, drop to shell "notify-send -u critical ...".
clipboard
Write a string to the clipboard via wl-copy. Read-from-clipboard is a shell step (wl-paste with as=).
clipboard "https://wflows.io"
clipboard "{{sha}}"
// Read into a variable:
shell "wl-paste" as=clipped
type "{{clipped}}"Annotations
note
Inline note that renders in the step trail on the workflow detail page and in the canvas editor. Useful for documenting intent inside a complex workflow without adding a KDL // comment (which encoders strip on round-trip).
note "open Slack first"
focus Slack
note "wait for it to settle"
wait 500Control flow
Three block-shaped steps: repeat, when, and unless. Detail on the control-flow page.
Imports + use
imports declares fragment files (step lists without a workflow wrapper); use NAME splices the fragment in as inline steps. Resolved at decode time. See KDL basics for the example.
Where this stops
The action set is intentionally small. wflow isn't trying to be AutoHotkey or AutoIt — it's trying to be a clean Wayland-native chord-to-actions binding layer. If you need behavior that isn't in this list, the answer is usually "shell out and let the OS do it." Want JSON parsing? shell "jq ...". Want HTTP? shell "curl ...". wflow stitches; the OS does the heavy lifting.