choreographr 0.2.1

Agentic coding assistant — daemon, TUI, and bridges
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
# Choreographr — `just` task runner
#
# One-stop entry point for building, testing, linting, running, and releasing
# the workspace. Bare `just` lists all recipes.
#
# Requirements:
#   - cargo          Rust toolchain >= 1.94.1 (the workspace MSRV — see Cargo.toml)
#   - zig            builds `zlob`, the glob/walker dependency of choreo-daemon
#                    (install with `brew install zig` on macOS, `apt install zig`
#                    on Debian/Ubuntu, or from ziglang.org)
#   - cargo-nextest  optional but recommended: the primary test runner
#                    (install once with `just install-nextest`; every nextest-backed
#                    recipe fails with a hint until it is on PATH)
#
# The recipes mirror the README "Testing & development" section and the
# AGENTS.md pre-commit workflow (fmt + clippy + full test suite).

set shell := ["bash", "-euo", "pipefail", "-c"]

# ── configuration ─────────────────────────────────────────────────────────────

# Cargo build profile used by `build`, `check`, and the `run-*` recipes.
# Defaults to release (matching the README quick start); override for faster
# local iteration with: `just --set profile debug build`
profile := "release"

# Extra flags appended to every cargo invocation. Read from the environment so
# CI can inject e.g. `CARGO_FLAGS="--offline"` without editing this file.
CARGO_FLAGS := env_var_or_default("CARGO_FLAGS", "")

# ── entry points ──────────────────────────────────────────────────────────────

# Show all recipes (default — run with bare `just`)
default:
    @just --list

# Show all recipes (alias of `default`)
help:
    @just --list

# Verify the toolchain: cargo + zig required, cargo-nextest recommended
preflight:
    @echo "==> checking toolchain"
    @command -v cargo >/dev/null 2>&1 || { echo "error: cargo not found — install Rust via rustup (https://rustup.rs/)" >&2; exit 1; }
    @command -v zig >/dev/null 2>&1 || { echo "error: zig not found — install it (choreo-daemon's zlob dependency needs it)" >&2; exit 1; }
    @command -v cargo-nextest >/dev/null 2>&1 || echo "note: cargo-nextest not found (recommended — run \`just install-nextest\`)"
    @echo "==> toolchain OK: cargo $(cargo --version | cut -d' ' -f2) · zig $(zig version)"

# Install the primary test runner (cargo-nextest). `brew install nextest` on macOS.
install-nextest:
    cargo install cargo-nextest

# ── hidden prerequisites ──────────────────────────────────────────────────────

# Fail fast with a hint when zig is missing. zig builds the zlob glob/walker
# dependency of choreographr, so every compile/test recipe needs it.
_require-zig:
    @command -v zig >/dev/null 2>&1 || { echo "error: zig not found — run \`brew install zig\` (choreo-daemon's zlob dependency needs it)" >&2; exit 1; }

# Fail fast with a hint when cargo-nextest is missing. The test-* recipes are
# the README's primary runner; failing here beats cargo's cryptic "no such
# command: test-fast".
_require-nextest:
    @command -v cargo-nextest >/dev/null 2>&1 || { echo "error: cargo-nextest not found — run \`just install-nextest\`" >&2; exit 1; }

# ── build & check ─────────────────────────────────────────────────────────────

# Build the whole workspace with `profile` (release by default)
build: _require-zig
    cargo build {{ CARGO_FLAGS }} --workspace --profile "{{ profile }}"

# Build the whole workspace in debug mode (fast local iteration)
build-debug: _require-zig
    cargo build {{ CARGO_FLAGS }} --workspace --profile debug

