tabs serve
+Start the local bridge the extension exports your tabs to.
+Binds 127.0.0.1 and blocks until Ctrl+C — give it its own shell.
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 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. +
+ +login and docs commands go here.
+ 6 of 18 commands
+
+ 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.
+
Start the local bridge the extension exports your tabs to.
+Binds 127.0.0.1 and blocks until Ctrl+C — give it its own shell.
Wait for the extension to report a tab change.
+Long-polls, so an idle browser costs nothing. A timeout prints nothing and still exits 0.
+Show the tabs the extension last exported.
+A snapshot on disk, not a live query — check savedAt before trusting tab ids.
Validate a TabBrew Script, with a before/after preview.
+Exits 1 on any parse error, so it drops into pre-run gates. Takes - for stdin.
Send a script to the extension to preview & run.
+It does not run anything. The script waits in the panel until you click Run — which is why it isn't called run.
Propose a script with a note, and wait for the answer.
+Always exits 0 — a Deny is an answer, not a failure.
+Show what changed between exported tab states.
+One delta per line, newest last. It remembers tabs you have since closed — --clear deletes it.
Print the interactive TabBrew Script skill prompt.
+The same prompt init installs as the tabbrew-tabs skill — for pasting somewhere by hand.
Send an HTML file to the TabBrew sidepanel Docs view.
+Local by default: only the absolute path is registered, and it opens as file://. --cloud uploads the content (≤ 2 MB).
List your pushed docs — titles are click-to-open.
+Titles are wrapped in OSC 8 hyperlinks: ⌘/Ctrl-click one in a capable terminal.
+Open a pushed HTML doc in your browser.
+The fallback for terminals that don't do hyperlinks. TABBREW_NO_BROWSER makes it print the URL instead.
Sign in via OAuth device flow and store the token.
+Opens a browser, prints a code. The token lands in ~/.config/tabbrew/credentials.json at chmod 600.
Print the signed-in user.
+Exits 1 when signed out, so CI can gate on it. TABBREW_TOKEN wins over the stored file.
Delete the stored token.
+Removes the credentials file. It cannot unset a TABBREW_TOKEN in your environment.
Set up an AI agent to use tabbrew, plus the two tab skills.
+Writes an awareness doc + a managed block in CLAUDE.md, and installs tabbrew-tabs and tabbrew-auto. Idempotent; a re-run reports unchanged.
Update the installed binary to the latest release.
+Verifies SHA-256, then atomically renames over itself. Refuses to run from a source checkout; --check always exits 0.
Demo: orchestrate git via Bun shell for repo stats.
+Listed only under help --all. Guards every call with which() before shelling out.
Show the command listing.
+Three views over one table: default, tabbrew <cmd> --help for one command, and help --all for flags, env vars and hidden rows.
+ 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. +
+You ask for something. The agent answers once.
++ No bridge? The same loop works by hand: Copy AI Prompt → paste to your agent → + tabs check → paste the script into developer mode → Run. +
+The agent watches and offers. You judge each offer.
+about:blank clutter. A high tab count is not a problem.
+
+ + 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. +
++ 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. +
+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
+ + 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. +
+ปิดแท็บ YouTube ที่ค้าง 6 อัน แล้วรวม github 5 แท็บเป็นกลุ่ม Code
++ Nothing in your browser has changed at this point. The script is only a proposal + sitting in the panel. +
+Chrome runs the ops. This is the moment tabs actually move — the first and only one in the whole loop.
+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.
+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.
+
| What you see | What's actually wrong |
|---|---|
nothing is listening on 127.0.0.1:49227 |
+ Step 1. The bridge isn't running, or that shell was closed. | +
tabs watch keeps timing out while you're clearly browsing |
+ Step 2. Auto mode is off — or the sidepanel is closed, which pauses everything. | +
| The agent says it can't see a snapshot | +An extension build predating auto mode. It sends bare tabs; run tabs list for the table. |
+
The loop ends when you tell it to, and the pending suggestion just expires.
+With tabs serve gone, tabs watch reports nothing is listening and the loop can't continue.
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.
+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.
+
the extension is the only source of truth about your tabs
+tabs.json (mode 0600), bumps the version, appends one delta line to the change log.
+ since is what stops a watcher seeing the same version twice. 204 on timeout.
+ one unclaimed script at a time; a new push replaces it
+/suggestion (note + id) and falls back to /script's protocol-1 shape on a 404.
+ the half that makes a loop able to listen
+accepted / denied / stale / failed, plus the reason you typed.
+ { ok, protocol: 2, tabsVersion, hasPending, history }. How a client tells "the server is gone" from "this connection dropped".
+ 127.0.0.1 is not configurable. Anything already running as you on this machine can reach it — that's the accepted trade.
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.
Must start chrome-extension://. Browsers always attach Origin to non-GET, so this blocks a drive-by POST /tabs. Invisible to curl.
Your tabs are browsing history, not a revocable token. Both the snapshot and the change log are written owner-only.
+
+ The DSL an agent writes and tabs check validates. Ids are Chrome tab ids straight
+ from the snapshot. Blank lines and # comments are ignored.
+
check counts these separately for a reason.
+ + 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.
+
+ 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.
+
Three files, all owner-only, none of them inside your project folder.
+| Path | Mode | What it holds |
|---|---|---|
~/.config/tabbrew/credentials.json |
+ 0600 | +Your login token. Re-asserted to 600 on every save. TABBREW_TOKEN overrides it. |
+
~/.config/tabbrew/tabs.json |
+ 0600 | +The last export: every open tab's id, title and URL. Overwritten each time. | +
~/.config/tabbrew/tabs-history.jsonl |
+ 0600 | +One 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.
+
| Environment | Default | Why you'd set it |
|---|---|---|
TABBREW_TOKEN | — | Authenticate in CI without an interactive login. Wins over the stored file. |
TABBREW_SERVE_PORT | 49227 | Move the bridge if the port is taken. See the warning below. |
TABBREW_TABS_HISTORY | on | Set to 0 to never write the change log. |
TABBREW_BASE_URL | www.tabbrew.com | Point the same binary at staging or a local server. |
TABBREW_NO_BROWSER | — | Print URLs instead of launching a browser (login, docs open). |
NO_COLOR / TABBREW_DEBUG | — | Drop 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. +
+