Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI Reference

This reference covers all Turnkey CLI commands.

tk - Buck2 Wrapper

tk is the primary Turnkey CLI. It wraps Buck2 with automatic dependency synchronization.

Overview

When using Buck2 with Nix-managed dependencies, certain files must be regenerated when source files change. tk solves this by automatically running sync operations before buck2 commands that read the build graph.

Quick Start

# Use tk just like buck2 - it syncs automatically
tk build //some:target     # syncs first, then builds
tk test //some:target      # syncs first, then tests
tk run //some:target       # syncs first, then runs

# Explicit sync operations
tk sync                    # manually sync all stale files
tk check                   # check if files are stale (for CI)

# Skip sync when needed
tk --no-sync build //...   # skip sync, run buck2 directly

Command Reference

tk build/run/test/... (Buck2 passthrough)

Most tk commands are passed through to Buck2. Commands that read the build graph automatically sync first:

Sync-first commands (sync before running):

  • build - Build targets
  • run - Run a target
  • test - Run tests
  • query - Query the build graph
  • cquery - Configured query
  • uquery - Unconfigured query
  • targets - List targets
  • audit - Audit the build
  • bxl - Run BXL scripts

Pass-through commands (no sync):

  • clean - Clean build artifacts
  • kill - Kill Buck2 daemon
  • killall - Kill all Buck2 processes
  • status - Show daemon status
  • log - View build logs
  • rage - Generate debug report
  • help - Show help
  • docs - Open documentation
  • init - Initialize a project

Unknown commands default to syncing first (safe default).

tk sync

Explicitly synchronize all stale files, or only those of the named deps rules (go, rust, pylock, python, javascript, solidity; the rules are in .turnkey/sync.toml). Rules always run in sync.toml order, so pylock runs before python, which reads what it writes.

tk sync              # sync stale files
tk sync go           # sync only go-deps.toml
tk sync --verbose    # show what's being synced
tk sync --dry-run    # show what would be synced without doing it

Exit codes:

  • 0 - Success (files synced or nothing to sync)
  • 1 - Sync failed

tk check

Check if any files are stale without regenerating them. Useful for CI validation.

tk check             # check staleness
tk check rust        # check only rust-deps.toml
tk check --verbose   # show detailed status

Exit codes:

  • 0 - All files up-to-date
  • 1 - Files are stale (run tk sync to fix)

Example CI usage:

- name: Check files in sync
  run: tk check

tk completion

Generate shell completion scripts.

tk completion bash    # output bash completion script
tk completion zsh     # output zsh completion script
tk completion fish    # output fish completion script

Enable completions:

# Bash (add to ~/.bashrc)
eval "$(tk completion bash)"

# Zsh (add to ~/.zshrc)
eval "$(tk completion zsh)"

# Fish (run once)
tk completion fish > ~/.config/fish/completions/tk.fish

tk materialize