# ── stable builds (escape hatch from the nightly default) ─────────────────────
# The default toolchain is NIGHTLY (see rust-toolchain.toml), so every adhoc
# `cargo` command — including per-crate `cargo build -p choreo-x` / `cargo
# check -p x` / `cargo nextest run -p x` — automatically applies the fast
# per-profile `-Z` flags via [unstable] profile-rustflags (see .cargo/config.toml).
# Those same nightly-only bits HARD-BLOCK stable Cargo, so a stable build is an
# explicit opt-out: these recipes run through scripts/build-stable.sh, which
# temporarily strips the nightly-only config/manifest keys for one command and
# restores them afterwards. Stable compatibility itself is guaranteed by the
# code (no nightly features) and the CI MSRV job, independent of this config.

# Stable build (the `profile` — release by default — plus any extra args)
build-stable: _require-zig
    ./scripts/build-stable.sh build --workspace --profile "{{ profile }}" {{ CARGO_FLAGS }}

# Stable type-check (all targets, no linking)
check-stable: _require-zig
    ./scripts/build-stable.sh check --workspace --all-targets {{ CARGO_FLAGS }}

# Stable unit tests (libtest, serialized — no nextest needed)
test-stable: _require-zig
    ./scripts/build-stable.sh test --workspace {{ CARGO_FLAGS }}

# Publish the 14 crates to crates.io (RELEASE.md Phase 2) with the nightly-only
# per-profile rustflags stripped, so published manifests stay buildable by
# stable consumers (`cargo install choreographr --locked`). Runs
# `cargo release publish --workspace` (dry-run by default; add `-- -x` to
# execute, or pass any other cargo-release args after `--`; add `-- --allow-dirty`
# to skip the wrapper's clean-tree gate — see scripts/publish-stable.sh).
publish-stable args="publish --workspace":
    ./scripts/publish-stable.sh {{ args }}

# No linking, but zig is still required (resolution runs zlob's build script).
# Type-check the whole workspace (all targets) — the fastest CI signal.
check: _require-zig
    cargo check {{ CARGO_FLAGS }} --workspace --all-targets

# macOS cross-compile gate (works from Linux or macOS): type-check every library
# crate for aarch64-apple-darwin using zig's clang+lld via cargo-zigbuild's
# `check` subcommand. Note the direct `cargo-zigbuild` invocation — `cargo
# zigbuild check` would misroute to the build subcommand, because cargo passes
# the subcommand name through (cargo-zigbuild 0.23+).
#
# Libs only, deliberately: the final Mach-O binary link needs the Apple SDK,
# which cannot be redistributed (it fails with "unable to find framework
# 'CoreFoundation'"), so real macOS binaries are built on a macOS host in CI.
# `cargo check` skips linking, so this gate passes — but C build scripts (ring,
# zlob, ckb-vm, onig) DO run, so target-specific C breakage is caught too.
#
# Do NOT add `--all-features`: the `blockchain` feature pulls subxt →
# native-tls → security-framework-sys, whose bindgen step reads Apple's
# Security.framework headers (SDK territory — fails from Linux).
check-macos: _require-zig
    rustup target add aarch64-apple-darwin
    cargo-zigbuild check {{ CARGO_FLAGS }} --target aarch64-apple-darwin --workspace --lib

# Windows cross-compile gate: type-check every library crate for
# x86_64-pc-windows-gnu via zig (MinGW bundled — no mingw install needed).
# Same libs-only rationale as check-macos; this is the recipe to iterate the
# Windows port against. A clean build also surfaces the zlob archive-naming
# quirk (zig emits `zlob.lib`, the windows-gnu target wants `libzlob.a`).
check-windows: _require-zig
    rustup target add x86_64-pc-windows-gnu
    cargo-zigbuild check {{ CARGO_FLAGS }} --target x86_64-pc-windows-gnu --workspace --lib

# ── Android ───────────────────────────────────────────────────────────────────
# Termux is the Android runtime for the four suite binaries: they are pushed
# into Termux's $PREFIX/bin as plain executables. choreo-gui is different —
# on Android it is built by `dx` as a cdylib (APK payload), not a suite binary.

