{"id":50944091,"url":"https://github.com/zero8dotdev/tabstack-cli","last_synced_at":"2026-06-17T18:05:57.978Z","repository":{"id":364146938,"uuid":"1266623308","full_name":"zero8dotdev/tabstack-cli","owner":"zero8dotdev","description":"Bun CLI for the Tabstack AI web extraction, generation, research \u0026 automation API","archived":false,"fork":false,"pushed_at":"2026-06-11T19:57:53.000Z","size":18,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T21:21:25.664Z","etag":null,"topics":["ai","bun","cli","tabstack","web-scraping"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zero8dotdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T19:51:16.000Z","updated_at":"2026-06-11T20:05:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zero8dotdev/tabstack-cli","commit_stats":null,"previous_names":["zero8dotdev/tabstack-cli"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/zero8dotdev/tabstack-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero8dotdev%2Ftabstack-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero8dotdev%2Ftabstack-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero8dotdev%2Ftabstack-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero8dotdev%2Ftabstack-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zero8dotdev","download_url":"https://codeload.github.com/zero8dotdev/tabstack-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero8dotdev%2Ftabstack-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34459713,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","bun","cli","tabstack","web-scraping"],"created_at":"2026-06-17T18:05:56.739Z","updated_at":"2026-06-17T18:05:57.973Z","avatar_url":"https://github.com/zero8dotdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tabstack\n\nA small, fast Bun CLI for the [Tabstack AI](https://docs.tabstack.ai) API —\nturn any URL into clean markdown or structured JSON, transform pages with AI,\nrun autonomous multi-source research, and drive natural-language browser\nautomation. One binary, no SDK required.\n\n\u003e **Built live, on the call.** This CLI was written with\n\u003e [Claude Code](https://claude.com/claude-code) during Tessa's Tabstack\n\u003e livestream — start to published repo while the stream was still going.\n\u003e It came together fast for one reason: it follows a gold-standard CLI\n\u003e pattern I'd already honed on previous tools — data to **stdout**, progress\n\u003e to **stderr**, `--json` on every command, honest exit codes, `--help` that\n\u003e never touches the network. A CLI built that way works for humans *and*\n\u003e agents — which is exactly why an agent could build, test, and dogfood it\n\u003e in one sitting. Good conventions compound.\n\n## What can it do?\n\n```bash\n# Turn any page into clean markdown — yes, even Product Hunt\ntabstack extract markdown https://www.producthunt.com/products/tabstack\n\n# Check how Tabstack's own launch is doing... using Tabstack\ntabstack extract json https://www.producthunt.com/products/tabstack/launches \\\n  --schema '{\"type\":\"object\",\"properties\":{\"launches\":{\"type\":\"array\",\"items\":{\n    \"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\n    \"upvotes\":{\"type\":\"number\"},\"comments\":{\"type\":\"number\"}}}}}}'\n\n# Settle the eternal argument, with citations\ntabstack research \"tabs vs spaces: what do the major style guides actually recommend?\"\n\n# Outsource your browsing to a browser that browses for you\ntabstack automate \"find the top 3 trending repos and their star counts\" \\\n  --url https://github.com/trending\n\n# Pipe it like any good Unix citizen — piped output is JSON automatically\ntabstack extract markdown https://example.com | jq -r .content | wc -w\n```\n\nFun fact: the Product Hunt example above is how this repo checked Tabstack's\nlaunch stats during the stream. The snake ate its own tail and returned\nvalid JSON matching the schema.\n\nWant more? **[RECIPES.md](./RECIPES.md)** has ten of these, light to hard —\nfrom a typed Hacker News feed to CI that fails when your landing page lies,\nto a blog post that fact-checks itself. They're also built into the CLI:\n\n```bash\ntabstack recipes        # browse the cookbook\ntabstack recipes 9      # one recipe, copy-paste ready\ntabstack recipes 9 --json | jq .command   # agents eat the cookbook too\n```\n\n## For agents\n\nThe skill ships inside the binary — anyone who installs the CLI can wire it\ninto their coding agent in one line:\n\n```bash\ntabstack skill install              # Claude Code, all sessions (~/.claude/skills)\ntabstack skill install --project    # Claude Code, this repo only (.claude/skills)\ntabstack skill agents \u003e\u003e AGENTS.md  # Codex, Cursor, Copilot, VS Code, …\ntabstack skill                      # print it, pipe it anywhere else\n```\n\nThree discovery paths, so an agent finds the recipes however it arrives:\n\n- **[AGENTS.md](./AGENTS.md)** — the agent contract: output conventions,\n  NDJSON event filtering, exit codes, safety flags, and the recipe index.\n  Read automatically by Codex, Cursor, Copilot, and friends.\n- **The Claude Code skill** (above) — triggers when a task needs live web\n  data, before the user mentions tabstack.\n- **`tabstack recipes --json`** — the cookbook as structured data, no docs\n  required.\n\n## Install\n\n```bash\nbun install\nbun link            # puts `tabstack` on your PATH (dev)\n# or build a standalone binary:\nbun run build       # → ./tabstack\n```\n\nRequires [Bun](https://bun.sh) ≥ 1.1.\n\n## Auth\n\nEasiest: log in once. This opens the Tabstack console so you can create/copy a\nkey, then stores it at `~/.config/tabstack/config.json` (mode `0600`).\n\n```bash\ntabstack login                              # opens the browser, prompts for the key\ntabstack login --with-key ts_xxx            # non-interactive (CI)\necho \"$KEY\" | tabstack login --no-verify    # from a pipe\ntabstack logout                             # remove the stored key\n```\n\nThe key is resolved in this order:\n\n```text\n--api-key \u003ckey\u003e   →   TABSTACK_API_KEY env   →   stored key from `tabstack login`\n```\n\n`export TABSTACK_API_KEY=...` or `--api-key` still work and take precedence.\nTabstack has no headless OAuth/device flow, so `login` just opens the console\n(API Keys → Create New API Key) and securely saves what you paste. By default it\nverifies the key with one cheap API call; skip that with `--no-verify`.\n\n## Commands\n\n```text\ntabstack login                                         Open console \u0026 store an API key\ntabstack logout                                        Remove the stored API key\ntabstack status                                        How your key resolves (never prints it)\ntabstack extract markdown \u003curl\u003e [--metadata]           Page → clean markdown\ntabstack extract json \u003curl\u003e --schema S                 Page → JSON matching a schema\ntabstack generate json \u003curl\u003e --schema S --instructions T   AI-transform a page → JSON\ntabstack research \"\u003cquery\u003e\"                            Multi-source research (streaming)\ntabstack automate \"\u003ctask\u003e\" [--url U]                   Browser automation (streaming)\ntabstack input \u003crequest-id\u003e --data D                   Answer a paused automation\ntabstack recipes [n|name]                              The cookbook, in your terminal\ntabstack usage [set|cookie|sync]                       Token budget tracking \u0026 cost estimates\n```\n\n### Usage \u0026 cost tracking\n\nThe API exposes no balance endpoint and no per-call token cost (only the\nconsole dashboard shows \"tokens remaining\"). The CLI works around that:\n\n- every API call is logged to a **local ledger** (`~/.config/tabstack/usage.jsonl`)\n- feed in the dashboard balance — `tabstack usage set 87500`, or store a\n  console session cookie once (`tabstack usage cookie \u003cname=value\u003e`) and run\n  `tabstack usage sync` to scrape it automatically. The cookie must be in\n  `name=value` format — copy the full cookie string from your browser's\n  DevTools (Network → request headers → Cookie), not just the value\n- the **delta between two readings**, split across the logged calls, teaches\n  per-verb costs; `tabstack usage` then shows the estimated balance and what\n  the next call of each verb will roughly cost\n- 429s **auto-retry** honoring `x-ratelimit-reset` (the only quota signal the\n  API does send), so pipelines absorb rate limits instead of failing\n\n**Extract vs research — the measured rule:** default to `extract`. If you can\nname the URLs, extract them (~10 cr/page) and synthesize in-context — two\ncontrolled experiments found extract beats research on quality at **4–25×\nlower cost**. 1 `research` ≈ 25 `extract` calls; buy it only when source\ndiscovery is the hard part. Never use research as a fact pipe — extract the\ncited pages first to verify specific numbers and quotes.\n\nThis is scaffolding by design: the day Tabstack ships `GET /v1/usage` or an\n`x-tokens-used` header, the ledger gets fed truth instead of inference.\n\nRun `tabstack help` for the full flag reference.\n\n### Extract\n\n```bash\n# Markdown to stdout\ntabstack extract markdown https://example.com\n\n# Structured JSON — schema from a file, stdin, or inline\ntabstack extract json https://news.ycombinator.com --schema @schema.json\necho '{\"type\":\"object\",\"properties\":{\"title\":{\"type\":\"string\"}}}' \\\n  | tabstack extract json https://example.com --schema -\ntabstack extract json https://shop.example.com \\\n  --schema @products.json --effort max --nocache --geo GB\n```\n\n### Generate (AI transformation)\n\n```bash\ntabstack generate json https://blog.example.com/post \\\n  --schema @out.json \\\n  --instructions \"Write a 2-sentence summary into 'summary' and classify 'sentiment'\"\n```\n\n### Research (streaming)\n\nProgress is printed to **stderr**; the final markdown report goes to **stdout**,\nso you can redirect it cleanly. In JSON mode the stream is NDJSON, one event\nper line.\n\n```bash\ntabstack research \"approaches to browser automation for AI agents\" --mode fast\ntabstack research \"EU vs US AI regulation\" --mode balanced -o pretty \u003e report.md\ntabstack research \"competitor pricing\" \\\n  | jq -r 'select(.event==\"complete\") | .data.metadata.citedPages'\n```\n\n### Automate (streaming)\n\nRead-only by default (a safe guardrail is applied unless you pass your own or\n`--allow-actions`). Progress to stderr, the agent's final answer to stdout.\n\n```bash\ntabstack automate \"find the top 3 trending repos and their star counts\" \\\n  --url https://github.com/trending\n\ntabstack automate \"fill the contact form and submit\" \\\n  --url https://co.example.com/contact \\\n  --data @form.json --allow-actions --max-iterations 30\n```\n\nPass `--interactive` to let the agent pause and ask for input mid-task\n(`interactive:form_data:request`) — the CLI prints the request id and how to\nanswer it:\n\n```bash\ntabstack input \u003crequest-id\u003e --data '{\"fields\":[{\"ref\":\"field1\",\"value\":\"yes\"}]}'\ntabstack input \u003crequest-id\u003e --data '{\"cancelled\":true}'    # decline\n```\n\n## Output conventions\n\n- **stdout** — the data (markdown, JSON, report, final answer). Pipeable.\n- **stderr** — progress, status, citations, errors.\n- **Output mode** — pretty (human-readable) on a TTY, JSON when piped, so\n  `tabstack ... | jq .` just works. Force with `-o pretty|json` (`--json` is\n  shorthand for `-o json`). Streaming commands emit NDJSON in JSON mode.\n  `extract json` and `generate json` print JSON in both modes — the JSON *is*\n  the data.\n- **Color** — disabled when piped, with `--no-color`, or via `NO_COLOR`.\n- **exit code** — `0` success · `1` runtime/API error · `2` usage error ·\n  `3` the task itself reported failure.\n- **`--base-url \u003curl\u003e`** / `TABSTACK_BASE_URL` for self-hosted or staging;\n  `--timeout \u003cseconds\u003e` for non-streaming calls.\n\n## Endpoints used\n\n| Command            | Endpoint                  | Transport |\n| ------------------ | ------------------------- | --------- |\n| `extract markdown` | `POST /v1/extract/markdown` | JSON      |\n| `extract json`     | `POST /v1/extract/json`     | JSON      |\n| `generate json`    | `POST /v1/generate/json`    | JSON      |\n| `research`         | `POST /v1/research`         | SSE       |\n| `automate`         | `POST /v1/automate`         | SSE       |\n| `input`            | `POST /v1/automate/{id}/input` | JSON   |\n\n## Develop\n\n```bash\nbun test                          # mock-server end-to-end tests (56 tests)\nbun x -p typescript tsc --noEmit  # typecheck\nbun run start --help\n```\n\n## Project layout\n\n```text\nsrc/\n├── index.ts    # CLI entry + subcommand router (the gold-standard pattern)\n├── config.ts   # base URL, key resolution, endpoint paths, credential storage\n├── auth.ts     # login/logout: open console, read key, verify, store 0600\n├── client.ts   # fetch wrapper: postJson + postStream (SSE) + TabstackError\n└── format.ts   # json(), stderr progress(), schema/text arg resolution\ntest/\n└── cli.test.ts # runs the real binary against a Bun.serve mock\n```\n\n## Notes\n\n- `--schema` and `--data` accept `@file`, `-` (stdin), or inline JSON;\n  `--instructions` accepts `@file`, `-`, or a literal string.\n- `balanced` research mode requires a paid Tabstack plan.\n- Feature parity with the official\n  [Mozilla-Ocho/tabstack-cli](https://github.com/Mozilla-Ocho/tabstack-cli)\n  (Go), plus three extras: safe-by-default `automate` guardrails, a\n  browser-opening `login` that verifies the key before storing it, and a\n  working human-in-the-loop flow — `automate --interactive` actually enables\n  the API's input requests (the official CLI ships `agent input` but no way\n  to turn interactive mode on).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzero8dotdev%2Ftabstack-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzero8dotdev%2Ftabstack-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzero8dotdev%2Ftabstack-cli/lists"}