Bring deps cells in line with their cell indexes. The shell runs it on every load (direnv's use_turnkey and enterShell), so you rarely need to.

tk materialize /nix/store/…-rustdeps-index.json

It adds missing store links, rewrites alias packages whose target changed, removes what the index no longer names, roots the index under .turnkey/gcroots/, and records the deps file's hash. It fails, and changes nothing, if a store link points anywhere but its own name. A run with nothing to do touches no file.

tk rules

Manage rules.star files that define Buck2 build targets from source files. This command automatically detects imports from source files and updates the deps list in rules.star.

tk rules check              # Check every rules.star file against its sources
tk rules sync               # Update rules.star files with detected dependencies
tk rules help               # Show help

Options:

FlagDescription
--all, -async: process all files (skip staleness detection)
--force, -fSame as --all
--verbose, -vShow detailed output including skipped files
--quiet, -qSuppress output
--dry-run, -nShow what would be changed without writing

Staleness Detection:

check always checks every rules.star, so it also catches a stale file that is already committed. sync by default only processes directories with uncommitted changes whose source files are newer than rules.star. Use --all or --force to sync all files.

Examples:

tk rules check                    # Check all rules.star files
tk rules check src/cmd/tk         # Check one directory
tk rules sync                     # Update stale rules.star files
tk rules sync --all               # Force update all files
tk rules sync src/cmd/tk          # Sync specific directory
tk rules sync --dry-run           # Preview changes without writing

Preserving Manual Dependencies:

If you have manual dependencies that shouldn't be auto-detected, use preservation markers in your rules.star:

# turnkey:preserve-start
    "//some/manual:dep",
# turnkey:preserve-end

Dependencies within these markers are preserved during sync.

How tk runs it: tk rules, and the rules sync tk runs before a Buck2 command, call the rules-syncer library (src/rust/rules-syncer) linked into tk, and print what it reports as above.

tk Flags

Flags must come before the subcommand:

FlagDescription
--no-syncSkip sync and the cell-freshness check, run Buck2 directly
--no-localSkip local target overrides from .turnkey/local.toml
--verbose, -vShow what tk is doing
--dry-run, -nShow what would be synced without doing it
--quiet, -qSuppress non-error output
--help, -hShow help

Examples:

tk --no-sync build //...     # skip sync
tk --no-local run //target   # skip local overrides
tk --verbose sync            # verbose sync
tk -v -n sync                # dry-run with verbose output

Configuration

Sync Configuration File

tk reads staleness rules from .turnkey/sync.toml. This file is automatically generated from your Nix configuration.

When you configure dependency files in your flake.nix:

  • goDepsFile generates a Go deps rule
  • rustDepsFile generates a Rust deps rule
  • pythonDepsFile generates a Python deps rule

Example generated sync.toml:

[[deps]]
name = "go"
sources = ["go.mod", "go.sum"]
target = "go-deps.toml"
generator = ["godeps-gen", "--go-mod", "go.mod", "--go-sum", "go.sum"]

[[deps]]
name = "rust"
sources = ["Cargo.toml", "Cargo.lock"]
target_sources = "manifests"
target = "rust-deps.toml"
generator = ["rustdeps-gen", "--cargo-lock", "Cargo.lock", "--platform", "linux-x86_64=x86_64-unknown-linux-gnu", "--platform", "macos-arm64=aarch64-apple-darwin"]

Each [[deps]] entry defines:

  • name - Human-readable name for this rule
  • sources - Files that trigger regeneration when modified
  • target - The generated file
  • generator - Command to regenerate the target
  • target_sources (optional) - A top-level key of the target whose array lists more sources, relative to the project root. They are files only the generator can find, such as a Cargo workspace's member manifests. A target without that key is stale.

Local Target Overrides

tk supports per-developer local overrides via .turnkey/local.toml. This file is not committed to git, allowing each developer to customize target arguments for their local environment.

Use cases:

  • Different network addresses for local development
  • Debug flags for specific targets
  • Custom ports or configuration

Example .turnkey/local.toml:

# Override args for tk run
[run."//docs/user-manual"]
args = ["-n", "192.168.1.100"]

# Override args for tk build
[build."//src/cmd/server:server"]
args = ["--config=debug"]

# Pattern matching with "..."
[test."//src/..."]
args = ["--verbose", "--timeout=60s"]

How it works:

When you run a command that matches a configured target:

tk run //docs/user-manual
# Becomes: buck2 run //docs/user-manual -- -n 192.168.1.100

The args are injected after --, which passes them to the target binary.

Pattern matching:

Patterns ending with ... match any target with that prefix:

  • //src/... matches //src:foo, //src/pkg:bar, //src/cmd/tool:main
  • //... matches any target

Disable for a single command:

tk --no-local run //docs/user-manual   # skips local.toml

Verbose output:

tk --verbose run //docs/user-manual
# Output: tk: applying local override for run //docs/user-manual: [-n 192.168.1.100]

Shell Integration

buck2 alias to tk:

# In devenv shell, buck2 is aliased to tk
buck2 build //...   # actually runs: tk build //...

Disable with:

TURNKEY_NO_ALIAS=1 buck2 build //...   # uses raw buck2

tw - Native Tool Wrapper

tw wraps native language tools (go, cargo, uv) to keep dependency files in sync when using standard workflows.

The Problem

When you run go get github.com/foo/bar, Go updates go.mod and go.sum. But Buck2 needs go-deps.toml to know about the new dependency. Without auto-sync, you'd need to manually regenerate it.

The Solution

Turnkey transparently wraps go, cargo, and uv so that dependency sync happens automatically:

go get github.com/foo/bar  # Just works - go-deps.toml is auto-updated

How It Works

User runs: go get github.com/foo/bar
                    │
                    ▼
Shell wrapper (provides 'go' binary)
• Sets TURNKEY_REAL_GO to actual go binary path
• Calls: tw go get github.com/foo/bar
                    │
                    ▼
tw (turnkey wrapper)
1. Loads .turnkey/sync.toml configuration
2. Finds the [[wrappers]] rule for 'go' (none: runs go untouched)
3. Checks if 'get' is a mutating subcommand → yes
4. Captures SHA256 hashes of go.mod, go.sum
5. Runs the real 'go get' command
6. Compares hashes - detects changes
7. Runs the rule's post-commands (go mod tidy)
8. Runs the go deps rule (godeps-gen → go-deps.toml), then any other
   rule left stale

Supported Tools

turnkey writes one [[wrappers]] rule per language into .turnkey/sync.toml, from its language records (nix/buck2/languages.nix), for each enabled language that has deps rules:

ToolMutating CommandsWatch FilesDeps Rule
goget, modgo.mod, go.sumgo (go-deps.toml)
cargoadd, remove, updateCargo.toml, Cargo.lockrust (rust-deps.toml)
uvadd, remove, lock, syncpyproject.toml, uv.lockpylock (pylock.toml), then python (python-deps.toml)

The file names are the configured ones (buck2.go.modFile and so on). Without buck2.python.uvLockFile, uv watches only pyproject.toml and runs the python rule.

Escape Hatches

Bypass for a Single Command

TURNKEY_NO_WRAP=1 go get github.com/foo/bar

This runs the real go directly, skipping tw entirely.

Disable Sync for a Command

tw --no-sync go get github.com/foo/bar

This runs through tw but skips the sync step even if files change.

Verbose Output

tw -v go get github.com/foo/bar

Shows what tw is doing:

tw: capturing state of [go.mod go.sum]
tw: detected changes in [go.mod go.sum], running sync
Syncing go-deps.toml...
  Running: godeps-gen --go-mod go.mod --go-sum go.sum
  Regenerated go-deps.toml

Non-Mutating Commands

Commands not in mutating_subcommands pass through without any overhead:

go build ./...    # No snapshot, no sync check - just runs go build
go version        # Direct passthrough

Deps generators

tk sync runs a deps generator for each enabled language (the [[deps]] rules of .turnkey/sync.toml), so you rarely run one yourself. When you do, every generator takes the same options:

OptionDescription
-o, --output PATHOutput file (default: stdout)
--no-prefetchSkip prefetching the Nix hashes the deps cell fetches with (the file gets placeholder or missing hashes)
--no-cacheAlways fetch from the network, bypassing turnkey's prefetch cache

Prefetching is on by default. jsdeps-gen takes only --output: the pnpm lock's integrity hashes are the ones Nix fetches with.

godeps-gen

Generate go-deps.toml from go.mod and go.sum.

Usage

godeps-gen [OPTIONS]

Options

OptionDescription
--go-mod PATHPath to go.mod file (default: go.mod)
--go-sum PATHPath to go.sum file (default: go.sum)
--indirectInclude indirect dependencies (default: true)

Prefetching hashes the modules' proxy.golang.org zips, the source the godeps cell fetches, in one nix-prefetch-cached --batch call: turnkey's prefetch cache answers the modules it has seen, and the rest are fetched in parallel.

Examples

# Generate with prefetched hashes
godeps-gen -o go-deps.toml

# Use custom paths
godeps-gen --go-mod src/go.mod --go-sum src/go.sum -o go-deps.toml

# Quick check without fetching (placeholder hashes)
godeps-gen --no-prefetch

rustdeps-gen

Generate rust-deps.toml from Cargo.lock.

Usage

rustdeps-gen [OPTIONS]

Options

OptionDescription
--cargo-lock PATHPath to Cargo.lock file (default: Cargo.lock)
--cargo-toml PATHPath to the workspace's root Cargo.toml (default: next to Cargo.lock)

Examples

# Generate from default Cargo.lock
rustdeps-gen -o rust-deps.toml

# Use custom path
rustdeps-gen --cargo-lock rust/Cargo.lock -o rust-deps.toml

pydeps-gen

Generate python-deps.toml from Python dependency files. Each distribution is recorded as its pure (py3-none-any) wheel, hashed unpacked; a distribution with only platform-specific wheels, or only an sdist, fails with its name (see Python).

Usage

pydeps-gen [OPTIONS]

Options

OptionDescription
--lock PATHPath to pylock.toml (PEP 751 lock file) - RECOMMENDED
--pyproject PATHPath to pyproject.toml
--requirements PATHPath to requirements.txt
--uv-lock PATHuv.lock, whose dependency graph is recorded with the --lock packages
--include-devInclude dev dependencies

Input Formats

FormatFlagReproducibilityNotes
pylock.toml (PEP 751)--lockBestExact versions and URLs
pyproject.toml--pyprojectVariesUses latest matching versions
requirements.txt--requirementsVariesPin versions with ==
# 1. Generate lock file from pyproject.toml
uv lock

# 2. Export to PEP 751 format
uv export --format pylock.toml -o pylock.toml

# 3. Generate python-deps.toml with Nix hashes
pydeps-gen --lock pylock.toml -o python-deps.toml

Examples

# From PEP 751 lock file (best for reproducibility)
pydeps-gen --lock pylock.toml -o python-deps.toml

# From pyproject.toml (resolves to latest matching versions)
pydeps-gen --pyproject pyproject.toml -o python-deps.toml

# From requirements.txt
pydeps-gen --requirements requirements.txt -o python-deps.toml

# Include dev dependencies
pydeps-gen --lock pylock.toml --include-dev -o python-deps.toml

Troubleshooting

"tk: .buckconfig not found"

tk looks for .buckconfig to find the project root. Make sure you're in a Buck2 project directory.

"tk: failed to load sync config"

The .turnkey/sync.toml file is missing or invalid. Ensure you're in a Turnkey project with proper configuration.

"tk: sync failed: generator command failed"

The generator command failed. Check that:

  • The generator command is correct
  • Required tools are in PATH
  • Source files exist

Sync is slow

If sync takes a long time:

  • Prefetched hashes are cached (prefetch-cache.json under $TURNKEY_CACHE_DIR, or else turnkey/ in the platform cache directory, e.g. ~/.cache/turnkey/ on Linux); check a generator isn't running with --no-cache
  • Check if generators are doing unnecessary work

Bypass tk

If you need to use raw Buck2:

# Option 1: --no-sync flag
tk --no-sync build //...

# Option 2: TURNKEY_NO_ALIAS environment variable
TURNKEY_NO_ALIAS=1 buck2 build //...