# Cross-build the four suite binaries for aarch64-linux-android via
# cargo-ndk. Requires an Android NDK (ANDROID_NDK_HOME / sdkmanager layout);
# add `emulator=true` for an x86_64 build too. See scripts/build-android.sh.
android-binaries *args="":
    ./scripts/build-android.sh {{ args }}

# Prerequisite check + dry run for `android-binaries` — validates cargo-ndk,
# the NDK, and the rustup Android targets, then prints what would run without
# touching the tree. This is the script's verification path (no NDK needed to
# at least see it fail with an actionable message).
android-check:
    ./scripts/build-android.sh --check

# Build choreo-gui for Android via Dioxus CLI (NOT part of build-android.sh —
# different toolchain: dx drives the Android Gradle/cdylib packaging itself).
# `dx` infers the native renderer on Android; requires the NDK for the final
# link. `--package` scopes the build to the choreo-gui crate (dx reads the
# workspace root; the Dioxus config lives in the crate).
#
# Self-sufficient: the `android` CLI (cmdline-tools 23+) replaces the old
# sdkmanager and auto-accepts licenses, so any SDK packages gradle wants
# (platform, build-tools) are fetched here on first run instead of failing
# mid-build. Root-requiring setup (the ANDROID_HOME/ndk symlink for the AUR
# standalone-NDK layout) is NOT attempted here — one-time setup is validated
# below with exact instructions; everything else runs as the normal user.
#
# JDK note: the dx-generated Gradle build (Gradle 9.1) cannot run on JDK 26+
# — its Groovy build-script compiler rejects class file major version 70
# ("Unsupported class file major version 70"). JAVA_HOME is therefore
# normalized to a supported JDK the same way ANDROID_HOME/ANDROID_NDK_HOME
# are: only when the caller has not already set it, never overriding an
# explicit choice. Preference order: JAVA_HOME → JDK 21 (LTS) → JDK 25 →
# whatever `java` resolves to (with a warning, since a too-new default is
# exactly the failure mode above).
gui-android args="":
    #!/usr/bin/env bash
    set -euo pipefail
    # Read-only env normalization (see build-android.sh): dx/gradle only look
    # for the NDK under ANDROID_HOME/ndk, never via ANDROID_NDK_HOME.
    if [ -z "${ANDROID_NDK_HOME:-}" ] && [ -d /opt/android-ndk ]; then
        export ANDROID_NDK_HOME=/opt/android-ndk
    fi
    if [ -z "${ANDROID_HOME:-}" ] && [ -d "$HOME/Android/Sdk" ]; then
        export ANDROID_HOME="$HOME/Android/Sdk"
    elif [ -z "${ANDROID_HOME:-}" ] && [ -d /opt/android-sdk ]; then
        export ANDROID_HOME=/opt/android-sdk   # read-only fallback; SDK installs need a writable ANDROID_HOME
    fi
    # Normalize JAVA_HOME to a Gradle-9.1-compatible JDK (see the comment
    # above): explicit JAVA_HOME wins, then the known JDK install roots, then
    # the PATH default with a warning. Exported so gradle (a JVM process dx
    # spawns) and the dx JVM toolchain resolution both inherit it.
    if [ -z "${JAVA_HOME:-}" ]; then
        for jdk in /usr/lib/jvm/java-21-openjdk /usr/lib/jvm/java-25-openjdk \
                   /usr/lib/jvm/java-17-openjdk; do
            if [ -x "$jdk/bin/java" ]; then
                export JAVA_HOME="$jdk"
                break
            fi
        done
        if [ -z "${JAVA_HOME:-}" ]; then
            echo "warning: no JAVA_HOME set and no known JDK found under /usr/lib/jvm; gradle will use the default java (must be JDK ≤ 25 — newer JDKs fail with 'Unsupported class file major version 70')" >&2
        fi
    fi
    [ -n "${ANDROID_HOME:-}" ] || { echo "error: no Android SDK found (set ANDROID_HOME)" >&2; exit 1; }
    [ -n "${ANDROID_NDK_HOME:-}" ] || { echo "error: no Android NDK found (set ANDROID_NDK_HOME)" >&2; exit 1; }
    # Validate (read-only) that the NDK the build will actually use exists.
    # dx resolves the NDK via ANDROID_NDK_HOME alone (verified empirically) and
    # does NOT require the ANDROID_HOME/ndk/<version> convention, so requiring
    # that symlink would wrongly reject valid setups like /opt/android-ndk.
    [ -e "$ANDROID_NDK_HOME/source.properties" ] || {
        echo "error: $ANDROID_NDK_HOME does not look like an NDK (no source.properties)" >&2
        exit 1
    }
    # Suppress user-level cargo config rustflags for the Android codegen, the
    # same way scripts/build-android.sh does: ~/.cargo/config.toml typically
    # sets [build] rustflags = -C target-cpu=native (host-CPU flags that poison
    # Android codegen and can even SEGFAULT nightly LLVM on aarch64). When
    # RUSTFLAGS is set, cargo ignores build/target rustflags from config files
    # entirely; an empty-string override is treated as unset and --config
    # build.rustflags=[] does not reliably win, so exporting RUSTFLAGS is the
    # only dependable suppression. Exported (not per-command) so it also covers
    # any nested cargo invocation dx makes.
    export RUSTFLAGS="-C target-cpu=generic"
    # Pre-fetch the SDK packages gradle/dx need if the `android` CLI is
    # available (best effort — if dx wants a different version it will say so
    # and the user can `android sdk install` it explicitly).
    if command -v android >/dev/null 2>&1 && [ -n "${ANDROID_HOME:-}" ]; then
        android sdk install platforms/android-35 build-tools/35.0.0 || \
            echo "warning: could not pre-install SDK packages; dx will report exactly what it needs" >&2
    fi
    exec dx build --platform android --release --package choreo-gui {{ args }}

