From 66f934c97413694afa3b7f876d925b97e1fcc6a6 Mon Sep 17 00:00:00 2001 From: Jiho Date: Tue, 21 Jul 2026 20:09:59 +0700 Subject: [PATCH] docs: add a visual command map (docs/commands.html) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The command surface was documented three ways — README, CLAUDE.md, and `tabbrew help` — all of them prose. None of them drew the thing that actually matters: that the CLI straddles three different places (this disk, a loopback bridge to Chrome, and tabbrew.com), and that no command in it can change a tab. So: one self-contained HTML page, diagram-first. Eight sections — - every command as a card, colour-coded by **reach** (offline / loopback 127.0.0.1 / account over HTTPS / GitHub Releases), with a legend that filters the map down to one reach at a time. The reach idea is new; it isn't stated anywhere else, and it's the fastest way to answer "what can this one actually touch"; - both workflows as step diagrams — one request → one script, and the auto-mode loop — each ending on the step where a human presses a button; - how to *turn auto mode on*, from the user's side: the three setup steps with what success looks like for each, and the Accept/Deny card drawn out, since that card is the only screen most people ever read; - the bridge's routes across its three conversations, plus the four gates that make a no-token loopback server defensible; - the six-verb grammar with the fixed DEL → UNPIN → UNGROUP → GROUP → PIN → MOVE phase order, and real `tabs check` output; - the four verdicts, where state lives on disk, and an "I want to…" index. Self-contained by construction: no CDN, font, or image requests, so it renders identically from a `file://` open, `tabbrew docs push`, or any strict-CSP host. Themed through `color-scheme` + `light-dark()` tokens, so system preference and a host's `data-theme` stamp both work from one definition. The page is a hand-written mirror of `registry.ts` — nothing generates it and no test pins it — so CLAUDE.md now says that out loud next to the "adding a command" checklist, and the layout tree lists `docs/`. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 9 + README.md | 4 + docs/commands.html | 1345 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1358 insertions(+) create mode 100644 docs/commands.html diff --git a/CLAUDE.md b/CLAUDE.md index 78ababe..9e46238 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -251,6 +251,13 @@ registry is the *second* gate: declare a new flag in `index.ts` **and** attach i command in `registry.ts`, or it will be rejected at runtime. Adding a command = a row here + a `case` in `index.ts`; help follows automatically. +**One thing does *not* follow automatically:** `docs/commands.html`, the visual command map, +is a **hand-written mirror** of this table — a card per command, coloured by how far the +command reaches (offline / loopback / account / GitHub Releases). Nothing generates it and +nothing tests it, so a new command, a renamed flag, or a reworded summary has to be carried +over by hand or the page quietly goes stale. It is the only file in the repo that duplicates +`registry.ts`; keep the duplication small enough to be worth it. + Help is **three views** over that one table: - the **default** (`printHelp()`) — grouped commands (`GROUPS`, ordered by what the CLI is *for*, so `tabs` leads) + non-`hidden` `GLOBAL_FLAGS` + the `GETTING_STARTED` block that @@ -321,6 +328,8 @@ src/ tabs-push.ts # tabs push — validate, then queue on the bridge tabs-suggest.ts # tabs suggest — queue with a required --note, wait for the verdict tabs-watch.ts # tabs watch — long-poll until the tabs change +docs/ + commands.html # visual command map — hand-written mirror of registry.ts ``` ## Configuration diff --git a/README.md b/README.md index a9ec80a..ee19b2b 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ Every `tabs` command is offline except `serve`/`push`/`suggest`/`watch`, which o talk to `127.0.0.1`. **None of them can change your tabs** — the browser does that, after you click **Run** (or **Accept**). +Prefer a picture? [`docs/commands.html`](./docs/commands.html) is a single-page visual map +of the whole surface — every command colour-coded by how far it reaches, both workflows as +diagrams, the bridge's routes, and the six-verb Script grammar. + ## Install ### Prebuilt binary (recommended) diff --git a/docs/commands.html b/docs/commands.html new file mode 100644 index 0000000..e6c0812 --- /dev/null +++ b/docs/commands.html @@ -0,0 +1,1345 @@ + + + + + + +tabbrew CLI — visual command map + + + + + + +
+
+ tabbrew-cli · v0.5.5 + 18 commands · 4 groups +
+

The terminal half of TabBrew

+

+ TabBrew is a Chrome extension for people running 100–200 tabs. This CLI is how a terminal — + and the AI agent working in it — joins that conversation: it reads your exported tabs, validates + the scripts an agent writes about them, and hands those scripts back to the browser. +

