jjwt
A workspace manager for jujutsu repositories. Inspired by worktrunk and config-compatible with it, jjwt is an independent implementation written from scratch in Rust, tailored to jujutsu's native concepts (workspaces, bookmarks, revsets).
Install
Cargo
Nix
Add jjwt as a flake input and apply its overlay:
{
inputs.jjwt.url = "github:endoze/jjwt";
outputs = { nixpkgs, jjwt, ... }: {
# Apply the overlay so pkgs.jjwt is available everywhere
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [{
nixpkgs.overlays = [ jjwt.overlays.default ];
environment.systemPackages = [ pkgs.jjwt ];
}];
};
};
}
Works the same way in home-manager — apply the overlay, then add pkgs.jjwt to home.packages.
Or run directly without installing:
Homebrew
Then set up your shell:
# Fish
# Bash (add to ~/.bashrc)
# Zsh (add to ~/.zshrc)
This creates a wt shell function that wraps jjwt so that wt switch can cd into the target workspace.
Shell completions
# Fish
# Bash (add to ~/.bashrc)
# Zsh (add to ~/.zshrc, before compinit)
Quick start
# Create a config file
# Create a new workspace (no existing bookmark)
# Switch to a workspace for an existing bookmark (auto-creates)
# List all workspaces
# Remove a workspace
# Remove the current workspace
Commands
switch <name>
Switch to a workspace. If the workspace doesn't exist but a bookmark with that name does, the workspace is created automatically. Use --create only for entirely new branches.
| Flag | Description |
|---|---|
-c, --create |
Create workspace and bookmark from scratch |
-x, --execute <cmd> |
Run a template-rendered command after switching |
--clobber |
Remove stale directory at target path |
--rerun-hooks |
Re-run start hooks even when workspace exists |
--no-hooks |
Skip all hooks |
--format json |
Output as JSON |
remove [names...]
Remove one or more workspaces. Omit names to remove the current workspace.
| Flag | Description |
|---|---|
-f, --force |
Bypass uncommitted changes check |
-D, --force-delete |
Delete bookmark even if not merged into trunk |
--no-delete-branch |
Keep bookmark even when merged |
--no-hooks |
Skip all hooks |
--format json |
Output as JSON |
list
Show all workspaces with status, diff stats, and trunk relationship.
The table adapts to your terminal width — columns are prioritized and low-value ones drop on narrow terminals.
hook
Run a configured hook manually, or inspect all hooks.
config
Manage jjwt configuration.
step utilities
Low-level tools for scripting and automation.
| Command | Description |
|---|---|
step eval <template> |
Render a template expression |
step for-each -- <cmd> |
Run command in every workspace |
step tether -- <cmd> |
Run command tied to current workspace lifecycle |
step prune [--dry-run] |
Remove all workspaces merged into trunk |
step relocate <old> <new> |
Rename workspace and move its directory |
step describe [--dry-run] |
Generate a commit message with an LLM |
step diff [args...] |
Show diff against trunk (jj diff -r trunk()..@) |
step pick |
Interactive workspace picker with preview |
step copy-ignored <src> [dest] |
Copy jj-ignored files between workspaces (CoW) |
step var set/get/list/delete |
Manage per-workspace template variables |
Aliases
Define custom aliases in your config and invoke them as subcommands:
[]
= "echo Hello from {{ branch }} ({{ args | join(' ') }})"
Configuration
jjwt reads config from two layers, merged together:
- User config —
~/.config/jjwt/config.toml(defaults, personal hooks) - Project config —
.config/wt.tomlin the repo root (shared with team)
User config can also contain [projects."host/owner/repo"] blocks that override settings per-repository.
Example .config/wt.toml
= ".worktrees/{{ branch | sanitize }}"
= true
[]
= "npm install"
[]
= "echo Switched to {{ branch }}"
[]
= "jj describe -m 'wip: {{ branch }}'"
[]
= "claude -p --no-session-persistence"
[]
= true
Hooks
Hooks run at lifecycle points: pre-switch, post-switch, pre-start, post-start, pre-remove, post-remove. They support template rendering with variables like {{ branch }}, {{ repo }}, {{ worktree_path }}, {{ vars.KEY }}, and more.
Project-sourced hooks require approval on first run (stored in ~/.config/jjwt/approvals.toml). Set JJWT_TRUST_PROJECT_HOOKS=1 to bypass in CI.
Template filters
| Filter | Example | Output |
|---|---|---|
sanitize |
feat/login |
feat-login |
sanitize_hash |
feat/login |
feat-login-a3f |
sanitize_db |
feat/LOGIN |
feat_login_a3f |
hash |
feat/login |
k7m |
hash_port |
feat/login |
14832 |
codename |
feat/login |
fair-mole |
codename(3) |
feat/login |
fair-icy-mole |
dirname |
src/core/plan.rs |
src/core |
basename |
src/core/plan.rs |
plan.rs |
License
MIT licensed. See LICENSE for details.
Some template filters were ported from worktrunk by Maximilian Roos (MIT / Apache-2.0). Attribution is preserved in the respective source files.