Programmatic API
Workler can be used as a library, so tools can create, list, and remove workspaces without shelling out to the CLI or parsing console text.
Install
npm install worklerThe package includes TypeScript declarations and supports both import and require. Its API is synchronous.
Quick start
import { initProject, createWorkspace, listWorkspaces, removeWorkspace } from "workler";
const root = "/path/to/project";
initProject(root);
const ws = createWorkspace(root, { name: "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 });The API and the CLI share the same core: workler add and createWorkspace() are the same code path.
Contract
- Explicit roots. Every function takes the project root as its first argument. Nothing reads
process.cwd()or mutates global state, so a long-running process can drive many projects concurrently. To get CLI-style discovery of the nearest enclosing project, callfindWorklerRoot(startDir)yourself. - Synchronous. All functions are synchronous, like the CLI.
- Structured errors. Failures throw
WorklerErrorwith a stablecode(plusdetails); themessageis the same human-readable text the CLI prints. Codes includeNOT_INITIALIZED,NOT_A_GIT_REPO,WORKSPACE_EXISTS,WORKSPACE_NOT_FOUND,WORKSPACE_DIRTY,BRANCH_EXISTS,BAD_REF,INVALID_NAME,INVALID_OPTIONS,CONFIG_INVALID,RULE_CONFLICT,SETUP_FAILED, andLOCKED. - Workspaces live in
<root>/.worktrees/<name>— the same project-local layout the CLI uses, including nested workspaces (pass a workspace's path as the root to manage its own.worktrees/).
Functions
initProject(root): InitResult
Same as workler init: creates .workler (if missing), .worktrees/, marks the repository with workler.* git config, and adds .worktrees/ to .git/info/exclude (or .gitignore for non-git roots). Idempotent; the result reports whether the config file was created.
inspectProject(root): ProjectInfo
Non-throwing inspection: { root, exists, gitRepo, marked, configFileExists, workspacesDirExists, initialized, parent }. A project is initialized when it is marked with workler.* git config or has a .workler file. parent is set when the root is itself a managed workspace (nested workspaces).
createWorkspace(root, options): CreateWorkspaceResult
Same semantics as workler add. options: { name, base?, branch?, checkout?, force? } with the exact add rules. checkout is exclusive, while base and branch may be combined to create an explicitly named branch from an explicit start point; name alone creates a new branch from HEAD. Optional onProgress(message) and onRuleResult(result) callbacks stream what the CLI would print. The result carries path, branch (undefined when detached), head, detached, and the structured copy/link rules outcome.
.workler copy/link rules are applied exactly as with the CLI, including conflict detection and force replacement. If setup fails after the clone was created (bad config, rule conflict), the error is SETUP_FAILED and the clone is left in place for inspection — the same partial-failure contract the CLI documents.
planWorkspaceCreation(root, options): WorkspacePlan
The read-only planning half of createWorkspace (what add --dry-run prints): the resolved target path, the checkout plan, and any warnings. Nothing is created.
listWorkspaces(root): WorkspaceInfo[]
The main project plus every entry under .worktrees/, including broken ones: { name, path, isMain, isClone, broken?, branch?, head?, shortHead?, detached, clean? }. clean uses the same guard as remove (untracked files count as dirty), so clean === true means removable without force.
resolveWorkspacePath(root, name): string
Absolute path of a workspace by name ("main" resolves to the root). Throws WORKSPACE_NOT_FOUND for unknown names.
removeWorkspace(root, name, { force? }): RemoveWorkspaceResult
Same as workler remove: refuses to remove main, and refuses a workspace with uncommitted changes (including untracked files) unless force is set — that failure is WORKSPACE_DIRTY.
Locking
The mutating operations (initProject, createWorkspace, removeWorkspace — and the CLI commands built on them) take a per-project lock for their duration, so concurrent processes cannot corrupt a project. Read-only operations never lock.
- The lock is a JSON file at
<root>/.worktrees/.workler.lock(projectLockPath(root)returns it) recording the holder's pid, hostname, operation, start time, and a unique ownership token. - If the lock is already held, the operation fails immediately with
LockError(code: "LOCKED", withlockPathandholder). There is no built-in waiting — retry at whatever cadence suits the caller. - Stale-lock recovery: a lock whose holder can be proven dead is reclaimed automatically — same hostname and the pid no longer exists, or an unreadable/torn lock file is at least 30 seconds old. Fresh malformed files are protected because another process may still be writing them. A lock from a different host is never reclaimed (liveness cannot be checked); delete the file manually if you know the process is gone.
- Locks are scoped per project root: nested projects lock their own
.worktrees/, independent of the parent.