CLI & Claude Code plugin
Install the skein command, create and inspect workspaces from a shell, and let Claude Code do it for you with the Skein plugin.
Skein ships a small skein command-line tool so scripts, hooks, and agents
can list projects and open new workspaces without going through the UI. A
Claude Code plugin wraps it, so you can ask Claude to "start a Skein
workspace for ENG-42" from any session.
The CLI talks to the running desktop app — it doesn't work on its own. Every workspace it creates opens live in the app window.
Install the CLI
Open Settings → Companion CLI and click Install next to Install to
PATH. Skein symlinks skein into /usr/local/bin, falling back to
~/.local/bin if that isn't writable, and a toast shows where it landed.
Check it from a new terminal:
skein --versionIf you fall back to ~/.local/bin, make sure that directory is on your
PATH. Install refuses to overwrite a different skein binary that's
already in the target directory — remove it first, then click Install
again. The CLI is only bundled in release builds of Skein.
The symlink points into the app bundle, so it stays current as Skein updates itself.
Find a project
Every create command needs a project. List them with:
skein projectsEach line shows a project id and its domain/name. Add --json for
[{ "id", "name", "domain" }, …]. Wherever a command takes --project, you
can pass either the id or the project's name; a name that exists in more
than one domain is rejected with the matching domain/name pairs, so use
the id instead.
Create a workspace
skein new --project <id-or-name> --prompt "Where does this milestone stand?"The workspace opens in the app with its terminal running claude and your
prompt typed in but not sent — review it and press Enter.
skein new prints the new workspace's id.
Options:
--issue <ref>— link an issue. Skein fetches it and derives the branch and workspace name exactly as the in-app issue picker would. Accepted refs:- GitHub:
#123(the project's GitHub issues repo),owner/repo#123, or a GitHub issue or PR URL - Linear:
ENG-42or alinear.appissue URL (the project needs a Linear team connected) - Airtable: an issue id like
O2178or anairtable.comrecord URL (the project needs an Airtable binding)
- GitHub:
--issueis repeatable:skein new --project skein --issue '#1309' --issue ENG-42. The first issue is the primary and names the branch; later ones become linked issues. If one of those can't be linked,skein newprints awarning:to stderr and still creates the workspace (--jsonincludeswarnings).--name <name>— the workspace's display name. Without--issue, the branch is derived from it:--name "Payment Retry Spike"givesskein/payment-retry-spike.--branch <branch>— use this branch name instead of a derived one.--tag <name>— tag the new workspace; repeat for more. A name that matches no tag (case-insensitively) is created as a project tag. If a tag can't be applied,skein newprints awarning:and still creates the workspace.--force— recover from a collision (below).--submit— send the prompt as soon asclaudeis ready instead of leaving it for you to review. If Skein can't tell thatclaudeis ready within 10 seconds of the terminal opening, the prompt stays typed in but unsent. Requires--prompt.--json— print{ "workspace_id": … }instead of a sentence.
New branches are always cut from the project's default branch.
Recover from a collision
skein new fails when something already occupies the branch or worktree,
and the error says which case you hit:
| Message starts with | What to do |
|---|---|
| "a workspace for … was archived at …" | Re-run with --force to reopen it. Its setup scripts run again. |
| "a branch named … already exists" | Re-run with --force to attach a workspace to that branch. |
| "workspace … is already live on this branch" | Nothing — the workspace exists; its id is in the message. |
| "a worktree or directory Skein doesn't manage …" | Move it aside, or pass --branch. |
| "branch … is already checked out by a worktree Skein doesn't manage …" | Adopt that worktree in the app, or pass --branch. |
--force never deletes a branch or a commit, and never touches a live
workspace.
Work with a workspace's issues and PR
These commands take a workspace id or name. Inside a workspace's worktree you
can leave it out and Skein uses the workspace containing the current
directory. All of them accept --json.
skein issues [<workspace>]
skein issues add [-w <workspace>] <ref>...
skein issues rm [-w <workspace>] <ref>...
skein issues primary [-w <workspace>] <ref>
skein pr [<workspace>] [--refresh]skein issueslists the primary issue (marked★ primary) and then the linked ones. Issues that come from the PR body end in(from PR).addlinks more issues.rmunlinks them; removing the primary leaves the workspace with none (nothing is promoted). A PR-body issue is refused with "referenced by the PR body — edit the PR to drop it".primarypromotes a ref, and the old primary stays linked. These exit1only if every ref failed.- Refs can be
#123,owner/repo#123,ENG-42,O2178, or an issue URL (GitHub, linear.app, airtable.com). GitHub issues must belong to the project's bound issues repo. skein prprints the branch's PR from the app's cache, for example#42 Add retries open · checks passing <url>.--refreshfetches from GitHub first. With no PR it printsNo PR for branch <branch>.
skein status also shows an issues: block and a pr: line, and skein list
shows +N for extra issues. If the running app is older, these commands ask
you to update Skein.
Tag a workspace
skein tags [<workspace>]
skein tags add [-w <workspace>] <name>...
skein tags rm [-w <workspace>] <name>...The workspace defaults to the one containing the current directory, and every
command accepts --json. The desktop sidebar updates as soon as a command
runs.
skein tagslists the workspace's user tags; global tags are marked(global).addassigns tags by name, case-insensitively. A project tag wins over a global tag with the same name. A name that matches no tag is created as a project tag (printscreated tag <name>).rmremoves tags from the workspace. The tag itself is kept — delete it from the Manage tags sheet.- System tags (Running, Linked, the project and domain name tags, …) are computed by Skein and can't be added or removed.
add and rm exit 1 only if every name failed. See
Tags for how tags work in the app.
Rename a workspace
skein rename "<new name>" [-w <workspace>] [--json]Changes the workspace's display name, like renaming it in the sidebar. The
branch keeps its name. The workspace defaults to the one containing the
current directory; pass -w with an id or a name that is unique across all
projects to pick another. A blank name is refused. --json prints the
renamed workspace.
Check on workspaces
skein list [--project <id-or-name>]
skein status <workspace-id-or-name>skein list shows every active and pending workspace (archived ones are
left out) with its status, project, branch, linked issue, and last activity.
Within each project the stalest workspace comes first. skein status
shows one workspace in detail: branch and base, status, origin, worktree
path, linked issue, and any setup error. A name passed to status must be
unique across all projects; otherwise it lists the matching ids. Both
commands take --json.
Archive workspaces
skein archive <id-or-name>... [--force] [--json]Archives one or more workspaces, like Archive… in the app: the working directory goes away, the branch stays. Each argument is a workspace id or a name that is unique across all projects. Every argument is resolved before anything is archived, so a typo or an ambiguous name fails the whole command and archives nothing. Archived workspaces can't be named: a ref to one fails with "no workspace matches".
Each workspace gets its own result line: archived, not found,
failed: …, or refused: uncommitted changes (re-run with --force).
already archived appears only when the same workspace is named twice, or
something else archived it while the command ran. A dirty worktree is never
discarded unless you pass --force, which discards uncommitted changes in
every workspace named. The command exits 1 if any workspace was refused,
not found, or failed, and 0 otherwise. --json prints the per-workspace
results as JSON.
skein archive refuses to archive the workspace you run it from (its
worktree is your current directory or one of its parents), since that would
end your own shell. Run it from somewhere else.
For a guided version, run:
skein archive -i [--project <id-or-name>] [--force]Without --project it first asks which project to clean up. It then shows
that project's workspaces stalest first, lets you select several, and asks
you to confirm. Any that were refused for uncommitted changes are offered
again with force. It needs an interactive terminal and exits with an error
otherwise. The same flow is in the app as Clean up workspaces…; see
Workspaces & panes.
Add the Claude Code plugin
The plugin teaches Claude Code when and how to call skein. Install the CLI
first, then run these in Claude Code:
/plugin marketplace add BuildQube/skein-releases
/plugin install skein@skein-marketplaceIt adds two skills:
- skein-workspace — for open-ended requests: "start a Skein workspace for BuildQube/Skein#123 and ask it to write a plan", "what are my Skein workspaces doing?", "what's the status of workspace X?". Claude looks up the project, checks existing workspaces, then creates one or reports what it found.
- skein-new — a shorthand for when the target is already explicit:
/skein-new ENG-42: fix the retry loop, or "new skein workspace named Payment Spike prompted with …". It skips the survey and creates the workspace straight away.
Either way, Claude only stages the prompt — it opens in the new workspace
unsent, for you to review — unless you ask Claude to start it running, in
which case it passes --submit.
The plugin is versioned with Skein releases. Run /plugin marketplace update skein-marketplace in Claude Code to pick up a newer version.
Troubleshooting
- "Skein isn't running" — open the Skein app and try again. The CLI finds the app through a file the app writes on launch.
- "CLI token rejected" — restart Skein.
- Targeting a dev or pre-release build — pass
--flavor devor--flavor pre(or setSKEIN_FLAVOR) to talk to that build instead of the release app.