Skip to content

Group tab commands under tabs, scope flags per command - #23

Merged
colevels merged 1 commit into
mainfrom
feat/tabs-command-group
Jul 19, 2026
Merged

Group tab commands under tabs, scope flags per command#23
colevels merged 1 commit into
mainfrom
feat/tabs-command-group

Conversation

@colevels

Copy link
Copy Markdown
Owner

Summary

run and serve shipped as top-level commands in #22 but never made it into a released binaryv0.5.3 is tagged at the commit before it. No user has them, so they're removed outright: no aliases, no deprecation warnings.

ACCOUNT  login · logout · whoami
DOCS     docs push <file> · docs list · docs open <id>
TABS     tabs check <file> · tabs push <file> · tabs serve · tabs list · tabs prompt
SETUP    init · update · help
  • runtabs push. The old name claimed something the command cannot do: it has no browser access and only queues a script for you to click Run on — which the README had to disclaim twice. Someone typing tabbrew run close-duplicates.tbrew and seeing ✓ Script queued could reasonably believe their tabs had already closed. docs pushtabs push now read as one mental model.
  • servetabs serve, so the whole tab workflow lives under one noun.
  • New tabs list — the counterpart to docs list. tabs serve wrote a file and said nothing more about it; the only way to see the result was cat | jq. Leads with the export's age, since the ids go stale.
  • New src/registry.ts — the command surface as data. printHelp renders from it and assertFlagsAllowed validates against it, so help can't drift from the routing and one command's flag can't leak into another.

Fixes a real bug

parseArgs takes one flat option table, so --port was accepted by every command and read by only one. With a serve on a non-default port:

$ tabbrew run script.tbrew --port 49299
✓ Script queued (2 op(s))          # …delivered to 49227 instead

tabs push now takes --port, and both commands resolve it through a single resolveServePort() — they can't disagree. Off-command flags are now rejected:

$ tabbrew docs push --port 99
✗ --port is not an option for `docs push`.
  `docs push` accepts: --cloud, --title

Also

  • init --skill <variant>--variant — same three prompts as tabs prompt --variant; --no-skill stays the install-or-not switch. Bad variant now throws a typed error instead of a bare Error.
  • tabs push rejects a zero-op script locally instead of surfacing the server's invalid_payload, which read as a bridge failure. (Slightly beyond the agreed scope — but the confusing message came from the error path this PR rewrites, so shipping it as-is meant knowingly keeping a bad message.)
  • Help is grouped, the header line no longer overflows 80 columns, and ENVIRONMENT is split into "things you might set" vs. endpoint overrides.
  • tabs serve startup prints what to do next in Chrome instead of a raw route table (routes still shown under TABBREW_DEBUG).
  • Table helpers → src/table.ts, shared by docs list/tabs list. Unchanged logic: still measures display width via Bun.stringWidth.

Compatibility

The wire protocol (POST /tabs, GET|POST /script, GET /health) and port 49227 are untouched, so the extension keeps working against this build. Its UI copy still names the old commands — filed as colevels/tabbrew#92, to fix before either side's next release.

Test plan

  • bun run typecheck and bun run build pass
  • run/serve are gone (Unknown command); --skill rejected
  • tabs serve --port 49299 + tabs push --port 49299 round-trips — verified the script lands on 49299, which is the case that previously misdelivered
  • tabs push to a port with no listener, and with an empty script, both give actionable errors
  • Flag scoping rejects docs push --port / tabs list --port
  • tabs list against Thai + Japanese + untitled tabs — columns verified equal by measured display width, not by eye
  • init / init --variant compact / --dry-run / --uninstall run from the compiled binary (exercises the SKILL.*.md text imports); installed TABBREW-CLI.md contains no dead command names

🤖 Generated with Claude Code

`run` and `serve` shipped as top-level commands in #22 but never made it into a
released binary (v0.5.3 is tagged before it), so they're removed outright — no
aliases, no deprecation.

- `run` -> `tabs push`. The old name claimed something the command cannot do:
  it has no browser access and only queues a script for the user to run, which
  the README had to disclaim twice. `docs push` <-> `tabs push` now read as one
  mental model.
- `serve` -> `tabs serve`, so the whole tab workflow lives under one noun.
- New `tabs list` shows what the extension last exported, with the export's age
  (the file is a snapshot on disk and its ids go stale). Tolerant reader: the
  two extension surfaces POST different shapes.
- New `registry.ts` is the command surface as data. `printHelp` renders from it
  and `assertFlagsAllowed` validates against it, so help can't drift from the
  routing and one command's flag can't leak into another.

Fixes a real bug this surfaced: `parseArgs` uses one flat option table, so
`run --port` parsed fine and was discarded — with a `serve` on a non-default
port, `run --port <that>` reported "Script queued" while delivering to 49227
instead. `tabs push` now takes `--port`, and both commands resolve it through
one `resolveServePort()`.

Also: `init --skill <variant>` -> `--variant` (same three prompts as
`tabs prompt --variant`; `--no-skill` stays the install-or-not switch), a typed
error instead of a bare `Error` for a bad variant, `tabs push` rejects a zero-op
script locally rather than surfacing the server's `invalid_payload`, help is
grouped with the oversized header line trimmed, and ENVIRONMENT is split into
common vs. endpoint overrides. Table helpers move to `table.ts` so `docs list`
and `tabs list` share the display-width padding.

The wire protocol and port 49227 are unchanged, so the extension keeps working.
Its UI copy still names the old commands — tracked separately in colevels/tabbrew.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@colevels
colevels merged commit 563a36a into main Jul 19, 2026
1 check passed
@colevels
colevels deleted the feat/tabs-command-group branch July 19, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant