dek 0.1.40

Declarative environment setup and scripted workflows from TOML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
# dek

Declarative environment setup. One TOML, any machine.

## Install

```bash
cargo install dek
# or
cargo binstall dek

# setup completions
dek setup
```

## Usage

```bash
dek apply              # apply ./dek.toml or ./dek/
dek check              # dry-run, show what would change
dek plan               # list items (no state check)
dek run <name>         # run a command from config
dek state              # query system state probes
dek test               # test in container
dek exec <cmd>         # run command in test container
dek bake               # bake into standalone binary
```

All commands have short aliases: `a`pply, `c`heck, `p`lan, `r`un, `s`tate, `t`est, `dx` (exec).

Config is loaded from: `./dek.toml`, `./dek/`, or `$XDG_CONFIG_HOME/dek/` (fallback).

## Config

```toml
# Packages
[package.os]  # auto-detects: pacman, apt, brew
items = ["curl", "git", "htop"]

[package.apt]
items = ["build-essential"]

[package.pacman]  # falls back to yay for AUR packages
items = ["base-devel", "yay"]

[package.cargo]
items = ["bat", "eza", "ripgrep"]

[package.go]
items = ["github.com/junegunn/fzf@latest"]

[package.npm]
items = ["prettier", "typescript"]

[package.pip]
items = ["httpie", "tldr"]

[package.pipx]
items = ["poetry", "black"]

[package.webi]
items = ["jq", "yq"]

# Systemd services
[[service]]
name = "docker"
state = "active"
enabled = true

# User services (systemctl --user, no sudo)
[[service]]
name = "syncthing"
state = "active"
enabled = true
scope = "user"

# Files
[file.copy]
"dotfiles/.zshrc" = "~/.zshrc"

[file.fetch]
"https://raw.githubusercontent.com/user/repo/main/.bashrc" = "~/.bashrc"
"https://example.com/config.json" = { path = "~/.config/app/config.json", ttl = "1h" }

[file.symlink]
"~/dotfiles/nvim" = "~/.config/nvim"

[file.ensure_line]
"~/.bashrc" = ["export PATH=$HOME/.local/bin:$PATH"]

# Structured line management
[[file.line]]
path = "/etc/needrestart/needrestart.conf"
line = "$nrconf{restart} = 'l';"
original = "#$nrconf{restart} = 'i';"
mode = "replace"

[[file.line]]
path = "/etc/ssh/sshd_config"
line = "PermitRootLogin no"
original_regex = "^#?PermitRootLogin\\s+"
mode = "replace"

# Shell
[alias]
la = "ls -larth"
g = "git"

[env]
EDITOR = "nvim"

# System
timezone = "Europe/Istanbul"
hostname = "workstation"

# Scripts (installed to ~/.local/bin)
[script]
cleanup = "scripts/cleanup.sh"

# Custom commands
[[command]]
name = "setup-db"
check = "psql -c 'SELECT 1 FROM pg_database WHERE datname=mydb'"
apply = "createdb mydb"

# Assertions
[[assert]]
name = "dotty up to date"
check = "git -C ~/dotty fetch -q && test $(git -C ~/dotty rev-list --count HEAD..@{upstream}) -eq 0"
message = "dotty has remote changes"

[[assert]]
name = "note conflicts"
foreach = "rg --files ~/Sync/vault 2>/dev/null | grep conflict | sed 's|.*/||'"

[[assert]]
name = "stow"
foreach = "for p in common nvim tmux; do stow -d ~/dotty -n -v $p 2>&1 | grep -q LINK && echo $p; done"
```

## File Fetch

Download files from URLs. Results are cached at `~/.cache/dek/url/`. Use `ttl` to control cache expiry:

```toml
[file.fetch]
# Cache forever (re-fetches only when cache is cleared)
"https://raw.githubusercontent.com/user/repo/main/.bashrc" = "~/.bashrc"

# Cache for 1 hour — re-fetches if older
"https://example.com/config.json" = { path = "~/.config/app/config.json", ttl = "1h" }
```

Supported TTL units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days). Can be combined: `1h30m`.

## Vars

Runtime variables defined in `meta.toml`, set in the process environment before anything runs. Available to all providers, commands, scripts — locally and remotely.

```toml
# meta.toml
[vars]
APP_NAME = "myapp"
DEPLOY_DIR = "/opt/default"

# Scoped by @label — applied when that label is selected
[vars."@staging"]
DEPLOY_DIR = "/opt/staging"
DB_HOST = "staging-db"

[vars."@production"]
DEPLOY_DIR = "/opt/production"
DB_HOST = "prod-db"

# Scoped by config key
[vars.deploy]
NOTIFY = "true"
```

Base vars are always set. Scoped vars overlay when their selector is active:

```bash
dek apply @staging    # APP_NAME=myapp DEPLOY_DIR=/opt/staging DB_HOST=staging-db
dek apply @production # APP_NAME=myapp DEPLOY_DIR=/opt/production DB_HOST=prod-db
dek apply deploy      # APP_NAME=myapp DEPLOY_DIR=/opt/default NOTIFY=true
```

Vars are inherited by all child processes, so `[[command]]` check/apply, `[script]`, and remote `dek apply` all see them.

## Cache Key

Skip steps when a value hasn't changed since last successful apply. Works on `[[command]]`, `[[service]]`, and `[[file.line]]`.

**`cache_key`** — a string value (supports `$VAR` expansion):

```toml
[[command]]
name = "generate test data"
check = "test -f /opt/test/input.csv"
apply = "generate-data.sh"
cache_key = "$INPUT_FILE_SIZE_MB"  # only re-runs when size changes
```

**`cache_key_cmd`** — a command whose stdout is the cache key:

```toml
[[command]]
name = "deploy jar"
check = "test -f /opt/dpi/jar/dpi.jar"
apply = "cp build/dpi.jar /opt/dpi/jar/"
cache_key_cmd = "sha256sum build/dpi.jar"  # only re-deploys when jar changes
```

Cache state is stored in `~/.cache/dek/state/`. The provider's `check` always runs — if the state is missing (e.g. file deleted), apply runs regardless of cache. When check passes and the cache key is unchanged, apply is skipped. When the cache key changes (e.g. a `$VAR` in `meta.toml` was updated), apply re-runs even if check still passes — this lets you force re-apply by changing a var.

## Assertions

Assertions are check-only items — they report issues but don't change anything. Two modes:

**check** — pass if command exits 0:

```toml
[[assert]]
name = "docker running"
check = "docker info >/dev/null 2>&1"
message = "docker daemon is not running"
stdout = "some regex"  # optional: also match stdout
```

**foreach** — each stdout line is a finding (zero lines = pass):

```toml
[[assert]]
name = "stow packages"
foreach = "for p in common nvim; do stow -n -v $p 2>&1 | grep -q LINK && echo $p; done"
```

In `dek check`, assertions show as `✓`/`✗`. In `dek apply`, failing assertions show as issues (not "changed") and don't block other items.

## Conditional Execution

Any item supports `run_if` — a shell command that gates execution (skip if non-zero):

```toml
[package.pacman]
items = ["base-devel"]
run_if = "command -v pacman"

[[assert]]
name = "desktop stow"
run_if = "echo $(uname -n) | grep -qE 'marko|bender'"
foreach = "..."

[meta]
run_if = "test -d /etc/apt"  # skip entire config file
```

## Package:Binary Syntax

When package and binary names differ:

```toml
[package.cargo]
items = ["ripgrep:rg", "fd-find:fd", "bottom:btm"]
```

Installs `ripgrep`, checks for `rg` in PATH.

## Split Config

```
dek/
├── meta.toml           # project metadata + defaults
├── banner.txt          # optional banner (shown on apply/help)
├── inventory.ini       # remote hosts (one per line)
├── 00-packages.toml
├── 10-services.toml
├── 20-dotfiles.toml
└── optional/
    └── extra.toml      # only applied when explicitly selected
```

Files merged alphabetically. Use `dek apply extra` to include optional configs.

### meta.toml

```toml
name = "myproject"
description = "Project deployment"
version = "1.0"
min_version = "0.1.28"               # auto-update dek if older
defaults = ["@setup", "@deploy"]     # default selectors for apply
inventory = "../devops/inventory.ini" # custom inventory path
remote_install = true                # symlink dek + config on remote hosts
bin_name = "mytool"                  # binary symlink name on remote (default: "dek")

# Hide sections from the welcome screen
# values: "usage", "commands", "options", "configs", "run", "powered", "powered_url"
hide = ["commands", "options", "usage", "powered_url"]

# Custom entries shown as COMMANDS section on the welcome screen
[[welcome]]
name = "deploy"
description = "Deploy to production"

[[welcome]]
name = "status"
description = "Check service status"

[test]
image = "ubuntu:22.04"
keep = true
mount = ["./data:/opt/data"]         # bind mounts for test container
```

When `name` is set, the welcome screen shows a "Powered by dek" line — useful for branded tools deployed via `remote_install`.

### Labels & Selectors

Tag configs with labels for grouped selection:

```toml
# 10-deps.toml
[meta]
name = "Dependencies"
labels = ["setup"]

[package.os]
items = ["curl", "git"]
```

```toml
# 20-deploy.toml
[meta]
name = "Deploy"
labels = ["deploy"]

[file.copy]
"app.jar" = "/opt/app/app.jar"
```

```bash
dek apply @setup          # only configs labeled "setup"
dek apply @deploy         # only configs labeled "deploy"
dek apply @setup tools    # @label refs and config keys can be mixed
dek apply                 # uses meta.toml defaults (or all main configs if no defaults)
```

When `defaults` is set in `meta.toml`, a bare `dek apply` applies only those selectors. Without `defaults`, it applies all non-optional configs (backward compatible).

## Run Commands

Define reusable commands:

```toml
[run.deploy]
description = "Deploy the application"
deps = ["os.rsync"]
cmd = "rsync -av ./dist/ server:/var/www/"

[run.backup]
description = "Backup database"
script = "scripts/backup.sh"  # relative to config dir

[run.restart]
cmd = "systemctl restart myapp"
confirm = true                 # prompt before running

[run.logs]
cmd = "journalctl -fu myapp"
tty = true                     # interactive, uses ssh -t
```

```bash
dek run              # list available commands
dek run deploy       # run command
dek run backup arg1  # args passed via $@
```

### Remote Run

Run commands on remote hosts without deploying dek — just SSH the command directly:

```bash
dek run restart -t server1        # single host
dek run restart -r 'app-*'        # multi-host (parallel)
dek run logs -t server1           # tty command (interactive)
```