+

+ The one rule worth memorising: no command here can close, pin, group, or move a tab. + The CLI only ever writes files and talks to a loopback port. Tabs move in Chrome, after + you press Run or Accept. +

+ +
+
+ In the browser + TabBrew extension + Sidepanel & developer-mode panel. Exports your tabs, previews a script, runs it. + ✓ the only place a tab moves +
+ +
+ 127.0.0.1:49227
loopback only
+ +
+ +
+ In the terminal + tabbrew + your agent + Runs the bridge, validates and simulates scripts, proposes changes, waits for your answer. + writes files · never touches chrome.* +
+ + + +
+ On the internet + tabbrew.com + Your account and the sidepanel Docs view. Only the login and docs commands go here. + 6 of 18 commands +
+
+
+ +
+ + +
+
+ 01 · The map +

Every command, coloured by how far it reaches

+

+ The groups below are the same ones tabbrew help prints. The colour is the extra + idea: how far the command's blast radius goes. Pick a reach to isolate it. +

+
+ +
+ + + + +
+ +
+ Showing all 18 commands. + +
+ +
+ +
+
+

Tabs

+ organize your Chrome tabs — the reason the CLI exists +
+
+
+

tabs serve

+

Start the local bridge the extension exports your tabs to.

+
--port <n>--out <path>--no-history
+

Binds 127.0.0.1 and blocks until Ctrl+C — give it its own shell.

+
+ +
+

tabs watch

+

Wait for the extension to report a tab change.

+
--timeout <s>--since <n>--changes-only--json--port <n>
+

Long-polls, so an idle browser costs nothing. A timeout prints nothing and still exits 0.

+
+ +
+

tabs list

+

Show the tabs the extension last exported.

+
--json
+

A snapshot on disk, not a live query — check savedAt before trusting tab ids.

+
+ +
+

tabs check <file>

+

Validate a TabBrew Script, with a before/after preview.

+
--snapshot <f>--json
+

Exits 1 on any parse error, so it drops into pre-run gates. Takes - for stdin.

+
+ +
+

tabs push <file>

+

Send a script to the extension to preview & run.

+
--port <n>
+

It does not run anything. The script waits in the panel until you click Run — which is why it isn't called run.

+
+ +
+

tabs suggest <file>

+

Propose a script with a note, and wait for the answer.

+
--note <text> required--wait <s>--no-wait--json--port <n>
+

Always exits 0 — a Deny is an answer, not a failure.

+
+ +
+

tabs history

+

Show what changed between exported tab states.

+
--limit <n>--json--clear
+

One delta per line, newest last. It remembers tabs you have since closed--clear deletes it.

+
+ +
+

tabs prompt

+

Print the interactive TabBrew Script skill prompt.

+
--variant <v>
+

The same prompt init installs as the tabbrew-tabs skill — for pasting somewhere by hand.

+
+
+
+ +
+
+

Docs

+ send HTML into the sidepanel — like this page +
+
+ + + + + +
+
+ +
+
+

Account

+ who you are to tabbrew.com +
+
+ + + + + +
+
+ +
+
+

Setup

+ get the CLI, and your agent, ready +
+
+
+

init

+

Set up an AI agent to use tabbrew, plus the two tab skills.

+
-g, --global--dry-run--uninstall-y, --yes--agent <id>--variant <v>--no-skill
+

Writes an awareness doc + a managed block in CLAUDE.md, and installs tabbrew-tabs and tabbrew-auto. Idempotent; a re-run reports unchanged.

+
+ +
+

update

+

Update the installed binary to the latest release.

+
--check--json
+

Verifies SHA-256, then atomically renames over itself. Refuses to run from a source checkout; --check always exits 0.

+
+ +
+

tools repo-info hidden

+

Demo: orchestrate git via Bun shell for repo stats.

+
no options
+

Listed only under help --all. Guards every call with which() before shelling out.

+
+ +
+

help

+

Show the command listing.

+
--all
+

Three views over one table: default, tabbrew <cmd> --help for one command, and help --all for flags, env vars and hidden rows.

+
+
+
+ +
+
+ + +
+
+ 02 · Two ways to use it +

One request, or a standing watch

+

+ Both paths end the same way: a human presses a button in Chrome. What differs is who starts + the conversation — you, or the agent noticing something. +

+
+ +
+
+
+

A · One request → one script

+

You ask for something. The agent answers once.

