workler
v0.1.3
Published
Local workspace manager with copy/link rules for untracked project files.
Downloads
494
Readme
workler
First version of a local workspace manager.
Workler does not use git worktree. It creates normal local clones under .worktrees/ and then applies local file rules from .workler.
📖 Full documentation: yjjosh.github.io/Workler
Config
.workler is line-based, like .gitignore but with an action first:
# full-line comment
link node_modules # inline comment
copy .env
copy "some folder/file.txt"
copy 'another file.txt'link <path>creates a symlink in the workspace back to the main project.copy <path>copies the file/folder into the workspace.- Blank lines are ignored. A
#at the start of a line or preceded by whitespace starts a comment; a#inside a quoted path (or glued to a word, likefile#1.txt) does not. - Quote a path with double or single quotes if it contains spaces or
#. - Paths must stay inside the project: absolute paths,
..,.git, and.worktreesare rejected. Parse errors report the line, column, and the offending line content.
Install
npm install -g workler
workler helpCommands
workler init
workler add <name> [base] [--branch <branch>] [--checkout <ref>] [--force] [--dry-run]
workler apply [name] [--force] [--dry-run]
workler apply --all
workler list
workler path <name>
workler remove <name>workler add branch behavior
| Command | Result |
| --- | --- |
| workler add feat | Creates a new branch feat from the main project's current HEAD and checks it out. |
| workler add feat main | Creates a new branch feat starting at main. <base> may be a local branch, a remote branch (e.g. origin/main), a tag, or a commit. A remote-branch base also sets upstream tracking. |
| workler add exp --branch feat/x | Workspace named exp, branch named feat/x. Creates feat/x from HEAD if it does not exist, otherwise checks it out. |
| workler add exp main --branch feat/x | Creates the explicitly named branch feat/x from main. This is useful when the workspace name must be filesystem-safe but the branch contains /. |
| workler add hotfix --checkout main | No new branch: checks out main directly. Tags/commits are checked out on a detached HEAD. This is the supported way to have several workspaces on the same branch. |
Rules:
--checkoutcannot be combined with--branchor positional[base].[base]may be combined with--branchto create that explicitly named branch from the base.workler add <name>/workler add <name> <base>always create a new branch and fail if branch<name>already exists; use--checkout <name>to reuse the existing branch.workler addalso accepts--forceand--dry-run; see the next section.
Safety, --force, and --dry-run
add and apply never overwrite an existing destination by default. A
symlink that already points at the right source counts as "already linked",
and a copy whose destination matches the source counts as up to date; both
are reported as ok and left alone. Anything else (a symlink pointing
elsewhere, a regular file, or a directory) is a conflict, and the error shows
both the source and the destination along with what is currently there:
cannot link node_modules: destination already exists
source: /path/to/project/node_modules
destination: /path/to/project/.worktrees/feature-a/node_modules (existing directory)
re-run with --force to replace the destination--dry-runprints exactly what would be copied, linked, skipped, replaced, or conflicted without changing anything. Foradd, no clone is created; the whole plan (clone, branch, rules) is printed instead.--forcereplaces conflicting destinations and reports what was replaced.
Multi-workspace commands
Because every workspace is an independent clone, each one has its own fetch/pull state and its own local branches. These commands operate on the main project and all workspaces at once:
workler status
workler fetch
workler sync
workler branch-syncworkler statusprints one line per workspace: current branch (or detached SHA), ahead/behind vs its upstream, and clean/dirty. Directories under.worktrees/that are not usable clones are flagged as broken.workler fetchrunsgit fetch --prune originin the main project and every workspace. Workspaces without anoriginremote are skipped with a note.workler syncfetches everything, then fast-forwards each workspace's current branch where an upstream exists. Workspaces with uncommitted changes to tracked files are never touched (untracked files, such as the ones created by copy rules, do not block a sync), and branches that have diverged from their upstream are reported asdiverged, skipped— nothing is ever merged, rebased, or forced.workler branch-syncsyncs local branches across clones:- ensures every workspace has a
workler-rootremote pointing at the main project (added even when there is noorigin, fixed if the URL is wrong) - creates the root's local branches in each workspace, and fast-forwards branches that are strictly behind the root
- never moves the branch currently checked out in a workspace, and never moves a branch with local-only or diverged commits (skipped with a note)
- mirrors each workspace's local branches back into the root as read-only
refs under
refs/workler/<workspace>/<branch>(inspect them withgit for-each-ref refs/workler/); no local branches are created in the root
- ensures every workspace has a
Build locally with:
npm run buildFor development, link it as workler:
npm install
npm link
workler helpOr run without linking:
npm run dev -- helpProgrammatic API
Workler is also a library: tools can create, list, and remove workspaces without shelling out or parsing console text. The CLI and the API share the same core.
import { initProject, createWorkspace, listWorkspaces, removeWorkspace } from "workler";
const root = "/path/to/project";
initProject(root);
const ws = createWorkspace(root, { name: "agent-1" }); // same semantics as `workler add agent-1`
console.log(ws.path, ws.branch, ws.head);
for (const info of listWorkspaces(root)) {
console.log(info.name, info.branch ?? info.shortHead, info.clean, info.broken ?? "ok");
}
removeWorkspace(root, "agent-1", { force: true }); // dirty protection unless force- Every function takes an explicit project root — nothing reads
process.cwd()or mutates global state. UsefindWorklerRoot(startDir)if you want CLI-style discovery. - Failures throw
WorklerErrorwith a stablecode(NOT_INITIALIZED,WORKSPACE_EXISTS,WORKSPACE_DIRTY,BRANCH_EXISTS,BAD_REF,RULE_CONFLICT,SETUP_FAILED,LOCKED, …) and the same message the CLI prints. - Mutating operations (
initProject,createWorkspace,removeWorkspace) hold a per-project lock file at.worktrees/.workler.lockfor their duration. A held lock fails fast withLockError; a lock whose holder is provably dead (same host, pid gone, or unreadable file) is reclaimed automatically, and a lock from another host never is. inspectProject(root),planWorkspaceCreation(root, options), andresolveWorkspacePath(root, name)cover inspection without side effects.
See the API reference for the full contract.
Nested workspaces (subagents)
Workspaces are ordinary clones, so workler also works inside them. Running workler add inside a managed workspace creates the new workspace under that workspace's own .worktrees/ — useful when you work on multiple features and each feature workspace needs more checkouts, e.g. one per subagent:
project/
.worktrees/
feature-a/
.worktrees/
agent-1/
agent-2/How it behaves at the nested level:
- Every command operates on the nearest enclosing workler project:
list,path,remove,apply, andaddrun insidefeature-aonly seefeature-a's own workspaces. - Rules for nested workspaces come from the
.worklerchecked out in the workspace, so commit.worklerif nested workspaces should inherit the rules. linktargets resolve to the immediate parent:agent-1/node_moduleslinks tofeature-a/node_modules, which may itself be a link back to the root.- Bare
workler applyinside a workspace refreshes it from its immediate parent. workler.rootin each clone points at its immediate parent; nesting depth is unlimited.
Shell switching helper
A CLI cannot directly cd your current shell, so load the helper:
eval "$(workler shell-init)"Then switch with:
wcd main
wcd feature-aProject structure
src/index.ts programmatic API entry point (package main)
src/cli.ts CLI entry point
src/commands/ command implementations (thin wrappers over src/core)
src/core/ core operations shared by CLI and API
src/config.ts .workler parser
src/rules.ts copy/link rule application
src/workspaces.ts root/workspace discovery
src/lock.ts per-project lock for mutating operations
src/errors.ts structured WorklerError/LockError
src/git.ts git command helpers
src/fs-utils.ts filesystem helpers
test/ node:test suite (`npm test`)