Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

work

license node status deps

A workflow CLI for parallel git worktrees.

The problem

You hit "new worktree" in Codex, or start Claude Code with --worktree. Now you want to actually see what it built. So:

  • install deps — easy
  • get the env vars right — maybe
  • get the supporting services (db, queue, sync server, …) isolated from the other worktrees — meh
  • and now those isolated services need their own URLs and credentials threaded back into the env — urgh
  • open a terminal for each long-running process — times every worktree you're juggling

Your agent runs for 30+ minutes, so you'd like to spin up the next worktree in parallel. Instead you have a pile of terminals open per workspace and you've lost track of which one is which.

The individual pieces are already solved:

Tool Solves
git worktree coexisting checkouts per branch
portless stable *.localhost URLs, no port collisions

work is the glue: a per-workspace setup script, a config of long-running commands, a tiny supervisor, and a single CLI to drive it.

Zero runtime dependencies — just Node ≥ 20.12 stdlib. The full CLI ships as one ~30 KB JS file.

What you get

work create feature-x       # create a worktree without setup or processes
work create feature-x --remote origin  # fetch + track a remote branch
work up feature-x --create   # create worktree, run setup, start configured servers
work urls feature-x          # see where everything is reachable
work logs -f web             # tail one service
work down feature-x          # tear it all down

Routed commands get a stable URL of the form:

{command}-{workspace}-{project}.localhost
# e.g. web-feature-x-tilly.localhost, sync-feature-x-tilly.localhost

Logs and state live as plain files in ~/.work-cli/ so you can cat, tail, jq them.

Install

Not on npm yet — clone and link locally with Bun:

git clone https://github.com/ccssmnn/work-cli.git
cd work-cli
bun install
bun run build
bun link

Optional but recommended: install portless (for route: true commands). Verify with work doctor.

Shell integration (completion + work cd) — add to ~/.zshrc or ~/.bashrc:

eval "$(work shell-init zsh)"    # or: bash

Remote HTTPS with Cloudflare Tunnel

Opt in per invocation; existing commands and setup scripts stay unchanged:

work setup --cloudflare
work up --cloudflare

Every routed command gets one stable HTTPS label:

https://{machine}-{command}-{workspace}-{project}.example.com

WORK_URL, every WORK_<ID>_URL, WebSocket variants, and both JSON maps use these URLs. Normal commands remain on *.localhost.

One-time machine setup:

brew install cloudflared
cloudflared tunnel login
cloudflared tunnel create work-cbook

Put the settings in a gitignored .env.local at the project root:

WORK_CLOUDFLARE_DOMAIN=example.com
WORK_CLOUDFLARE_TUNNEL_ID=<tunnel UUID>
# Recommended: owner + random 64-bit token + hostname
WORK_CLOUDFLARE_MACHINE=carl-7f3a91c8d2e4b6a8-cbook

Generate the random token once with openssl rand -hex 8, keep the resulting machine value stable, and use a different value per machine. This makes URLs impractical to guess while preserving stable origins. It is obscurity, not authentication: anyone who learns a URL can open it. Do not publish the value in a public repository.

work loads .env.local from the project root, then the selected workspace; the invoking shell overrides both. It removes all WORK_CLOUDFLARE_* values before starting setup scripts or dev servers. Other Cloudflare variables, such as CLOUDFLARE_API_TOKEN, remain available to commands that need them. Once a workspace is running through Cloudflare, work run, work restart, and work up inherit that mode without another flag. Run work down before switching the workspace back to local mode. cloudflared tunnel create stores credentials in its standard directory; work discovers them automatically.

The domain must use Cloudflare DNS. Keeping the complete work name in one label, directly below the zone, allows standard Universal SSL to cover it. work creates each exact DNS CNAME and keeps one machine-wide connector synchronized with active commands. DNS records remain stable after commands stop.

In WorkOS, allow CORS origin https://*.example.com and the required non-default redirect wildcard, such as https://*.example.com/callback. Published Tunnel hostnames remain internet-reachable; the random label only makes discovery difficult.

Example: a real workflow