+
+
    +
  1. + You + + Send to Claude Code + In the sidepanel — your open tabs land on the bridge. (Or Copy AI Prompt and paste them.) + +
  2. +
  3. + CLI + + tabbrew tabs list + The agent reads the snapshot: windows, groups, tab ids, titles, URLs. + +
  4. +
  5. + Agent + + writes plan.txt + A TabBrew Script — one op per line, following the tabbrew-tabs skill. + +
  6. +
  7. + CLI + + tabbrew tabs check plan.txt --snapshot tabs.json + Line-numbered parse errors, a simulated before/after, and a count of what would close. + +
  8. +
  9. + CLI + + tabbrew tabs push plan.txt + Queued on the bridge. The extension pops it and shows a preview. Still nothing has moved. + +
  10. +
  11. + You + + click Run + Chrome executes it. This is the only step in the whole diagram where a tab actually changes. + +
  12. +
+

+ No bridge? The same loop works by hand: Copy AI Prompt → paste to your agent → + tabs check → paste the script into developer mode → Run. +

+
+ +
+
+

B · Auto mode → a standing watch

+

The agent watches and offers. You judge each offer.

+
+
    +
  1. + Setup + + tabbrew tabs serve & + Auto mode on + The bridge in a second shell, and the Auto switch in the sidepanel's Send to Claude Code card. + +
  2. +
  3. + 1 · Wait + + tabbrew tabs watch --timeout 60 + Blocks until tabs actually move, then prints the diff plus the snapshot. No output means nothing changed. + +
  4. +
  5. + 2 · Decide + + default: do nothing + Only propose a concrete win — exact duplicates, ≥3 loose tabs on one topic, about:blank clutter. A high tab count is not a problem. + +
  6. +
  7. + 3 · Check + + tabbrew tabs check plan.txt --snapshot … + Never propose something that hasn't parsed and simulated cleanly. + +
  8. +
  9. + 4 · Offer + + tabbrew tabs suggest plan.txt --note "…" + The note is one plain sentence in your language, destructive part first. It's the only thing most people read. + +
  10. +
  11. + 5 · You + + Accept / Deny + A card in the panel: the note, the preview, two buttons. Deny takes an optional reason. + +
  12. +
  13. + 6 · Listen + + verdict → back to step 1 + A denial is a standing rule, not a retry signal. Never re-send the thing you were told no about. + +
  14. +
+

+ In auto mode the agent does not ask you to confirm a close in chat — the Accept card + is the confirmation, and the --note is where the closes are named. +

+
+
+
+ + +
+
+ 03 · Turning it on +

Auto mode, from the human's side

+

+ Loop B above is what the agent does. This is the other half — three things you set up + once, and one decision you make each time a card appears. +

+
+ +
+ +
+

You do three things. The agent does the rest.

+
+
+ 1 + Terminal + Start the bridge + tabbrew tabs serve + It binds 127.0.0.1 and blocks until Ctrl+C — give it a second shell, never the one you're still working in. + ✓ TabBrew bridge · ready on 127.0.0.1:49227 +
+ +
+ 2 + Chrome + Switch Auto mode on + sidepanel → Manage Tabs → Send to Claude Code + Flip the Auto mode switch on that card. Keep the sidepanel open — closing it pauses the whole stream. + ✓ tabs watch starts returning changes +
+ +
+ 3 + Chat + Ask the agent to watch + "keep an eye on my tabs" + That's what loads the tabbrew-auto skill: the loop, the rules for writing a note, and the rule that a denial is permanent. + ✓ it reads tabs history first, to catch up +
+
+
+ +
+

Then you browse. Until a card appears.

+

+ This is the entire interface. The note is one sentence written in whatever language + you've been using, and it leads with anything that closes tabs — so you can decide + without reading a diff. +

+
+
+
+
TabBrew · suggestion
+
+

ปิดแท็บ YouTube ที่ค้าง 6 อัน แล้วรวม github 5 แท็บเป็นกลุ่ม Code

+
+ − 6 tabs close + + group "Code" · 5 tabs +
+
+ Accept + Deny +
+
+
+

+ Nothing in your browser has changed at this point. The script is only a proposal + sitting in the panel. +

+
+ +
+
+

Accept

+

Chrome runs the ops. This is the moment tabs actually move — the first and only one in the whole loop.

+
+
+

Deny — and say why

+

Nothing changes. The reason you type comes straight back to the agent and becomes a standing rule for the session: "อย่าปิด youtube เปิดฟังเพลงอยู่" means YouTube is off the table from now on, not just this once. That round trip is what stops a loop re-proposing what you just rejected.

+
+
+

Ignore it

+

You weren't looking. The agent leaves it alone rather than stacking a second suggestion on top — the bridge holds exactly one, and a new push would silently replace it.