# ── testing ───────────────────────────────────────────────────────────────────

# Full suite — unit + integration — via nextest in one pass (alias of `test-all`)
test: test-all

# Unit tests via nextest (parallel, every test in its own process)
test-fast: _require-nextest _require-zig
    cargo test-fast

# Unit tests with every optional feature off (the default config) — compiles
# the metrics no-op stubs and the feature-off `--metrics-addr` refusal that the
# `--all-features` recipes never build
# (see .cargo/config.toml for why this guards against stub drift)
test-lean: _require-nextest _require-zig
    cargo test-lean

# Integration tests — the #[ignore] suite — via nextest
test-integration: _require-nextest _require-zig
    cargo test-integration

# Everything: unit + integration via nextest in one pass.
# `--locked` keeps the committed Cargo.lock authoritative on the gate path (CI
# and pre-commit): a stale/regenerated lockfile fails instead of silently
# re-resolving against the live registry (see release.sh for the same control).
test-all: _require-nextest _require-zig
    cargo test-all {{ CARGO_FLAGS }} --locked

# Unit tests via libtest (serialized across binaries; no nextest required)
test-libtest: _require-zig
    cargo test {{ CARGO_FLAGS }} --workspace

# Integration tests via libtest (the #[ignore] suite; no nextest required)
test-libtest-ignored: _require-zig
    cargo test {{ CARGO_FLAGS }} --workspace -- --ignored

# Note: the cargo test-* aliases bake in `--workspace` and reject `-p`, so this
# calls nextest directly (see README).
# Test a single crate with nextest (e.g. `just test-crate choreo-proto`)
test-crate crate: _require-nextest _require-zig
    cargo nextest run {{ CARGO_FLAGS }} -p {{ crate }}

# Run doc tests across the workspace
test-doc: _require-zig
    cargo test {{ CARGO_FLAGS }} --workspace --doc

# Run a shard of the nextest suite (CI parallelism). e.g. `just shard 1/2`
shard part: _require-nextest _require-zig
    cargo nextest run {{ CARGO_FLAGS }} --workspace --partition count:{{ part }}

# Re-run flaky nextest tests up to N times. e.g. `just retry 2`
retry n="2": _require-nextest _require-zig
    cargo nextest run {{ CARGO_FLAGS }} --workspace --retries {{ n }}

# ── lint & format ─────────────────────────────────────────────────────────────

# Format all code in place (rustfmt)
fmt:
    cargo fmt {{ CARGO_FLAGS }} --all

# Check formatting without modifying files (CI gate)
fmt-check:
    cargo fmt {{ CARGO_FLAGS }} --all -- --check

# Lint the whole workspace, all targets. `--locked` = no silent lockfile reuse.
clippy: _require-zig
    cargo clippy {{ CARGO_FLAGS }} --locked --workspace --all-targets

# Lint with warnings denied — the CI-grade gate (stricter than `clippy`)
clippy-strict: _require-zig
    cargo clippy {{ CARGO_FLAGS }} --locked --workspace --all-targets -- -D warnings

# Supply-chain gate: deny.toml bans (the 2026-08-20 arrayref@0.3.10 attacker
# versions — RUSTSEC-2026-0260 — plus the six deleted payload crates), RustSec
# advisories, crates.io-only sources, and a local registry-cache scan for the
# deleted malicious .crate files. See scripts/check-supply-chain.sh.
check-supply-chain:
    ./scripts/check-supply-chain.sh

# Release-name drift guard: `choreo-proto/release-name.txt` (the machine source
# of truth baked into the binaries and read by CI for the release title) must
# match the `(Name)` on the current version's CHANGELOG heading. See
# scripts/check-release-name.sh.
check-release-name:
    ./scripts/check-release-name.sh

# CHANGELOG structure guard: every `## [X.Y.Z]` section may carry each Keep a
# Changelog category (Added/Changed/Deprecated/Removed/Fixed/Security) AT MOST
# ONCE, with no empty blocks. The release job extracts the version's section
# verbatim as the release body, so a duplicated `### Fixed` would ship as two
# Fixed sections on the release page. See scripts/check-changelog.sh.
check-changelog:
    ./scripts/check-changelog.sh

# Install the dependency-policy tool cargo-deny (the authoritative layer of
# check-supply-chain). Without it the script falls back to cargo-audit + a
# literal lockfile scan, which covers advisories but not hard version bans.
install-cargo-deny:
    cargo install cargo-deny

# Run this before `git commit` — it must pass green.
# The pre-commit gate from AGENTS.md: formatting, lints, the full suite, and
# the supply-chain checks (deny.toml bans + RustSec advisories + cache scan)
# plus the release-metadata guards (release-name drift + changelog structure).
pre-commit: fmt-check clippy test-all check-supply-chain check-release-name check-changelog

# CI gate: format check + warnings-denied lints + full suite + supply chain
# + the release-metadata guards (release-name drift + changelog structure).
ci: fmt-check clippy-strict test-all check-supply-chain check-release-name check-changelog

# ── running ───────────────────────────────────────────────────────────────────

#   just daemon -v            # debug logging
# just daemon "-v -q"       # multiple flags
# Run the daemon (`choreographr`) — default-run selects it from the root package
daemon args="": _require-zig
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p choreographr -- {{ args }}

# Run the terminal UI client (its own crate — owns its binary)
tui args="": _require-zig
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p choreo-tui -- {{ args }}

# Run the desktop GUI client (its own crate — owns its binary)
gui args="": _require-zig
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p choreo-gui -- {{ args }}

# Run the instant-messaging bridge (e.g. `just im telegram`).
# Its own crate (choreo-im) owns the binary — no root feature gating anymore.
im args="":
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p choreo-im -- {{ args }}

# Run the ACP bridge for ACP-compatible editors.
# Its own crate (choreo-acp) owns the binary. No _require-zig: these bridge
# crates do not depend on choreo-daemon (the only zlob consumer), so the
# zig requirement the old root-feature-gated recipes inherited is gone.
acp args="":
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p choreo-acp -- {{ args }}