A real work.config.js from tilly — an Astro PWA with a Jazz sync server. Each workspace gets its own isolated sync server, and the web app is told which sync URL to talk to via env var:

// tilly/work.config.js
export default {
  project: "tilly",
  worktrees: {
    dir: "../tilly.worktrees",
    setup: "bun scripts/work-setup.ts",
  },
  commands: {
    sync: {
      run: 'bunx jazz-run sync --port "$PORT" --host "$HOST"',
      autoStart: true,
      route: true,
    },
    web: {
      run: 'PUBLIC_JAZZ_SYNC_SERVER="wss://sync-${WORK_WORKSPACE}-tilly.localhost" astro dev --port "$PORT" --host "$HOST"',
      autoStart: true,
      route: true,
    },
  },
}

scripts/work-setup.ts does the per-worktree prep — copy .env.local, run codegen, whatever the workspace needs. It receives:

Env var What it points to
WORK_ROOT the workspace (worktree) being set up
WORK_SOURCE_ROOT the main repo — useful for copying .env.local etc.
WORK_WORKSPACE slugified branch name
WORK_PROJECT project slug from config
WORK_URL primary routed URL (the web command, if routed)
WORK_WEB_URL same as WORK_URL
WORK_SYNC_URL full URL for the sync command, if routed
WORK_SYNC_WS_URL WebSocket URL for the sync command, if routed
WORK_URLS JSON of all routed URLs, keyed by command id
WORK_WS_URLS JSON of WebSocket URLs, keyed by command id

WORK_SOURCE_ROOT always resolves to the main worktree via git worktree list, so it works the same whether you ran work from the main repo or from another worktree. Configured commands receive the same routed URL variables.

Day in the life:

# Codex finishes a worktree on the `chat-streaming` branch.
# Spin it up — creates ../tilly.worktrees/chat-streaming, runs setup, starts both servers.
work up chat-streaming --create

# Open the web app. Sync server is already wired up via the env var.
open https://web-chat-streaming-tilly.localhost

# Meanwhile, the agent is grinding for 30 minutes. Start the next worktree in parallel.
work up image-uploads --create

# Or just create a clean worktree without setup or servers.
work create refactor-sidebar

# Need to debug? Tail the sync server logs.
work logs -f -w chat-streaming sync

# Done with this branch — stop everything.
work down chat-streaming

At any moment:

work ps           # what's running here
work ps -a        # what's running everywhere
work watch        # live-refresh the ps table
work watch -a     # live-refresh everything
work urls         # routed URLs for the current workspace
work doctor       # diagnose anything broken

Adopting a worktree created by another tool

If a worktree already exists — created by git worktree add directly, Codex, Claude Code, or anything else — cd into it and let work derive everything from the current branch:

cd /path/to/the/worktree
work setup        # run the per-workspace setup script against this worktree
work up           # start the configured servers (no --create needed)

The workspace name is the slugified branch name. The worktree root is $PWD. No path flag needed.

Checking out a remote branch

When the worktree doesn't exist yet, work create (and work up --create) resolves the branch like git checkout does:

  1. a local branch with that name → used as-is
  2. exactly one remote has the branch → fetched, then checked out as a local tracking branch
  3. otherwise → a new branch off HEAD

To grab a branch you've never fetched, name the remote — work fetches it first and fails if the branch doesn't exist there:

work create feature-x --remote origin
work up feature-x --create --remote origin   # same, plus setup + servers

Branch names keep their real spelling; only the workspace slug is normalized (feat/foo → workspace feat-foo, branch stays feat/foo).

Reference

work --help              # all subcommands
work <command> --help    # one subcommand
work help run            # same help, command form
work docs                # list built-in topics (config, urls, daemon, …)
work docs config         # full config field reference
work docs setup          # setup-hook env vars

Development

Requires Bun for the dev loop. Build targets Node ≥ 20.12 with zero runtime dependencies.

bun run dev -- doctor   # run the CLI from source
bun test                # node:test runner
bun run check           # typecheck + lint + knip
bun run build           # emit dist/

License

MIT © Carl Assmann

About

Parallel git worktree workflows: per-workspace commands, stable .localhost URLs, tmux-parged agents, one CLI.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages