Skip to content

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

bash
npm install workler

The package includes TypeScript declarations and supports both import and require. Its API is synchronous.

Quick start

ts
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, call findWorklerRoot(startDir) yourself.
  • Synchronous. All functions are synchronous, like the CLI.
  • Structured errors. Failures throw WorklerError with a stable code (plus details); the message is the same human-readable text the CLI prints. Codes include NOT_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, and LOCKED.
  • 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", with lockPath and holder). 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.

Released under the MIT License.