+
+
+
+

+ No double-confirming. In auto mode the agent will not ask you in chat before + closing anything — this card is the confirmation, and the note is where the closes are + named. That's the one point where the tabbrew-auto skill deliberately + contradicts tabbrew-tabs, which does require an explicit "yes" in chat. +

+
+ +
+
+

When it isn't working

+
+ + + + + + + + + + + + + + + + +
What you seeWhat's actually wrong
nothing is listening on 127.0.0.1:49227Step 1. The bridge isn't running, or that shell was closed.
tabs watch keeps timing out while you're clearly browsingStep 2. Auto mode is off — or the sidepanel is closed, which pauses everything.
The agent says it can't see a snapshotAn extension build predating auto mode. It sends bare tabs; run tabs list for the table.
+
+
+ +
+

How it stops

+
+
+

You say so

+

The loop ends when you tell it to, and the pending suggestion just expires.

+
+
+

Ctrl+C the bridge

+

With tabs serve gone, tabs watch reports nothing is listening and the loop can't continue.

+
+
+

Three denials in a row

+

The agent stops on its own — that many refusals means its read of what you want is wrong, so it asks you directly instead of guessing a fourth time.

+
+
+
+
+ +
+
+ + +
+
+ 04 · The bridge +

What actually crosses 127.0.0.1:49227

+

+ tabs serve is a small HTTP server holding three conversations. Protocol 2, + no auth token — the loopback bind is the entire boundary. Both ends update independently + (extension via the Web Store, CLI via tabbrew update), so no route ever changes + shape; new capability gets a new route. +

+
+ +
+
+
+

1 · Tabs in

+

the extension is the only source of truth about your tabs

+
+
+
+ Extension + POST /tabs + bridge + Saves tabs.json (mode 0600), bumps the version, appends one delta line to the change log. +
+
+ bridge + GET /tabs?since=&wait= + tabs watch + Long-poll, held open until a newer version exists. Never pops — since is what stops a watcher seeing the same version twice. 204 on timeout. +
+
+ bridge + GET /history?limit= + caller + The delta log — what changed between versions, not a series of snapshots. +
+
+
+ +
+
+

2 · Script out

+

one unclaimed script at a time; a new push replaces it

+
+
+
+ tabs push / suggest + POST /suggestion · POST /script + bridge + Both names write the same slot, so an old CLI still reaches a new bridge. Queuing also clears the previous verdict. +
+
+ bridge + GET /suggestion → GET /script + Extension + A pop: claimed by exactly one poll. A new extension asks for /suggestion (note + id) and falls back to /script's protocol-1 shape on a 404. +
+
+
+ +
+
+

3 · Answer back

+

the half that makes a loop able to listen

+
+
+
+ Extension + POST /decision + bridge + Carries accepted / denied / stale / failed, plus the reason you typed. +
+
+ bridge + GET /decision?id=&wait= + tabs suggest + Long-poll for the answer to one suggestion. Doesn't clear it, so a retried wait can still read it. +
+
+ bridge + GET /health + both ends + Cheap reachability: { ok, protocol: 2, tabsVersion, hasPending, history }. How a client tells "the server is gone" from "this connection dropped". +
+
+
+
+ +
+
+

Loopback bind, hardcoded

+

127.0.0.1 is not configurable. Anything already running as you on this machine can reach it — that's the accepted trade.

+
+
+

Host must match

+

127.0.0.1:port or localhost:port. This is the anti-DNS-rebinding gate: a rebound page's GETs are same-origin, so they carry no Origin at all.

+
+
+

Origin, when present

+

Must start chrome-extension://. Browsers always attach Origin to non-GET, so this blocks a drive-by POST /tabs. Invisible to curl.

+
+
+

0600 on disk

+

Your tabs are browsing history, not a revocable token. Both the snapshot and the change log are written owner-only.

+
+
+
+ + +
+
+ 05 · TabBrew Script +

Six verbs, one per line

+

+ The DSL an agent writes and tabs check validates. Ids are Chrome tab ids straight + from the snapshot. Blank lines and # comments are ignored. +

+
+ +
+
+ DEL + DEL <id> [<id> …] + Close tabs. The only destructive verb — check counts these separately for a reason. +
+
+ GROUP + GROUP <id> … "Name"
GROUP <id> … @<gid>
+ Into a new named group, or into an existing group by id. +
+
+ UNGROUP + UNGROUP <id> [<id> …] + Pull tabs out of whatever group they're in. +
+
+ PIN + PIN <id> [<id> …] + Pin to the front of the window. +
+
+ UNPIN + UNPIN <id> [<id> …] + Unpin. Runs early, so a tab can be unpinned and regrouped in one script. +
+
+ MOVE + MOVE <id> <index> [@win=<wid>] + Reposition, optionally into another window. The only single-id verb. +
+
+ +
+ DEL + UNPIN + UNGROUP + GROUP + PIN + MOVE +
+

