A small CLI that fetches authenticated content from issuetracker.google.com (Buganizer) and clusterfuzz.com, using a persistent headless Chromium session driven by Playwright.
Built because there is no usable external API for either service and pasting issue contents into another tool gets old fast.
The output is a faithful markdown dump of the rendered page (light DOM + open shadow roots), so changes to either UI don't silently drop content — at worst they make the output look a little different. A small amount of structured extraction sits on top of the markdown for things the CLI itself needs to act on (testcase links inside an issue, command-line flags on a CF page, attachment download URLs, reproducer endpoints).
git clone <this repo> ~/repos/bnz
cd ~/repos/bnz
npm install
npx playwright install chromiumAdd ~/repos/bnz to your PATH so bnz is on it (the repo ships a bnz
symlink to bnz.js).
Each service is a one-time headed-browser login. The session is stored in a
persistent Playwright profile at ~/.config/bnz/profile.
bnz login # log into issuetracker.google.com
bnz cf login # log into clusterfuzz.comBoth services use Google SSO, so logging into one usually carries the other — but running both is harmless and ensures cookies are warm.
bnz 505610970 # one issue
bnz 505610970 506855825 # several issues, one Chromium session
bnz b/505610970 # `b/<id>` shorthand also works
bnz https://issuetracker.google.com/issues/505610970
bnz https://crbug.com/506855825Output is the full page rendered as markdown, with a synthesized
# Issue <id> header plus a final ## Attachments section listing any
attachment URLs found on the page.
bnz cf 5009280990216192 # by testcase key
bnz cf 505610970 # by Buganizer issue id (resolves the testcase link)
bnz cf b/505610970
bnz cf https://clusterfuzz.com/testcase?key=5009280990216192
bnz cf 505610970 --download-original # also fetch the unminimized reproducerNumeric input is disambiguated by length: 14+ digits is treated as a testcase
key; otherwise it's a Buganizer issue id and the issue's markdown is scanned
for a clusterfuzz.com/testcase?key=... link.
The output is the full testcase page as markdown plus appendix sections for
the minimized reproducer (always) and the original reproducer (when
--download-original is set).
bug 505610970 --download-attachments # to cwd
bug 505610970 --download-attachments=/tmp/bugs # to a directoryEvery attachment URL encountered on the page is fetched via the authenticated
session. A ## Downloaded attachments section is appended to the markdown
output listing the resulting filenames and sizes.
bug list "reporter:me status:open"
bug list "assignee:me modified>now-7d"
bug list "componentid:1456355 status:open" --format=jsonRuns a Buganizer search and emits the matching issues. The --format=json
form is suitable for piping into xargs bug for batch fetches.
| Flag | Effect |
|---|---|
--format=markdown |
Default. Pretty-printed with ANSI color when stdout is a TTY. |
--format=json |
Structured object (or array, when multiple targets are passed). |
--refresh |
Bypass the cache for this fetch (writes back as usual). |
--no-cache |
Disable cache reads and writes entirely. |
--download-original |
cf: also fetch the unminimized reproducer. |
--download-attachments[=DIR] |
Download every attachment URL the page exposes. |
--debug |
Add rawHtml to JSON output. |
--no-color |
Disable ANSI color (also respects NO_COLOR). |
-h, --help |
Show usage. |
Both sites are JavaScript SPAs (Polymer with shadow DOM), so we don't try to hit any backend API. The extractor is three clean stages:
- Fetch: Playwright launches Chromium against a persistent user-data dir,
reusing the cookies from
bnz login/bnz cf login. Navigate, wait fornetworkidle. - Flatten (in-page, ~70 lines in
lib/dom.js): recursively walk the live DOM and emit HTML mirroring the rendered flat tree — descend into open shadow roots, replace each<slot>with itsassignedNodes({flatten:true}). Drop invisible elements (display:none,aria-hidden) and a small media skip-list (script, style, iframe, svg, ...). The result is plain HTML. - Render: hand that HTML to Turndown (with the GFM tables/strikethrough plugin) in Node to produce markdown.
On top of the markdown, a tiny amount of structured extraction handles the things the CLI itself acts on: testcase keys inside an issue (regex), the attachment URLs collected during the flatten walk, and reproducer downloads probed via Playwright's request context.
Pages are cached at ~/.config/bug-cli/cache/ with a 5-minute TTL. Pass
--refresh to bust the cache for a single fetch, or --no-cache to disable
it entirely.
A single invocation can fetch multiple targets in one Chromium session —
bug 1 2 3 and bug cf a b c both reuse one launched browser.
- The persistent profile at
~/.config/bnz/profilecontains authenticated Google session state. Treat it like a browser profile and do not share it. - The cache at
~/.config/bug-cli/cache/stores full markdown of fetched pages, which can contain sensitive issue content. It's mode-private by default (created with the user's umask). --debugincludes raw HTML in JSON output. That can contain sensitive issue or testcase content.
- Shadow-DOM walking covers open shadow roots only. Closed shadow roots are invisible (Polymer/Buganizer use open roots, so this hasn't been a problem).
- Polymer pages often have lots of nested layout divs and component shells. The walker normalizes whitespace but doesn't try to interpret semantic structure beyond "this is a heading", "this is a link", "this is a code block". Expect more verbose output than a hand-curated version.
bnz login/bnz cf loginrequire manual interaction. Cookie rotation may eventually invalidate the session; re-run the login if requests start redirecting toaccounts.google.com.
bnz.js— CLI entry, dispatch, output rendering.lib/url.js— URL normalization (issue, testcase, search, attachment).lib/browser.js— Playwright session helpers + Turndown wiring.lib/dom.js— In-page flat-tree HTML serializer (shadow + slot projection).lib/cache.js— Disk cache.lib/render.js— Terminal sanitization, ANSI colors, header/appendix glue.test/— Unit tests (URL resolution, parsing, sanitization, cache key).