@ferrousapps/listr
v0.2.0
Published
Run Claude Code on your own machine against tasks you assign in Listr. Connect with a pairing code, allow-list the repos it may touch, and it implements tasks and opens PRs for you to review.
Maintainers
Readme
@ferrousapps/listr
Run Claude Code on your own machine against tasks you assign in Listr. Hand a task to Claude from the app; the agent on your machine implements it on a branch, opens a pull request, runs a review pass, and reports status back to the task. You merge.
You, anywhere ───(Listr / Firestore)───▶ your machine: listr-agent start
"Hand off to Claude" on a task claim → branch
(repo comes from the list) → claude (code) → gh pr create
→ claude (review) → PR comment
◀── status: Queued · Coding · PR Open · Reviewed- Browser sign-in — no passwords.
listr-agent loginapproves the machine in a tab; a one-time pairing code covers headless boxes. - Repo allowlist is the hard security boundary — the agent only ever touches repos you added on this machine.
- Shared pools — everyone on a collaborative list can connect machines, and a teammate can hand a task to any online machine in the pool.
- The agent never merges. Every change lands as a PR for you to review.
The command this package installs is listr-agent.
Requirements
- Node ≥ 20
- Claude Code CLI (
claude), signed in (claudeonce interactively, or an API key configured). - For GitHub repos: the GitHub CLI (
gh), authenticated with push + PR rights:gh auth login. - For Bitbucket repos: an app password (
BITBUCKET_USERNAME+BITBUCKET_APP_PASSWORD) or an access token (BITBUCKET_TOKEN) — Bitbucket has nogh-style CLI, so the agent uses git-over-HTTPS + the REST API. - A Listr Pro/Elite account (Coding Agents is a paid capability).
Install
npm install -g @ferrousapps/listrThis puts the listr-agent command on your PATH. (Prefer not to install
globally? Use npx @ferrousapps/listr <command> anywhere below.)
Update later with npm update -g @ferrousapps/listr.
Quick start
The fastest path is the guided wizard — it asks for each value (Enter skips any, add it later), then offers to allow-list a repo and sign the machine in:
listr-agent setupIt writes your answers to ~/.listr-agent/.env; re-run it any time to change or
fill in skipped values. Prefer to do it by hand? The individual steps:
# 1. Sign this machine in (opens a browser tab you approve).
listr-agent login
# …no browser on this machine? Use a code from Listr → Agents → "Connect a machine":
# listr-agent connect ABCD-1234
# 2. Allow-list the repo(s) this machine may touch (the security boundary).
listr-agent repos add --slug your-org/your-app
# …or an existing local checkout:
listr-agent repos add --path ~/code/your-app --alias app
# 3. Sanity-check everything (tools, sign-in, visible tasks).
listr-agent doctor
# 4. Run it.
listr-agent startThen in Listr:
- Open a list, edit its details, and set the GitHub repository
(
owner/repo) — every task you hand off inherits it. - On any task, choose Handoff to Claude → pick a machine (or Any available agent) → Hand off.
The task advances Queued → Claimed → Coding → Committing → PR Open → Reviewing →
Reviewed, with the PR link attached. Manage running jobs (cancel / rerun /
remove, and view logs) from the Agents tab.
You stay signed in across restarts — you only sign in once per machine.
Per-task options: tag a task Urgent to have agents pick it up first (it
even preempts a running non-urgent job). Add a custom field named "Branch" to
name the working branch the agent creates; otherwise it auto-generates
listr-agent/<id>-<slug>.
Commands
listr-agent start Watch for tasks and run them (default)
listr-agent once Process whatever's eligible now, then exit
listr-agent doctor Check config, tools, sign-in, and visible work
listr-agent login Sign in via your browser (recommended)
listr-agent connect <code> Pair with a code — for machines with no browser
listr-agent disconnect Sign out and remove this machine's session
listr-agent mcp Run as an MCP server so Claude Code can create tasks
listr-agent repos list Show this machine's repo allowlist
listr-agent repos add --slug owner/repo [--alias name]
listr-agent repos add --path /abs/path [--alias name]
listr-agent repos remove <slug|path|alias>
listr-agent machines List machines registered to your accountAdd --verbose (or -v) to any command for detailed logging.
listr-agent doctor is the safe pre-flight: it lists exactly which tasks it can
see and whether each one's repo is allow-listed.
How repos work
A task's repo is untrusted cloud input — the agent never clones or runs it directly. Instead:
- You set a repository on the Listr list (or override per task) — GitHub or Bitbucket.
- The agent resolves that value against this machine's local allowlist
(
listr-agent repos add). If it isn't allow-listed, the task is skipped on this machine — another machine that has allow-listed it can run it.
So the cloud can only ever pick from the repos you explicitly approved on a given machine. Provider is inferred from the form:
listr-agent repos add --slug owner/repo # GitHub
listr-agent repos add --slug bitbucket.org/workspace/repo # Bitbucket
listr-agent repos add --slug workspace/repo --provider bitbucket
listr-agent repos add --path ~/code/your-app --alias app # local checkoutOn the Listr list, write owner/repo for GitHub or bitbucket.org/workspace/repo
for Bitbucket. GitHub clones via gh; Bitbucket clones via git-over-HTTPS using
your configured credentials and opens PRs through the Bitbucket REST API.
Sources & the shared pool
Control which tasks a machine watches with LISTR_AGENT_SOURCE:
| Source | Watches | Default |
|---|---|---|
| own | your personal lists only | ✅ |
| collab | collab lists you own or are shared on | |
| both | personal + collab | |
collab/both run task text authored by collaborators, so they require an
explicit opt-in: set LISTR_AGENT_ALLOW_COLLAB=1.
When you run with collab/both, your machine joins that list's shared pool:
every collaborator can see it online and hand it a task. If several machines
watch the same list, an atomic claim guarantees only one runs a given task,
and you can target a specific machine when handing off.
# Join collab pools (in addition to your own tasks):
LISTR_AGENT_SOURCE=both LISTR_AGENT_ALLOW_COLLAB=1 listr-agent startCreate tasks from Claude Code, Cursor, or any MCP client
The same pairing also lets any MCP client — Claude Code, Cursor, Windsurf,
VS Code (Copilot agent mode), Cline, Codex CLI, Gemini CLI, … — create tasks
in your Listr projects — the reverse direction from handing tasks off. Because
the session acts as you, it writes your own projects directly; no bot
account, no extra login. It's a standard stdio MCP server, so every client
launches it the same way: npx -y @ferrousapps/listr mcp.
Sign in once first (if you haven't already):
listr-agent login # opens your browser — nothing to copyYour browser is normally already signed in to Listr, so login just asks you to
approve this machine and hands the session back over loopback. On a machine with
no browser — SSH, a container, CI — use a pairing code instead:
listr-agent connect ABCD-1234 # code from Listr → Agents → "Connect a machine"Either way the machine stays signed in across restarts.
Claude Code
claude mcp add listr -- npx -y @ferrousapps/listr mcpCursor — one click: Add Listr to Cursor
Or add it by hand to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json
(one project), or via Settings → MCP → Add new global MCP server:
{
"mcpServers": {
"listr": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@ferrousapps/listr", "mcp"]
}
}
}On Windows, npx is a batch script and Cursor won't run it directly — wrap
it in the command interpreter:
{
"mcpServers": {
"listr": {
"type": "stdio",
"command": "cmd",
"args": ["/c", "npx", "-y", "@ferrousapps/listr", "mcp"]
}
}
}Cursor starts the server on demand; the tools appear under Settings → MCP. If
you haven't paired yet, the server still connects — the first tool call replies
with the connect command to run rather than dying silently.
Windsurf — same mcpServers block in ~/.codeium/windsurf/mcp_config.json.
Cline / Roo Code — same mcpServers block via MCP Servers → Configure.
VS Code (Copilot agent mode) — .vscode/mcp.json uses a servers key:
{
"servers": {
"listr": { "type": "stdio", "command": "npx", "args": ["-y", "@ferrousapps/listr", "mcp"] }
}
}Codex CLI — in ~/.codex/config.toml:
[mcp_servers.listr]
command = "npx"
args = ["-y", "@ferrousapps/listr", "mcp"]Gemini CLI — the mcpServers block in ~/.gemini/settings.json.
The client then has these tools:
| Tool | What it does |
|---|---|
| list_projects | Lists your projects (personal + shared) with their ids. Hidden/PIN lists are excluded. |
| list_tasks | Lists tasks in a project; pass a parent task to see its sub-tasks. Each row carries status, priority, due date, assignees and tags, plus counts of the task's notes, comments, bugs, context entries and attachments. |
| get_task | Reads one task in full — everything the task-detail view shows: description, the Notes, Bugs and Context tabs, the comments thread, attachments, status, priority, tags, due/start dates, reminder, recurrence, assignees, milestone, cycle, dependencies, estimate and time spent, number/currency, custom-field values, agent state, and sub-tasks (subtaskDepth, 0–5). |
| create_task | Creates a task. Supports nesting (subtasks for a whole tree, or parentTaskId to add under an existing task) and the same fields update_task takes. |
| update_task | Updates any field on a task by id: complete/incomplete, rename, description, status, priority, tags, due/start dates, reminder, repeat rule, assignees, milestone, cycle, dependencies, estimate, time spent, number/currency, custom-field values, and attachments (add or remove). |
| add_note / update_note / delete_note | Sticky notes on the task-detail Notes tab. |
| add_comment / delete_comment | The task's discussion thread. Posted as your account; @mentions notify teammates. The app has no comment editing, so neither does the MCP. |
| add_bug / update_bug / delete_bug | Bugs logged against a task (Bugs tab). Bugs take the same fields as a task but never nest. |
| list_context | Reads a task's working-context notes (the task-detail Context tab) — load this before working a task. Only that tab; use get_task for the rest. |
| add_context | Appends a context note to a task (findings, decisions, repo layout). Tagged as written by the agent. |
| update_context | Edits an existing context note by its entry id. |
| delete_context | Removes a context note by its entry id. |
Writes go through the same rules as the app, not just the same fields. Completing
a task stamps completedAt and cascades to its sub-tasks; ticking a repeating
task rolls its due date forward instead of closing it; a terminal status keeps the
checkbox in agreement; renaming a task with sub-tasks renames its sub-list. On
shared lists every write stamps the editor and the per-task unread marker, so
collaborators see the agent's changes exactly as they'd see a teammate's. Statuses,
priorities, tags, milestones, cycles and custom fields accept either the stored id
or the label you see in the app, and an unknown value is refused with the list of
valid ones rather than written through.
Deleting whole tasks is deliberately not exposed — the agent can close, edit and annotate them, but removing them stays a decision you make in the app.
Examples of what you can ask your agent:
- "Add a task 'Ship v2 release' to my Roadmap project with sub-tasks for QA, docs, and changelog."
- "Create a bug ticket under the 'Auth' task and attach ./crash.log."
- "Read the context on the 'Auth' task, then note what you changed when you're done."
- "Open the 'Auth' task and summarise its notes, comments and logged bugs."
- "Move the 'Auth' task to In Review, assign it to [email protected], and comment that the PR is up."
- "Log a bug on the 'Checkout' task for the crash I just hit, priority Urgent."
Identify a project by title (case-insensitive) or id — run list_projects
first if a title is ambiguous. Attachments are capped at 10 MB and reject
executables, matching the app. Files on a shared list can only be uploaded by
that list's owner; collaborators can still attach links.
The MCP server speaks JSON-RPC over stdio (status goes to stderr). It's a local process your MCP client starts on demand — nothing is exposed to the network. Tool schemas avoid
$ref/$defsand never emit a property-less object, so they work with strict clients (Cursor with non-Claude models, Gemini CLI) too. Those clients sometimes send structured arguments as JSON strings; the server parses them, sosubtasks,attachmentsandassignedTowork either way.
If Cursor shows the server as red / "Client closed"
- Sign-in is not the cause — since v0.1.9 an unpaired machine still connects
and reports the fix in the tool result. Run
listr-agent loginand retry the call; no restart needed. npxmust be on the PATH of the GUI app, which does not read your shell rc files.which npxin a terminal, then put that absolute path incommand.- On Windows, use the
cmd /cform above. - Cursor hides server output: Help → Toggle Developer Tools → Console, or the Output panel's MCP channel, shows the real error.
Configuration
Everything has a sensible default. To override, set environment variables or copy
.env.example to .env next to the package (or in the
directory you run from). Common knobs:
| Variable | Default | Purpose |
|---|---|---|
| LISTR_AGENT_AUTH | pairing | pairing (code) or password (bot user) |
| LISTR_AGENT_SOURCE | own | own · collab · both |
| LISTR_AGENT_ALLOW_COLLAB | 0 | required 1 for collab/both |
| LISTR_AGENT_NAME | hostname | display name for this machine |
| LISTR_AGENT_HOME | ~/.listr-agent | config/state directory |
| CLAUDE_MODEL | opus | e.g. claude-opus-4-8 |
| CLAUDE_PERMISSION_MODE | acceptEdits | Claude permission mode |
| CLAUDE_ALLOWED_TOOLS | Bash Edit Write … | tools Claude may use |
| CLAUDE_DANGEROUS | 0 | 1 → run Claude with --dangerously-skip-permissions |
| CLAUDE_TIMEOUT_MS | 1800000 | per-run timeout (30 min) |
| WORKDIR | ~/.listr-agent/repos | where remote repos are cloned |
| BITBUCKET_USERNAME + BITBUCKET_APP_PASSWORD | — | Bitbucket app-password auth |
| BITBUCKET_TOKEN | — | Bitbucket access token (alternative to app password) |
The Listr project's public Firebase web config is baked in, so you don't
need to supply it. Forking Listr? Point the agent elsewhere with FIREBASE_*
and LISTR_PAIRING_ENDPOINT — see .env.example.
State on disk
All under ~/.listr-agent (override with LISTR_AGENT_HOME):
machine-id— stable id for this machineauth/— the persisted Firebase session (refresh token; mode 0600)credentials.json— connection marker (email/uid; mode 0600)repos.json— your repo allowlistrepos/— cached GitHub clones
listr-agent disconnect removes the session and signs this machine out.
Security model (please read)
- Repo allowlist is the boundary — untrusted cloud repo values are only ever resolved against what you added locally; everything else is refused.
- Own tasks by default. Running collaborators' tasks (
collab/both) is a deliberate opt-in (LISTR_AGENT_ALLOW_COLLAB=1). - Per-machine session.
disconnectcuts a machine off. - No auto-merge. Output is always a PR you review.
CLAUDE_DANGEROUS=1removes Claude's permission prompts for fully unattended runs — only use it on repos/machines where that's acceptable.
Troubleshooting
gh not authenticated→gh auth login(needs push/PR rights on the repo).- Task is skipped: "not in this machine's repo allowlist" →
listr-agent repos add --slug owner/repoon the machine that should run it. - Nothing happens after handoff → run
listr-agent doctorto confirm the machine is signed in, the source includes the task's list, and its repo is allow-listed. Add--verbosetostartto watch each step live. Saved session expired→listr-agent loginonce more.- Claude seems stuck → it streams progress; a long pause usually means a slow
step, not a hang. Raise
CLAUDE_TIMEOUT_MSfor big tasks.
Part of Listr. Source:
ferrousdesigner/listr-ce under
tools/listr-agent.
