tailr
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Warn
- process.env — Environment variable access in bin/tailr.js
- network request — Outbound network request in bin/tailr.js
- process.env — Environment variable access in demo/serve.js
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Markup and iterate your projects live with your agent.
##### #####
... ##### #####
... #####
........... ######### ##### ##### #### ####
........... ############ ##### ##### #### #####
..... ##### ##### ##### ##### ##########
................ ............... ..... ##### ##### ##### #####
............... ................ ..... +############ ##### ##### #####
..... ##### .#### ##### ##### #####
...... . ##### .##### ##### ##### #####
........ ######### #### ##### ##### #####
....... ###### #### ##### ##### #####
Tailr
Mark up a running dev server and hand the changes to your coding agent as one batch.
[!TIP]
Quick start
Paste this to your coding agent:
Set up Tailr so I can mark up my running dev server visually and hand you the changes as one batch. Fetch: https://raw.githubusercontent.com/gcrft123/tailr/main/PROMPT.md and follow it exactly, without summarizing it.
https://github.com/user-attachments/assets/bb164693-9f44-4e16-95c4-7357c798ab38
Get started
Paste this to your coding agent:
Set up Tailr so I can mark up my running dev server visually and hand you the
changes as one batch. Fetch
https://raw.githubusercontent.com/gcrft123/tailr/main/PROMPT.md
and follow it exactly, without summarizing it.
Or run it yourself, if you'd rather your agent didn't fetch anything, then ask
it to start a session:
npx -y @gcrft123/tailr init
Either way, init edits four things in your project and nothing else:
- adds
@gcrft123/tailrto your devDependencies - registers its MCP server in
.mcp.json, and in.cursor/mcp.jsontoo if the
project already uses Cursor - writes the agent's operating rules into your
AGENTS.md/CLAUDE.md,
between markers of its own - adds
.tailr/to your.gitignore, if the project is a git repository
Re-running it is safe: it rewrites its own block and leaves everything around it
alone. --no-mcp and --no-install opt out of either half; --file <path> puts
the rules somewhere else.
The agent then starts a session against your dev server, hands you a review URL,
and watches for your first batch. See PROMPT.md for what it follows.
Try it first
npx -y @gcrft123/tailr demo
That starts a small sample application, proxies it, and prints a review URL —
nothing installed into a project, no agent involved. Hold Alt, mark a few things,
press Send, then run npx -y @gcrft123/tailr pull from the same directory in
another terminal to print the batch an agent would receive.
As a plugin
The marketplace edits nothing in your project, and Tailr is fetched with npx
when a session starts, so there is nothing to install first. You get the MCP
server, the review loop's operating rules as a skill, and a command that opens a
session and hands you the review URL.
Claude Code
/plugin marketplace add https://github.com/gcrft123/tailr.git
/plugin install tailr@tailr
Use the git URL, not the owner/repo shorthand: the Claude Code app clones the
shorthand over SSH and has nothing to answer the host-key prompt with, so the add
hangs and then fails. Updating is /plugin → Update, which moves you to the
last release: the listing is read from the default branch, but the plugin it
installs is pinned to the release tag.
Cursor
Import https://github.com/gcrft123/tailr as a Team Marketplace (Dashboard →
Settings → Plugins). To load it on this machine only, copy plugin/ to~/.cursor/plugins/local/tailr and reload the window.
Codex
codex plugin marketplace add https://github.com/gcrft123/tailr.git
codex plugin add tailr@tailr
GitHub Copilot CLI
copilot plugin marketplace add gcrft123/tailr
copilot plugin install tailr@tailr
Antigravity CLI
agy plugin install https://github.com/gcrft123/tailr
That reads the same extension manifest Gemini CLI does, and brings the skills and
the MCP server with it. Gemini CLI still takes gemini extensions install https://github.com/gcrft123/tailr.
Everywhere else — Windsurf, OpenCode, Cline, Amp, and the rest of the agents
that read a global skills/ directory:
npx skills add gcrft123/tailr -g
That puts tailr-start, tailr-review and tailr-config in the shared~/.agents/skills directory those agents read, symlinked into the folders of the
ones that keep their own. It does not register the MCP server; for that, use a
marketplace or extension command above, or tailr init.
Not every route namespaces a skill, so what the commands are called depends on
the way in:
| Installed with | Commands |
|---|---|
| Claude Code, Codex or Copilot marketplace | /tailr:start /tailr:config |
| Cursor | /start /config |
Antigravity, Gemini, npx skills add |
/tailr-start /tailr-config |
This is an alternative to tailr init, not an addition. The plugin suits someone
reviewing across several projects; init suits a project that wants Tailr in its
own setup, and is the only one of the two that writes to your repository. Running
both is harmless — the rules are the same text either way.
Start a session
npx tailr --target http://localhost:3000
That assumes Tailr is in the project, which init sees to; npm install --save-dev @gcrft123/tailr is the same thing by hand, and npx @gcrft123/tailr --target … skips it altogether.
Tailr proxies your dev server on http://localhost:4100 and injects its overlay
into the HTML. Your application is not modified — no script tag, no build step, no
config. Hot-reload WebSockets pass through untouched.
npx tailr # proxies http://localhost:3000
npx tailr --target <url> # a different dev server
npx tailr --port <n> # serve Tailr somewhere else
npx tailr -- npm run dev # start the dev server too, then proxy it
Review at the Tailr URL, not the original one. A session writes nothing to your
repository except .tailr/session.json, so the CLI can find it.
Marking up
Hold Alt to arm — ⌥ Option on a Mac, or whichever key you have set under
Settings. While it is held:
| Gesture | Result |
|---|---|
| Left-click | Comment on an element, and ask for versions or a slider if you want to compare |
| Right-click | Stage an element for removal (right-click again to undo) |
| Double-click text | Edit text in place |
| Shift-click | Mark a spot rather than an element — to ask for something new there, or to note the place. Middle-click does the same, if you have one |
Release the key and you can control the application again. Double-tap it to latch
markup on for keyboard use. Marks persist in the browser across reloads, span
routes, and survive the reload after the agent has worked.
The island in the corner shows what is staged; hover it for the list. Drag it to
any corner if it's covering page content.
Versions and sliders
A comment on an element, or on a spot, can ask for more than one answer. The
composer carries a 1× button next to Add; click it for 2×, 3×, 4×, and the
agent builds that many versions of the change instead of one. The button beside
it asks for a slider instead — one number you scrub on the page, for anything
continuous like a glow, a depth, a scale. A mark can ask for both.
After the reload, a small pill sits on the element. For versions it carries a tab
each: hover one and the pill widens to the name the agent gave it while the page
switches to it live, so you compare the real thing rather than two descriptions of
it. A slider's pill carries the control — drag it and the page follows.
Click a tab to keep that version, or Keep to hold a slider where you left it.
Keeping is itself a mark: it goes into your batch, and the next Send makes it
permanent and clears the rest out of the source. The × on its row in the island
keeps none of it.
Ending a session
End session is at the bottom of the island's panel. It asks first, and the
card says what you are agreeing to: marks you never sent are discarded, and Tailr
stops proxying, so it names the address your app goes back to (or tells you the
dev server is stopping too, if Tailr started it).
Confirming runs a cleanup pass first. Anything you never decided — versions you
did not choose between, a slider you did not keep a value on — goes to the agent
as one last batch that takes it, and the switches guarding it, out of your source.
That scaffolding is Tailr's, and it shouldn't outlive the session that asked for
it. Then the server stops, the overlay clears what it kept in your browser, and it
takes itself off the page.
If the agent isn't listening, End anyway leaves without waiting; Tailr says so
on the way out rather than pretending the cleanup happened.
Settings
Two things about Tailr are yours to set rather than the project's:
| Setting | Values | Default | What it does |
|---|---|---|---|
sfx |
true / false |
true |
A short sound on each action — a mark made or dropped, a batch sent, a version picked, a run closing |
modifier |
alt ctrl cmd |
alt |
The key you hold to arm marking |
Ask your agent with the config command your install gave you — see the table
above — or set them yourself:
npx -y @gcrft123/tailr config sfx:false modifier:cmd
Either way they are written to ~/.tailr/config.json and hold across every
project. With no arguments the command prints where they stand. A change made
while a session is up lands on the open review page without a reload.
The agent side
Run these from the same project directory, while a session is up.
tailr status # is a batch waiting? exit 0 if yes, 3 if not
tailr wait # block until one is; exit 0 waiting, 3 timed out, 2 session ended
tailr pull # lease the pending batch, printed as JSON on stdout
tailr pull --wait # lease it, blocking until one arrives
tailr variants <ref> "First name" "Second name"
# name the versions you built for a mark that asked for several
tailr slider <ref> --min 0 --max 100 --value 40 --label "Glow" --unit "%"
# report the parameter you wired for a mark that asked for a slider
tailr progress <ref> # one mark applied — the reviewer sees it land, live
tailr done # the run finished
tailr fail "reason" # it returned incomplete
tailr pull prints:
{
"id": "r1",
"sentAt": "2026-08-30T01:00:06.545Z",
"marks": [
{
"ref": "01",
"type": "comment",
"route": "/invoices",
"address": "InvoiceTable.tsx:20",
"selector": "body > div > section:nth-of-type(2) > div:nth-of-type(3)",
"element": "Bellweather Ltd",
"comment": "Overdue pills should link to the invoice",
"orphaned": false
}
]
}
type is one of comment, remove, text, point, choice. A text mark
carries before and after. A point mark carries page coordinates x/y
instead of an element, and its comment says whether the reviewer wants something
new there or is noting the spot. orphaned: true means the element was gone when
the batch was sent — the address is the last one known, and the mark is worth
raising with the reviewer rather than guessing at.
Versions. A mark carrying "variations": 3 asks for three answers to the same
comment, built together so the reviewer can compare them on the running page.
Guard each one on the attribute Tailr sets on <html> for that mark —[data-tailr-var-03="2"] .card { … }, with version 1 also being what renders if
the attribute is absent — then name them in order with tailr variants 03 "Softer edges" "Full width" "Two columns".
Sliders. A mark carrying "slider": true asks for one continuous parameter
instead. Build it behind data-tailr-slide-03 on <html>, with the default being
what renders if the attribute is absent, then report the range: tailr slider 03 --min 0 --max 100 --value 40 --label "Glow" --unit "%". A mark can ask for
versions and a slider together; do both.
Closing either. What comes back later is a choice mark. For versions it
carries variantOf and variant: keep that version as plain code and take the
others and the guards out with it, or at variant: 0 keep none of them. For a
slider it carries sliderOf and value: bake that number in and remove the
switch, or at value: null put the element back as it was.
Where address comes from. Nothing standard tells a page which file an
element came from, so Tailr reads whatever your dev tooling already emits:data-v-inspector (vite-plugin-vue-inspector), data-inspector-relative-path
(react-dev-inspector), data-astro-source-file, Svelte's __svelte_meta, a
generic data-source, and React 18's development fibers. Emitdata-tailr-source="Component.tsx:20" yourself and that wins. With none of them
present address is null and the mark still carries its selector, its text, and
its route — which is the fallback, not a failure.
Don't wait to be told. tailr wait hangs on the session's event stream and
returns within a moment of Send being pressed — no polling, and no asking the
reviewer to announce every batch. Run it as a background process and treat its
exit as the notification:
tailr wait && tailr pull
It returns immediately if a batch is already waiting, so none can be missed.--timeout <seconds> bounds the wait; without it, it waits as long as the session
lives.
Report progress as you go. Each tailr progress <ref> empties that mark on
the reviewer's screen while they watch — the difference between a tool that looks
stuck and one that looks like it is working.
Always close the run. Until tailr done or tailr fail arrives, the reviewer
cannot send another batch — and if you never answer, they can take that batch back
and send it again. If you cannot finish, tailr fail with what happened; Tailr
does not guess at causes, it points the reviewer back to you.
The whole contract, including the events to listen for when a version or a slider
has to re-render rather than restyle, is in the rules tailr init writes into
your agent instruction file.
As an MCP server
tailr init registers this for you, and the plugin brings it
along without touching your project at all. By hand, most clients take:
{
"mcpServers": {
"tailr": { "command": "npx", "args": ["-y", "@gcrft123/tailr", "mcp"] }
}
}
Prefer it to the CLI where you can: tool descriptions stay in the agent's context
every turn, so the protocol cannot quietly fall out the way a pasted prompt does.
Same round trip as the CLI:
| Tool | What it does |
|---|---|
tailr_status |
Is a session running, is a batch waiting, and where should the reviewer go |
tailr_wait |
Block until the reviewer sends a batch, so they never have to tell you |
tailr_pull |
Lease the pending batch. wait: true blocks until one arrives |
tailr_variants |
Name the versions you built for a mark that asked for several |
tailr_slider |
Report the range of the parameter you wired for a slider mark |
tailr_progress |
Report a ref, or several refs, as applied |
tailr_done |
Close the run; the reviewer is prompted to reload |
tailr_fail |
Close it as incomplete with a reason, releasing the send lock |
tailr_config |
Read or change the reviewer's settings, when they ask |
The server must still be running — the MCP tools talk to the same session the CLI
does, found through .tailr/session.json. If none is running, the tools say so
and tell you what to ask the user for rather than failing opaquely.
Requirements
Node 18 or newer, and nothing else — Tailr has no dependencies.
Of your dev server it asks almost nothing. Tailr injects into HTML responses and
passes everything else through, so there is no framework list here: if it serves
HTML over http or https, it works. A self-signed certificate is fine, and so is a
server that compresses — Tailr asks for uncompressed HTML and decodes gzip,
deflate or brotli when one arrives anyway. The hot-reload WebSocket is relayed
untouched.
Source addresses are the one part that depends on your setup — see The agent
side for what Tailr reads, and what a mark still carries when a
project emits none of it.
License
MIT.
The interaction sounds are Cuelume — copied
into src/overlay/cuelume.js rather than depended on, under its MIT licence,
which travels with it at the top of that file. It synthesizes every sound through
the Web Audio API, so there are no audio files here either.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found