
Vibeflow
io.github.zorcecv0.19.3Updated Oct 7, 2026
Kanban for agentic development over MCP — annotate any UI element into a ticket with file and line.
Overview
Vibeflow is a local Kanban task server that lets an assistant read, create, and update UI-fix tickets annotated with CSS selectors and source locations.
- What it does
- Vibeflow runs a local Kanban board and task store, and exposes those tasks over MCP so an agent can list, get, create, update, comment on, attach files to, and claim tasks. Tasks can be created by clicking a UI element in a browser overlay, which captures the CSS selector, URL, and source file location. It also supports parent/child task trees, verification verdicts, review gates, and JSON envelopes for both success and refusal.
- When to use it
- Use it when an agent needs to pick up UI fixes, layout bugs, or small front-end changes with precise element context instead of prose descriptions. It suits projects where a human annotates a running app and an agent implements the resulting tickets.
- Requirements
- Node.js >= 22 and npx or a global npm install of @vibeflow-tools/cli. The MCP server is started with the mcp command and requires a --project path; it runs over stdio or an optional HTTP endpoint. No account, cloud service, API key, or environment variable is declared.
Installation
In SourceWeft
- Open Vibeflow in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
Vibeflow
Tell your AI agent exactly what to fix — by clicking on it.
Vibeflow eliminates the back-and-forth of describing UI bugs in words. Click any element on a page to create a task with its exact CSS selector, URL, and source location. Your agent gets precise, actionable context — no "the button in the top right" needed.
[Vibeflow kanban board — a task is dragged from Todo to In Progress]
▶ Watch the full motion demo (MP4)
Why It Matters
AI agents write code fast, but understanding what to change is slow. Describing a UI issue in prose wastes tokens and produces wrong fixes.
Vibeflow turns visual feedback into structured tasks:
- Click any element → instant task with CSS selector, URL, and source file location
- Track on a Kanban board → see everything at a glance, drag between columns
- Agents implement with context → no guessing, no wrong elements, no wasted iterations
Perfect for small UI fixes, broken layouts, spacing issues, and anything where pointing is faster than explaining.
Install
Run it once with npx — nothing to install:
Or install it globally for day two. This adds the short alias vf:
Requirements: Node.js >= 22. Apache-2.0 licensed. No account and no cloud — every task is a JSON file in your repo.
Quick Start
See It in Action
Drag tasks across the board — Backlog → Todo → In Progress → Review → Done. Drag a card to change its status and the board persists the new column and order, even after a reload. Tag, user and type filters keep their state while you move work, and every change is written straight to the task store — so the CLI and your agent see it immediately.
Break work into a parent / child tree — Turn an epic into child tasks that keep their own status, priority and history. Expand
the tree inline on the card, or create children from the terminal with
vibeflow tasks --add --title "..." --parent <id>. The same hierarchy comes back from
vibeflow tasks --get <id>, so an agent can pick up a leaf without losing the parent.
Get the whole ticket in one panel — Open any card for the full ticket: status, description, tags, priority, author, relations (children and related tasks), an activity feed and file attachments. Changes save automatically, and pasting a screenshot or file anywhere in the panel attaches it to the task instead of your Downloads folder.
MCP Server
Vibeflow exposes its task tools over MCP, so an agent can read and update tickets directly. Configure one server per project: a server resolves its project root once at startup and every tool call uses that root, so it cannot write into the wrong project.
stdio (recommended — no port, nothing to keep running):
--project is required: a spawned client's working directory (often your home directory)
is never trusted, and the root is validated before anything is created. Put this config
with the project — .mcp.json at the repo root, or your editor's workspace config using
${workspaceFolder}.
HTTP (a server you keep running):
Loopback-only until you configure a token. Both transports mount the identical tool set; only the transport differs.
Do not run one global server with a per-call project argument — that is deliberately not supported. One process serves one root, resolved at startup.
A type:"Research" task is refused review with RESEARCH_REPORT_REQUIRED until a .md
report is attached, and over MCP the way to do that is attach_file with a .md filename
— the gate looks for a .md among a task's files, so the name is what counts. There is no
--report-file equivalent on this surface; the CLI flag is named in the refusal's
suggestion for CLI callers only. A Research task must not carry a verification verdict
(RESEARCH_VERIFY_NOT_ALLOWED) — it has no annotated UI to verify.
MCP results
A tool's result carries its payload as one JSON document in content[0].text — that holds for
every tool-level success and every tool-level refusal. There are exactly two shapes that are
not JSON: a protocol-level failure — arguments the tool's own schema rejects, or the name of a
tool that does not exist — comes back as result.isError === true with content[0].text =
MCP error -32602: … and no envelope to parse. So check isError first and parse the text as JSON
only when it is absent.
Those two protocol-level failures are told apart by what the message names, not by the code —
the SDK reports an unknown tool through the same input-validation path, so both carry -32602 even
though JSON-RPC reserves -32601 for "method not found":
The schema-error class (-32602) — no vibeflow code, by design
An input the tool's own schema rejects (create_task with no title, attach_file with no
contentB64, list_tasks with limit:-1) is refused by the MCP SDK's input validation, before
any vibeflow handler runs. vibeflow therefore never sees the call and cannot attach a code or a
suggestion to it. What arrives is:
Three properties, all deliberate and all load-bearing:
- It has no entry in the code table below, and never will. Every code in that table is a vibeflow
domain code that a handler produced on purpose.
-32602is the JSON-RPC invalid params code; it is not a member of vibeflow's vocabulary, and-32602will never appear as a row there. - It names the offending field (
… at title,… at contentB64) — the last word of the message is the field you got wrong. - Its code string is client-dependent, so do not key on it. Some clients normalise this class into
a string of their own (the Pi MCP adapter reports it as
call_failed, for instance). That label belongs to the client, not to vibeflow: the same refusal isMCP error -32602from a plain JSON-RPC client, and greping for one client's label in another's output is how a consumer concludes the behaviour differs when it does not.
Recognise the class by its shape, not by any code string: a result with isError === true whose
text carries an input-validation message naming a field. The remedy is always the same — send the
field it names, with the type the schema asks for. An unknown tool is NOT a member of this class: it
names no field, so the rule above does not match it, and its remedy is a different one (the table
above). Keying on the code alone would put the two in one bucket and send the reader after an input
field when the real mistake was the tool's name.
Because tools/list is the only place a client can read the contract before it fails, the input
shapes publish a description for every field an agent commonly gets wrong — id on each task tool
(most take a full id or a unique prefix; export_prompt matches ids exactly, and says so), the
setVerify/verifyReason pair, limit, contentB64, the comment
body, and filename (its extension decides acceptance). Read them there; there is no second chance.
Three rules for the JSON case, all of them the CLI's own conventions:
-
A refusal is the CLI envelope.
{ok:false, error:{code, message, retryable, suggestion?}}— the same shapetasks --jsonwrites to stderr, so one parser reads both surfaces. A tool-level refusal does not setisError; it is an ordinary result whose JSON is{ok:false, …}. -
Every refusal carries a
suggestion, and it means "what to change". A code tells you which rule fired; the suggestion tells you the input that satisfies it.error.suggestionis a non-empty string on every tool-level refusal — including the genericcatchwrappers (LIST_TASKS_ERROR,GET_TASK_ERROR,CREATE_TASK_ERROR,UPDATE_TASK_ERROR,ADD_COMMENT_ERROR,ATTACH_FILE_ERROR,EXPORT_PROMPT_ERROR,VERIFY_TASK_ERROR,PUSH_TASKS_ERROR,CLAIM_TASK_ERROR) — and it names both surfaces where the same gate is reachable: the MCP input or tool and the CLI flag. A shared gate's suggestion is read by the CLI and by MCPupdate_taskalike, so a CLI-only suggestion is an answer an MCP client cannot act on. (verify_taskis the one tool that returns the CLI verify engine's own codes —E_NOT_FOUND,E_NO_BASELINE,E_NO_SELECTORand the rest. Those codes are unchanged; where the engine attaches no text of its own, the tool substitutes a generic recovery line rather than returning a code with nothing to act on.) The one class with nosuggestionis the schema error above, which never reaches a handler. -
"Nothing to claim" is a success.
claim_next_taskon an empty board — or with a valid filter that matched no task — isokwith a payload ofnull, not an error. A claim's payload is theTaskitself, so an object means a task was claimed andnullmeans there was nothing to claim; that is the same answervibeflow tasks --next --jsongives withtask:nulland exit 0. ThedryRunpreview answers the same way, so a preview cannot disagree with the real call. There is noNO_TASKS_AVAILABLEcode. -
Non-failures you must know about ride
notices, an array of{code, message}objects and the same key the CLI puts on its--jsonsuccess payloads. A notice is a non-fatal signal, not a synonym for partial success — branch oncode, never on the array merely being present:A
dryRun:truepreview carriesnotices:[{code:"DRY_RUN", message:"Task would be updated"}](or the matching phrase —Task would be created/claimed,Comment would be added,File would be attached,Verification would run); a review transition carriesGIT_COMMITTEDwith the sha when its auto-commit succeeded and{code:"GIT_COMMIT_FAILED", message:"…"}when it did not. There is nostepskey on this surface any more, and no bare string innotices. (Thewarningstring is a different field and never appears here.)
Related Packages
- @vibeflow-tools/prototyping — in-app variant switching for React with URL persistence.
npm install @vibeflow-tools/prototyping - Live kanban demo — try the vibeflow board in your browser.
Commands
vibeflow kanban [dir]
The Kanban board provides a visual task tracker with drag-and-drop columns, parent/child task trees, agent status display, and file attachments. Create tasks directly on the board or import them from annotated prototypes.
vibeflow serve [target]
Serve HTML prototypes with the annotation overlay — click any element to create a task with its CSS selector, URL, and source location.
vibeflow tasks
Full task management from the command line — designed to be agent-friendly.
JSON output (--json): one envelope, one ok discriminant. Success writes {ok:true, …payload}
to stdout — tasks → {ok:true, tasks:[…], hiddenChildren}, --get → {ok:true, task:{…}},
--add/--edit/--next → {ok:true, task:{…}, next_actions:[…]}. Failure writes
{ok:false, error:{code, message, retryable, suggestion}} to stderr and exits non-zero. Every
non-zero exit emits that envelope — a refusal that printed prose left a machine consumer with empty
stdout and no code. Under --json, stdout is empty or exactly one JSON document on every path,
with no exceptions.
"Nothing to work on" is a success. --next on an empty board — or with a valid filter
(--type Bug, --tag) that matched no todo task — writes
{ok:true, task:null, next_actions:[]} to stdout and exits 0. The key set is the same as a
successful claim's, so task === null is the branch: a claimed task is always an object. A filter that
matched nothing is the same situation as an empty board, not a special case. Without --json the
sentence No todo tasks found. Nothing to work on. and exit 0 are unchanged. The MCP tool
claim_next_task answers the identical situation the same way — see below. (This reverses an earlier
ruling that printed the sentence under --json; NO_TASKS_AVAILABLE no longer exists.)
--user is the exception and always was: tasks --next --user … refuses with E_USAGE (exit 2)
rather than reporting an empty board, because the filter is validated against a candidate-author list
that --next does not build. That refusal is pre-existing behaviour, unchanged by the empty-board
success above.
One code per meaning:
Codes are scoped per command, not global: TASK_NOT_FOUND on tasks means "no such task",
while E_NOT_FOUND on tasks means "no such FILE" (e.g. --report-file), and the two surfaces have
their own schemes. Read the code from the command you called.
This table is the domain-code vocabulary — every row is a refusal a vibeflow handler produced on
purpose, and every one of them carries a suggestion saying how to correct it. The schema-error class
is deliberately absent: an input the tool's own schema rejects is refused by the MCP SDK before
any handler runs, so it has no vibeflow code and no suggestion — see The schema-error class
(-32602) above for how to recognise it.
notices is the CLI's structured field for non-fatal signals the caller should know about,
discriminated by code — it is not a synonym for "partial success", and the array being present
does not by itself mean anything went wrong. On a success payload it is an optional array of
{code, message}, absent on a clean run:
The array is always an array (never a bare object, never a plural notices/warnings split). The
local --edit path emits all of them; the SaaS --edit path can only reach SET_STATUS_DONE (the
rest are decided against the LOCAL task file, which an online board has no equivalent of) — so read
notices as "an array that may or may not be there", never as a fixed set of codes. The MCP surface
also uses the name, for the same concept plus two of its own: DRY_RUN and GIT_COMMITTED, which the
CLI never emits (the CLI says nothing when a commit succeeds). See MCP results.
warning is a different field, and it is per-surface — a bare string everywhere, with a different
meaning on each of the two surfaces that have one:
- the online board's server-passthrough string on the SaaS
--editpayload, and - the verify surface's capture-truncation note on a page-wide
vibeflow verify <id> <tool> --jsonresult (truncated:trueresults carry awarningexplaining the partial view).
So warning is never an object and never means "partial success": a consumer that branches on
notices[].code can never trip over it.
tasks --commit --json also carries autoPush: {attempted, ok, error?}. The auto-push runs in
BOTH modes — --json suppresses its progress lines, never the push — so this is how its outcome
reaches a machine consumer instead of as prose. attempted: false means there was nothing new to
push: a linked existing commit, or autoPush turned off in settings. A failed push is still
ok:true and exit 0, because the commit itself landed; a failed push can be retried separately.
Breaking as of 0.18.0: tasks --json used to return a bare array, --get --json a flat object,
success payloads carried success:true instead of ok:true, and an auto-commit failure exited 1
even though the task had been written. The local notice field is notices (an array), not warning.
vibeflow verify <id> only collects page-health evidence (the element resolves, no new console errors); it does not set a verdict. The agent judges correctness and attests with --set-verify when it moves the task to review — pass (implemented correctly), fail (not correct — blocks review), or cannot with --verify-reason (unverifiable here, recorded in the task's activity).
Task types: Task · Bug · Feature · Enhancement · Research
Task statuses: backlog → todo → in-progress → review → done
Priorities: Critical · High · Medium · Low
vibeflow watch [dir]
Runs until interrupted (Ctrl+C) and prints full ticket details whenever a task is
newly created or moved back to todo — handy as a driver for AI-agent loops that
react to new work. Events can also be written to a file (--output <file>) or POSTed
to a webhook (--webhook <url>).
vibeflow telemetry
No PII is ever collected. User identity is hashed.
What is collected. One command_run event per invocation with a small,
coarse property set:
command— the top-level command (tasks,serve,kanban, …)subcommand— the mode within a command that has one:tasksemitslist/get/add/edit/next/reindex,serveemitsapi(API-only task server) orprototype(an HTML target was given)from_status/to_status— ontasks --editonly: the task's status before and after the edit, drawn frombacklog | todo | in-progress | review | done, so status transitions are queryable as a funnel
Never collected: task ids, titles, descriptions, file paths, selectors, URLs, or any other task content.
Browser Overlay
The overlay is a Shadow DOM panel injected into any page — HTML prototypes or live apps:
- Click-to-annotate — click any element to open a task form, pre-filled with CSS selector, URL, and source location
- Task sidebar — lists open tasks with status badges; click to jump to the annotated element
- Task indicators — numbered markers on annotated elements
- Real-time sync — over WebSocket with live file watching
- Screenshot capture — attach screenshots to tasks via the overlay
- Dark theme — polished dark UI, no configuration needed
- Keyboard shortcut —
Alt+Ato toggle annotation mode - CSP-safe injection — bookmarklet bypasses
script-srcrestrictions
Injection Methods
The overlay can be injected into any page three ways:
Visit /inject on your running server for ready-to-use bookmarklets and snippets.
How It Works
- Overlay — embed the bookmarklet or script into your app, click any element to annotate
- Kanban — open the board to see all tasks at a glance, create new ones directly
- Tasks —
vibeflow tasks --nextpicks the highest-priority task with full context for your agent - Iterate — agent implements, browser reloads, annotate again
Writing Prototypes
Each HTML file is one screen. Use Tailwind CSS, Lucide icons, and Google Fonts via CDN — the annotation contract tells your LLM to use exactly these libraries.
Rules:
- One file per screen — name after the route (
login.html,dashboard.html) - Every meaningful element gets a
data-vibeflow-id— kebab-case, globally unique - Navigate between pages with relative links:
<a href="./page.html"> - Repeat navigation on every page (no shared includes)
Agent Integration
Vibeflow tasks are formatted for AI agents with full context:
- CSS selectors — exact element targeting, no guesswork
- Source locations — file, line, and column where the element is defined
- Screenshots — visual context attached to tasks
- Comments — threaded discussions on each task
- File attachments — research reports, specs, and reference materials
- Git commits — changes linked back to tasks via
[proto:task-id]in commit messages
Agents can also run directly from the Kanban board via POST /api/agent/run, which spawns opencode with full task context.
API
A REST API and tRPC router are available at http://localhost:3700 for integrations and the browser overlay. Key endpoints:
/kanban— live Kanban boardGET/POST /api/tasks— list and create tasksGET/PATCH/DELETE /api/tasks/:id— manage individual tasksGET/POST /api/tasks/:id/comments— task commentsGET/POST/DELETE /api/tasks/:id/files— file attachmentsPOST /api/agent/run— spawn an AI agent for a task/inject— overlay injection helper page
See src/server/server.ts for the full API.
Contributing
test:browser needs a browser once: npx playwright install chromium.
Required checks for a change
Each suite is reachable only through its own script — no script runs the
others — so a green pnpm test says nothing about e2e, integration, or
the browser suite. Before you commit a change, run all of them:
The first three plus build are what the pre-commit hook runs. Integration,
e2e, and browser are not in the hook, so nothing but this list will tell you
they were skipped.
test:coverage reports what the in-process unit tests execute. That is not the whole
story for this package: src/index.ts is almost entirely command dispatch, and the
commands are exercised by the e2e suite, which spawns the CLI as a child process that
v8 coverage cannot follow — so src/index.ts sat at ~16% lines while hundreds of e2e
assertions ran through it. test:coverage:e2e fixes the measurement: it builds a
sourcemapped, unminified CLI into .coverage-cli/ (the shipped bundle is minified and its
source maps are deleted, so it cannot be attributed back to src/**), runs the e2e suite
against it with NODE_V8_COVERAGE, converts and remaps the child counters, and merges them
with the unit counters by source position into coverage/merged/ (text, json, lcov). It
prints the unit-only / e2e-only / merged numbers for src/index.ts on stdout. The merge
toolchain is already present as a transitive dependency of @vitest/coverage-v8; nothing
was added to package.json.
License
Apache-2.0 — see NOTICE for third-party attributions.
Source: packages/cli/README.md at commit 591155d
Tools
0Version history
1- v0.19.3LatestOct 7, 2026