+ Line order does not matter. The extension collects every op and runs the six phases in + this fixed order, and the CLI's simulator mirrors it exactly — which is why a preview is + directionally correct rather than a byte-exact prediction of Chrome's final layout. +

+ +
+
+

A script

+
# close two duplicates, file the rest
+DEL 101 102
+GROUP 103 104 "Code"
+PIN 105
+MOVE 106 0 @win=1
+BOGUS 9
+
+
+

tabbrew tabs check

+
✗ 1 parse error:
+  line 5: BOGUS 9
+            → unknown verb "BOGUS"
+
+✓ Parsed 4 ops  · DEL 1, GROUP 1, PIN 1, MOVE 1
+  affects 6 tabs  ·  2 to close (DEL — destructive)
+

+ Exit code 1. Add --snapshot to get the simulated before/after and the list of + stale ids that were silently dropped. +

+
+
+
+ + +
+
+ 06 · The answer +

Four things a suggestion can come back as

+

+ tabs suggest waits up to 300 seconds and prints one of these. It + always exits 0 — an agent that reads "no" as a failure retries instead of listening. +

+
+ +
+
+ ✓ accepted + You pressed Accept and Chrome applied the ops. + Tabs really moved. Re-read the state before the next idea. +
+
+ ✗ denied + You said no, optionally with a reason that comes straight back. + A standing rule. Never re-send this one; fold the reason into every later decision. +
+
+ ↺ stale + The tabs changed between the snapshot and the click, so the ids no longer line up. + Not a rejection of the idea. Re-read the tabs and offer it again. +
+
+ ! failed + You accepted, and Chrome refused. Nothing moved. + They wanted this — fix the script, don't drop the idea. Reporting it as accepted would leave the agent building on a fiction. +
+
+
+ + +
+
+ 07 · On disk & in the environment +

Where the CLI keeps things

+

Three files, all owner-only, none of them inside your project folder.

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
PathModeWhat it holds
~/.config/tabbrew/credentials.json0600Your login token. Re-asserted to 600 on every save. TABBREW_TOKEN overrides it.
~/.config/tabbrew/tabs.json0600The last export: every open tab's id, title and URL. Overwritten each time.
~/.config/tabbrew/tabs-history.jsonl0600One delta per version, newest last, capped at 500 lines.
+
+ +

+ The one privacy sharp edge. tabs.json only ever holds tabs that are currently + open, but the change log remembers the titles and URLs of tabs you have closed — the only + place the CLI accumulates browsing history at rest. Hence the cap, and three ways off: + tabs serve --no-history, TABBREW_TABS_HISTORY=0, or + tabs history --clear. +

+ +
+ + + + + + + + + + + + +
EnvironmentDefaultWhy you'd set it
TABBREW_TOKENAuthenticate in CI without an interactive login. Wins over the stored file.
TABBREW_SERVE_PORT49227Move the bridge if the port is taken. See the warning below.
TABBREW_TABS_HISTORYonSet to 0 to never write the change log.
TABBREW_BASE_URLwww.tabbrew.comPoint the same binary at staging or a local server.
TABBREW_NO_BROWSERPrint URLs instead of launching a browser (login, docs open).
NO_COLOR / TABBREW_DEBUGDrop ANSI colour; print stack traces on unexpected errors.
+
+ +

+ About that port. The extension only ever talks to 49227 — it's baked into its + host permissions. A custom port works fine for your own scripts, but the extension will report + the bridge as not running. +

+
+ + +
+
+ 08 · Quick index +

I want to…

+
+
+
teach my AI agent this existstabbrew init
+
sign in / check who I amtabbrew login · tabbrew whoami
+
see my tabs from the terminaltabbrew tabs serve → tabbrew tabs list
+
know what a script does before it runstabbrew tabs check plan.txt --snapshot ~/.config/tabbrew/tabs.json
+
hand a script to the browsertabbrew tabs push plan.txt
+
let the agent watch and offertabbrew tabs watch → tabbrew tabs suggest plan.txt --note "…"
+
catch up on what changed while I was awaytabbrew tabs history --limit 20
+
read an HTML report in the sidepaneltabbrew docs push report.html
+
get the newest binarytabbrew update --check → tabbrew update
+
+
+ +
+ + + + + +