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 keystrokes

Default 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_Down

key-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 shift

click

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 click

mouse-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 complete

move

Move the cursor. Absolute screen coordinates by default.

move 800 400
move 50 0 relative=true     // 50px right of where we are now

scroll

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 right

Window / 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=Ghostty

wait

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=continue

Props:

  • as=name — capture stdout into a variable for later steps
  • timeout-ms — wall-clock limit. Default: no limit.
  • retries — extra attempts on failure. Default: 0.
  • backoff-ms — sleep between retries. Default: 500ms when retries > 0.
  • on-errorfail (default; stops the workflow) or continue (proceed anyway).
  • with="/bin/bash" — pick a different shell. Default: $SHELL or /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 500

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

Next

Repeat / when / unless →