- **`-t`** — single host, prints output directly. With `tty: true`, uses `ssh -t` for interactive commands.
- **`-r`** — multi-host from inventory, runs in parallel with progress spinners. `tty: true` commands are rejected (can't attach TTY to multiple hosts).
- **`confirm: true`** — prompts `[y/N]` before running (works both locally and remotely).
- **Vars** — base vars from `meta.toml` `[vars]` are exported to the remote shell automatically, so `$VAR` references in remote commands resolve correctly.

## Shell Library

Put shared shell functions in `data/functions.sh` under your config directory. dek automatically sources it before every `cmd`, `check`, `apply`, `run`, and `assert` script:

```
~/.config/dek/
  data/
    functions.sh   ← sourced automatically
  10-tools.toml
  20-apps.toml
```

```bash
# data/functions.sh
is_laptop() { [ "$(uname -n)" = "thinkpad" ]; }
has_display() { [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; }
```

```toml
[[command]]
name = "laptop-brightness"
check = "is_laptop && has_display && cat /sys/class/backlight/*/brightness | grep -q ."
apply = "is_laptop && brightnessctl set 50%"

[[state]]
name = "laptop"
cmd = "is_laptop && echo yes || echo no"
```

## Remote

Apply to remote hosts via SSH:

```bash
dek apply -t user@host
dek check -t server1
```

Use `-q`/`--quiet` to suppress banners (auto-enabled for multi-host). Use `--color always|never|auto` to control colored output.

### Multi-host with Inventory

Ansible-style `inventory.ini` (one host per line, `[groups]` and `;comments` ignored):

```ini
# inventory.ini
[web]
web-01
web-02
web-03

[db]
db-master
```

```bash
dek apply -r 'web-*'    # glob pattern (-r is short for --remotes)
dek apply -r '*'         # all hosts
```

Hosts are deployed in parallel. Override inventory path in `meta.toml`:

```toml
inventory = "../devops/inventory.ini"
```

### Remote Install

With `remote_install = true` in `meta.toml`, dek symlinks itself and the config on remote hosts after deploy:

```
~/.cache/dek/remote/dek      ← binary (persists across reboots)
~/.cache/dek/remote/config/  ← config

~/.config/dek          → ~/.cache/dek/remote/config/
/usr/local/bin/dek     → ~/.cache/dek/remote/dek   (root)
~/.local/bin/dek       → ~/.cache/dek/remote/dek   (non-root)
```

Use `bin_name` to deploy as a custom-named tool:

```toml
# meta.toml
name = "recover"           # display name (shown in welcome screen)
remote_install = true
bin_name = "recover"       # binary symlink name on remote
```

After deploy: `recover apply`, `recover run logs`, etc.

This lets you run `dek` directly on the remote (e.g. `dek apply`, `dek run`) without re-deploying. Re-deploying updates the cached binary and config in-place, so the symlinks stay valid.

### Deploy Workflow

Use `[[artifact]]` to build locally before shipping to remotes or baking:

```toml
[[artifact]]
name = "app.jar"
build = "mvn package -DskipTests -q"
watch = ["src", "pom.xml"]              # skip build if unchanged
src = "target/app-1.0.jar"              # build output
dest = "artifacts/app.jar"              # placed in config for shipping

[file.copy]
"artifacts/app.jar" = "/opt/app/app.jar"

[[service]]
name = "app"
state = "active"
```

```bash
dek apply -r 'app-*'
# 1. Builds artifact locally (skips if watch hash unchanged)
# 2. Packages config + artifact into tarball
# 3. Ships to all app-* hosts in parallel
# 4. Copies jar, restarts service

dek bake -o myapp
# Artifact is built and included in the baked binary
```

Artifacts are resolved before any config processing — they work with `apply`, `apply -r`, and `bake`.

Freshness can be determined two ways:
- **`watch`** — list of files/directories to hash (path + size + mtime). Build is skipped when the hash matches the previous run. Best for source trees.
- **`check`** — shell command that exits 0 if the artifact is fresh. Use for custom logic (e.g., `test target/app.jar -nt pom.xml`).

**`deps`** — local dependencies needed before build. Ensures build tools exist on the machine running the build:

```toml
[[artifact]]
name = "app.jar"
build = "mvn package -DskipTests -q"
deps = ["apt.default-jdk:java", "apt.maven:mvn"]
src = "target/app-1.0.jar"
dest = "artifacts/app.jar"
```

Format: `"package:binary"` — installs `package` if `binary` isn't in PATH. Prefix with package manager (`apt.`, `pacman.`, `brew.`) to force a specific one, or omit for auto-detection (`os.`).

## Auto Update

Set `min_version` in `meta.toml` to ensure all users/hosts run a compatible version:

```toml
min_version = "0.1.28"
```

If the running dek is older, it auto-updates via `cargo-binstall` (preferred) or `cargo install`, then exits with a prompt to rerun.

## Inline

Quick installs without a config file:

```bash
dek os.htop os.git cargo.bat
dek pip.httpie npm.prettier
```

Group multiple packages from the same provider with commas:

```bash
dek pacman.grim,slurp,tesseract
dek cargo.bat,eza,ripgrep
```

## Test

Bakes config into the binary and runs it in a container. The baked `dek` inside the container is fully functional — `apply`, `list`, `run` all work.

```bash
dek test                     # bake + create container + apply + shell
dek test @core               # only apply @core labeled configs
dek test -i ubuntu tools     # custom image + specific configs
dek test                     # (second run) rebake + apply + shell (container kept)
dek test -f                  # force new container (remove + recreate)
dek test -a                  # attach to running container (no rebuild)
dek test -r                  # remove container after exit
```

Containers are kept by default and named `dek-test-{name}` (from `meta.toml` name or directory). On subsequent runs, dek rebakes the binary, copies it into the existing container, reapplies config, and drops into a shell — installed packages and files persist.

### Exec

Run commands directly in the test container:

```bash
dek exec ls /opt/app         # run a command
dek dx cat /etc/os-release   # dx is a short alias
dek dx dek run version       # run dek commands inside
dek dx dek list              # list configs in container
```

Configure defaults in `meta.toml`:

```toml
[test]
image = "ubuntu:22.04"
mount = ["./data:/opt/data", "/host/path:/container/path"]
```

Mounts are bind-mounted into the test container. Relative host paths are resolved against the config directory.

CLI flags override meta.toml (`-i/--image`, `-r/--rm`).

## Completions

Dynamic completions for configs, @labels, and run commands.

### Manual

```bash
dek setup              # auto-detect shell, install completions
dek completions zsh    # raw output (pipe to file yourself)
```

### Via dek config

Add to any config (e.g., `15-shell.toml`):

```toml
[[command]]
name = "dek completions"
cmd = "dek setup"
check = "dek _complete check"
```

Completions support all aliases (`a`, `c`, `p`, `r`, `t`, `dx`) and dynamically complete config keys, `@labels`, and run command names from whatever config is in the current directory.

## State

Query system state via shell commands with optional rewrite rules, named templates, and dependencies. Probes run in parallel (respecting dependency order).

```toml
[[state]]
name = "machine"
cmd = "uname -n"

[[state]]
name = "screen"
cmd = "hyprctl -j monitors | jq -r '.[].description'"
ttl = "1h"   # cache probe output for 1 hour
rewrite = [
  {match = "Samsung.*0x01000E00", value = "tv"},
  {match = "C49RG9x", value = "ultrawide"},
]
templates = { short = "{{ raw[:2] }}", icon = "{% if raw == 'tv' %}T{% else %}U{% endif %}" }

[[state]]
name = "hour"
cmd = "date +%H"
rewrite = [
  {match = "^(2[0-3]|0[0-7])$", value = "night"},
  {match = ".*", value = "day"},
]

# Computed state — no cmd, just deps + templates
[[state]]
name = "summary"
deps = ["machine", "screen", "hour"]
templates = { default = "{{ machine.raw }}/{{ screen.raw }}/{{ hour.raw }}" }
```

Rewrite rules are checked in order against raw stdout. First regex match wins, output replaced with `value`. No match = raw output. When a rewrite matches, the pre-rewrite value is preserved as `original`.

### Templates

Named Jinja templates rendered after cmd+rewrite. Context includes `raw`, `original`, and all dependency values (`dep.raw`, `dep.original`, `dep.<template>`).

The `fromjson` filter parses JSON strings into objects for field access:

```toml
[[state]]
name = "weather"
cmd = "curl -s 'https://api.example.com/weather'"
ttl = "30m"
templates.short = "{{ (raw | fromjson).text }}"
templates.long = "{{ (raw | fromjson).tooltip }}"
```

### TTL

Cache slow probe commands so they don't re-run every time. Cached output is stored in `~/.cache/dek/url/` and reused until the TTL expires. The raw command output is cached (before rewrites/templates), so rewrites and templates always re-evaluate.

```toml
[[state]]
name = "screen"
cmd = "hyprctl -j monitors | jq -r '.[].description'"
ttl = "1h"   # re-run cmd only after 1 hour
```

No `ttl` = no caching (runs every time). Supported units: `s`, `m`, `h`, `d` (combinable: `1h30m`).

### Expressions

`expr` is a Jinja template rendered with dependency values to produce the raw value — an alternative to `cmd` for computed states. Rewrites apply to the result, so you can combine deps into a matchable string:

```toml
[[state]]
name = "network"
deps = ["machine", "ssid", "networktype"]
expr = "{{ machine.raw }}:{{ networktype.raw }}:{{ ssid.raw }}"
rewrite = [
  {match = "marko:ethernet", value = "home"},
  {match = "bender:ethernet", value = "ng"},
  {match = ".*:wifi:home", value = "home"},
  {match = ".*:wifi:office", value = "ng"},
  {match = ".*", value = "other"},
]
```

### Dependencies

States can depend on other states via `deps`. Dependencies are evaluated first (topologically sorted), and their results are available in templates. States without `cmd` are computed purely from deps+templates.

```bash
dek state                          # all probes, aligned key/value
dek state --json                   # nested JSON: {"screen":{"raw":"tv","original":"Samsung...","icon":"T"}}
dek state machine                  # single probe value
dek state screen.icon              # named template value
dek state screen.original          # pre-rewrite value
dek state machine screen hour      # multiple probes
dek state screen.icon is T         # operators work on any variant
dek state screen get tv ultra def  # "tv"/"ultra" pass through, else "def"
dek state summary.default          # computed from deps: "hostname/tv/night"
```

Alias: `s`. Useful in scripts:

```bash
dek s machine is marko && hyprctl dispatch ...
dek s hour is night && notify-send "go to sleep"
theme=$(dek s screen get tv ultrawide default)
icon=$(dek s screen.icon)
```

## File Templates

Render Jinja template files with state values, built-in variables, and vars files. Templates are checked/applied like any other file provider.

```toml
[[state]]
name = "screen"
cmd = "hyprctl -j monitors | jq -r '.[].description'"
ttl = "1h"
rewrite = [{match = "Samsung.*", value = "tv"}]
templates = { icon = "{% if raw == 'tv' %}T{% else %}U{% endif %}" }

[[state]]
name = "hour"
cmd = "date +%H"

[[file.template]]
src = "templates/waybar.json.j2"
dest = "~/.config/waybar/config.json"
states = ["screen", "hour"]
```

`templates/waybar.json.j2`:
```
// Generated on {{ hostname }} by {{ user }}
{
  "output": "{{ screen.raw }}",
  "icon": "{{ screen.icon }}",
  "mode": "{{ hour.raw }}"
}
```

### Template Context

**Built-ins** (always available): `hostname`, `user`, `os`, `arch`

**States** (from `states` field): each state is an object with `.raw`, `.original`, and any template variant keys (e.g. `screen.icon`).

Only states listed in `states` (and their transitive dependencies) are evaluated.

Missing variables render as empty strings (lenient mode).

### Vars Files

Load external variable files (YAML or TOML) into the template context — like Ansible's vars files. Supports nested maps, arrays, and complex structures.

**Shared vars** — available to all templates:

```toml
[file]
vars = ["vars/common.yaml", "vars/defaults.toml"]
```

**Per-template vars** — merged on top of shared vars (overrides):

```toml
[[file.template]]
src = "templates/app.conf.j2"
dest = "~/.config/app/config"
vars = ["vars/site.yaml"]
states = ["screen"]
```

File format is detected by extension: `.yaml`/`.yml` for YAML, `.toml` for TOML. All top-level keys become template variables.

Example `vars/site.yaml`:
```yaml
site_vars:
  kafka_server:
    - 169.254.0.10:9092
    - 169.254.0.11:9092
  site_id: 1
```

In templates: `{{ site_vars.site_id }}`, `{% for s in site_vars.kafka_server %}{{ s }}{% endfor %}`.

## Editor Support

### Neovim

[nvim-dek](https://github.com/zcag/nvim-dek) provides syntax highlighting for dek config files — injects bash into `cmd`/`apply`/`check`/`foreach` keys and Jinja2 into `templates`/`expr` keys.

```lua
-- lazy.nvim
{ "zcag/nvim-dek" }
```

Requires the tree-sitter TOML parser (`TSInstall toml`). Files named `*.dek.toml` are detected automatically. For other `.toml` files, add `# vim: ft=dek` anywhere in the first few lines.

## Bake

Embed config into a standalone binary:

```bash
dek bake ./dek -o mysetup
./mysetup              # show help with available configs
./mysetup apply        # apply all
./mysetup run deploy   # run commands
```