# Run any workspace crate's binary. e.g. `just run choreographr -v`
run crate args="": _require-zig
    cargo run {{ CARGO_FLAGS }} --profile "{{ profile }}" -p {{ crate }} -- {{ args }}

# ── release & packaging ──────────────────────────────────────────────────────

# Release version — read from the workspace manifest (the single source of
# truth that scripts/release.sh, the Homebrew formula, and the AUR PKGBUILD
# mirror). Evaluated when `just` loads; the release scripts re-read it.
VERSION := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1`

# Host release target — the same target set scripts/release.sh and
# scripts/install.sh hardcode (Linux x86_64 — static musl — plus both darwin
# triples, which release.sh builds in one pass on a Darwin-arm64 host;
# smoke-test defaults to the host triple's tarball).
TARGET := `case "$(uname -s)-$(uname -m)" in Linux-x86_64) echo x86_64-unknown-linux-musl ;; Darwin-arm64) echo aarch64-apple-darwin ;; *) echo unsupported ;; esac`

# The tarball `just release` produces — used as the default for
# `just smoke-test` (built by concatenation: just does not recursively
# interpolate `{{ }}` inside a variable's value, so the pieces must be
# joined here, not nested).
release-tarball := "dist/choreographr-" + VERSION + "-" + TARGET + ".tar.gz"

# Pass extra flags through after `--`, e.g. `just release -- --upload --allow-dirty`.
# Dry-run release: build the four shipped binaries, pack the release tarball,
# write SHA256SUMS, build the .deb/.rpm when the tools are present — never uploads.
release args="": _require-zig
    ./scripts/release.sh {{ args }}

# Build everything AND run `gh release create` (requires gh, a clean tree,
# and a tag).
# Full release: the dry run plus upload.
release-upload: _require-zig
    ./scripts/release.sh --upload

# Release from a dirty tree (CI-style flows that stage files first).
release-allow-dirty: _require-zig
    ./scripts/release.sh --allow-dirty

# Pass flags through, e.g. `just release-tap -- --push`.
# Bump the Homebrew tap formula to the release version — dry-run by default:
# clones choreographr/homebrew-choreographr, rewrites Formula/choreographr.rb
# from the dist/ tarballs (version, urls, sha256 digests), validates, and
# prints the diff. `--push` commits + pushes to the tap repo. Run on the
# Linux box after Phase 4 (macOS tarball staged in dist/).
release-tap args="":
    ./scripts/update-homebrew-tap.sh {{ args }}

# (just parameter defaults are literal, so the fallback is an `if` expression.)
# Smoke-test a release tarball — defaults to the one `just release` just built.
smoke-test tarball="":
    ./scripts/smoke-test.sh "{{ if tarball == "" { release-tarball } else { tarball } }}"

# Build the fat .deb from existing target/dist artifacts (Linux only)
package-deb:
    ./scripts/build-deb.sh

# Build the fat .rpm from existing target/dist artifacts (Linux only)
package-rpm:
    ./scripts/build-rpm.sh

# Build the Termux .deb from existing target/android/arm64-v8a binaries
# (Linux only; build those first with `just android-binaries`)
package-deb-termux:
    ./scripts/build-deb-termux.sh

# Pass flags through, e.g. `just install -- --uninstall`.
# Run the pinned-version installer locally (instead of curl|sh).
install args="":
    ./scripts/install.sh {{ args }}

# ── docs & maintenance ────────────────────────────────────────────────────────

# Build API documentation for the workspace (without dependencies)
doc: _require-zig
    cargo doc {{ CARGO_FLAGS }} --workspace --no-deps

# Build and open the API documentation in the browser
doc-open: _require-zig
    cargo doc {{ CARGO_FLAGS }} --workspace --no-deps --open

# Remove all build artifacts
clean:
    cargo clean

# Update all dependencies to the latest compatible versions
update:
    cargo update

# Show the workspace dependency tree
tree:
    cargo tree {{ CARGO_FLAGS }}