{"id":50869670,"url":"https://github.com/shyamsivakumar/backfill","last_synced_at":"2026-06-18T07:01:07.589Z","repository":{"id":364292157,"uuid":"1267056220","full_name":"shyamsivakumar/backfill","owner":"shyamsivakumar","description":"Get paid while your dbt runs. One sponsored terminal line during dbt, cargo, docker — and Claude Code/Codex sessions. 50% revenue share, open-source CLI that never reads your code.","archived":false,"fork":false,"pushed_at":"2026-06-17T06:19:42.000Z","size":1679,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T06:27:56.559Z","etag":null,"topics":["ads","claude-code","cli","codex","dbt","developer-tools","revenue-share","terminal"],"latest_commit_sha":null,"homepage":null,"language":"Go","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/shyamsivakumar.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-12T07:18:35.000Z","updated_at":"2026-06-17T06:19:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shyamsivakumar/backfill","commit_stats":null,"previous_names":["shyamsivakumar/backfill"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/shyamsivakumar/backfill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shyamsivakumar%2Fbackfill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shyamsivakumar%2Fbackfill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shyamsivakumar%2Fbackfill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shyamsivakumar%2Fbackfill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shyamsivakumar","download_url":"https://codeload.github.com/shyamsivakumar/backfill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shyamsivakumar%2Fbackfill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34479555,"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-18T02:00:06.871Z","response_time":128,"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":["ads","claude-code","cli","codex","dbt","developer-tools","revenue-share","terminal"],"created_at":"2026-06-15T04:00:37.861Z","updated_at":"2026-06-18T07:01:07.578Z","avatar_url":"https://github.com/shyamsivakumar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Backfill\n\n**Get paid while your terminal waits.** A sponsored line rides long runs: `dbt run`, `cargo build`, `docker build`, and the rest of the slow stuff. Advertisers bid for the slot, you keep half. Open source, and it never reads your code.\n\n[![release](https://img.shields.io/github/v/release/shyamsivakumar/backfill)](https://github.com/shyamsivakumar/backfill/releases) [![PyPI](https://img.shields.io/pypi/v/backfill-cli)](https://pypi.org/project/backfill-cli/) [![license](https://img.shields.io/github/license/shyamsivakumar/backfill)](LICENSE)\n\n![bf on a real dbt run: per-model output collapses into one live line carrying the ad](assets/dbt.gif)\n\n*A real `dbt run` under `bf`: the per-model `START`/`OK` noise collapses into one live line that carries the ad, with the header and `PASS/WARN/ERROR` summary intact.*\n\n## Quickstart\n\n```sh\npip install backfill-cli   # downloads + SHA-256-verifies the bf binary on first run\nbf init                    # wrap the common slow commands; your normal runs now earn\ndbt run                    # runs exactly as before, with a sponsored line that pays\n```\n\n`bf init` wraps a curated set (dbt, sqlmesh, cargo, docker, terraform, npm, and more). `bf init --all` wraps every non-interactive command on your `PATH`. `bf wrap` / `bf unwrap` adjust the list, `bf uninit` removes it. The explicit `bf dbt run` always works with no setup.\n\n## How it shows up\n\nThere are exactly two surfaces, and no reserved row anywhere.\n\n1. **Coding agents** (Claude Code, Factory droid, Codex). `bf agents install claude` (and `droid` / `codex`) sets the agent's thinking-spinner verb to a rotating batch. The ad, a trending dev content slot, and your running `$X.XX earned` tally cycle while it thinks. It installs no status line and never touches an existing one. The verb refreshes on `SessionStart` and each turn.\n2. **Any other wrapped command** (dbt, sqlmesh, cargo, docker, make, `terraform plan`, npm/pnpm/yarn/bun install, pip, go). The run collapses into one live line that rotates the ad, a trending repo / HN story / tip, and your $earned tally, with a spinner and an elapsed timer. That single line replaces the scrolling output in place. On a non-zero exit the captured output is flushed, so failures are never hidden. dbt and sqlmesh also show model progress counts on that line.\n\nInteractive and full-screen commands (vim, less, ssh, sudo, gh, psql and other REPLs, `terraform apply`, `docker run -it`, `npm init` / `npm login`) are detected and run directly in your terminal, untouched. CI and non-TTY runs exec plainly with zero overhead.\n\n## Install\n\nThree install paths. All of them SHA-256 verify the downloaded binary.\n\n```sh\n# 1) PyPI (most common)\npip install backfill-cli\n\n# 2) Homebrew\nbrew install shyamsivakumar/tap/backfill\n\n# 3) curl installer\ncurl -fsSL https://backfill.sh/install.sh | sh\n```\n\nThe pip wheel ships a thin Python entry point. On first run it fetches the matching `bf` release binary, verifies its SHA-256 against the published checksums, and execs it.\n\n**macOS note.** Recent macOS (26 / \"Tahoe\") runs a Code Signing Monitor that silently kills an unsigned downloaded `bf` on launch, with no error to your shell. The pip and curl installers re-sign the binary ad-hoc (`codesign --sign -`) after download to clear this. If you copy a `bf` binary from somewhere else and hit it, run `codesign --force --sign - /path/to/bf` once and it works.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eLocked-down containers\u003c/strong\u003e (Paradime, Codespaces, read-only base env)\u003c/summary\u003e\n\nWhen the base Python env isn't writable, `pip install` falls back to `--user` and puts `bf` in a dir that isn't on `PATH` (you'll see *\"The script bf is installed in '.../.local/bin' which is not on PATH\"*). That's a pip/platform thing, not a bf bug. Two ways through it:\n\n```sh\n# bootstrap without bf on PATH (self-heals PATH into your rc for next shell):\npython -m backfill_cli init \u0026\u0026 exec $SHELL\n\n# or use the explicit form, which never needs PATH:\npython -m backfill_cli dbt run --select my_model\n```\n\nOn **Paradime**, the durable path is `bf init`. It adds a real `export PATH=\"$HOME/.backfill/shims:$PATH\"` to `~/.zshrc`, which the Code IDE terminal sources. Open a fresh terminal and `command -v dbt` should resolve to `~/.backfill/shims/dbt`. Avoid setting `PATH` through the Code IDE env-var UI: those values are stored literally, so a `${PATH}` reference won't expand and will clobber your shell's `PATH`.\n\n`bf init` installs a pass-through shim per command into `~/.backfill/shims`. Because it's a real shim and not a shell alias, it fires wherever the command runs: your shell, a Makefile, a script.\n\n\u003c/details\u003e\n\n## Commands\n\n`bf` is a single Go binary (~600 lines, MIT). The full surface:\n\n| Command | What it does |\n|---|---|\n| `bf \u003ccmd\u003e...` | Run `\u003ccmd\u003e` wrapped. No setup needed. |\n| `bf init [cmd...]` | One-time setup. Wraps a curated set of slow commands (dbt, sqlmesh, cargo, docker, terraform, npm, and more) by installing a PATH shim per command in `~/.backfill/shims` and prepending that dir to your shell rc. Pass extra commands to wrap more. |\n| `bf init --all` | Wrap every non-interactive command on your `PATH` (skips interactive tools: editors, shells, paginators, `sudo`, `ssh`, anything that takes over the screen). |\n| `bf wrap \u003ccmd\u003e...` | Wrap the listed commands now (adds shims). |\n| `bf unwrap \u003ccmd\u003e...` | Remove the shims for the listed commands. |\n| `bf uninit` | Remove every shim `bf init` / `bf init --all` / `bf wrap` installed, and strip the `PATH` line from your rc. |\n| `bf on` / `bf off` | Globally pause or resume. `off` execs plainly with zero overhead, as if no shim is installed. |\n| `bf status` | Show what's wrapped, current `on`/`off` state, and your device id and dashboard link. |\n| `bf claim \u003cemail\u003e` | Print a one-time code and link to bind this device to your web account, so earnings show in your dashboard. |\n| `bf agents install claude` | Spinner-verb rotation for Claude Code. No status line. |\n| `bf agents install droid` | Spinner-verb rotation for Factory `droid`. No status line. |\n| `bf agents install codex` | Spinner-verb rotation for Codex. No status line. |\n| `bf agents remove \u003cname\u003e` | Remove a previously installed agent integration. |\n| `bf agents status` | Show which agent integrations are installed. |\n\n`bf wrap droid` adds the spinner-verb injection for Factory droid without the full agent install (handy for droid-specific sessions).\n\n## How the wrapper works\n\n`bf \u003ccmd\u003e` runs `\u003ccmd\u003e` and collapses its output into one live line. There is no pseudo-terminal on the collapsed path, no scroll region, and no reserved row.\n\n- A wrapped non-interactive command has its stdout and stderr piped and collapsed into the one live line. On a non-zero exit the captured output is flushed, so failures are never hidden.\n- Interactive and full-screen commands (vim, less, ssh, sudo, gh, psql and other REPLs, `terraform apply`, `docker run -it`, `npm init` / `npm login`) are detected and run directly in your terminal, untouched.\n- Exit codes pass through end to end.\n- Non-TTY execs (CI, Airflow, dbt Cloud, cron) run plainly with zero overhead: the shim detects no TTY and just `exec`s the underlying binary.\n\n## Smart progress\n\nFor verbose commands, the per-line noise is the problem, not the wait. `bf` recognizes a set of command/verb pairs and collapses the run into one live line that carries the ad, your $earned tally, model progress, a spinner, and an elapsed timer.\n\n| Command | Recognized verbs | What you see |\n|---|---|---|\n| `dbt` | `run`, `build`, `test`, `seed`, `snapshot` | One live line like `⠹ dbt 5/8 main.fct_orders · ad …`, the version header, any errors verbatim, and the final `PASS/WARN/ERROR` summary. |\n| `sqlmesh` | `plan`, `run` | One live line carrying the model being applied and the ad. |\n\nIn both cases the ad rides the line your eyes are already on. The header stays, errors stay, the summary stays. Everything in between collapses.\n\n## SQLMesh\n\nSQLMesh is wrapped by `bf init` out of the box (or add it explicitly with `bf wrap sqlmesh`). Smart progress is active for `sqlmesh plan` and `sqlmesh run`: SQLMesh's per-model output collapses into one live line carrying the ad, the important output and the result stay, and the child exit code passes through. Same engine as the dbt smart progress.\n\n```text\n⠹ sqlmesh applying prod.my_model · ad …\n```\n\nIt works from shells, Makefiles, and scripts through the PATH shim. Non-TTY and CI runs pass through plainly.\n\n## Scaffold completion ads\n\nAfter any of the following exits 0, `bf` prints one persistent sponsored line under the success screen (one impression, regardless of how long the command took):\n\n- `npm create` / `npm init`, `pnpm create` / `pnpm init`, `yarn create` / `yarn init`, `bun create` / `bun init`\n- `npx create-*`\n- `cargo new`, `cargo init`\n- `django-admin startproject`\n- `rails new`\n- `dotnet new`\n- any binary named `create-*` on your `PATH` that exits 0\n\nThe line prints once under whatever success UI the scaffolder drew. These commands finish too fast for the live line to earn, but their \"you're all set, here's what's next\" screen is the highest-intent moment in the session.\n\n## npm and package installs\n\nPackage installs are long waits worth monetizing. `bf init` wraps `npm`, `pnpm`, and `yarn`; add `bun` with `bf wrap bun`. A plain install then runs through the collapsed live line while it resolves and downloads:\n\n```sh\nnpm install\n```\n\nThere's no smart-progress collapsing for installs, `bf` just runs the live line. `npm init` and `npm login` are interactive, so they pass straight through to your terminal. CI and non-TTY installs pass through plainly.\n\nScaffold completions are separate (see above): after `npm create` / `npm init`, the pnpm/yarn/bun equivalents, or `npx create-*` exit 0, `bf` prints one persistent sponsored line under the \"you're all set\" success screen.\n\n## Coding agents\n\n`bf` doesn't patch any agent's source. It uses each agent's own exposed surface.\n\n| Agent | Integration | Install |\n|---|---|---|\n| Claude Code | Spinner-verb rotation (no status line) | `bf agents install claude` |\n| Factory (`droid`) | Spinner-verb rotation (no status line) | `bf agents install droid` |\n| Codex | Spinner-verb rotation (no status line) | `bf agents install codex` |\n\nFor Claude Code you can also install via the plugin marketplace: `/plugin marketplace add shyamsivakumar/backfill` then `/plugin install backfill@backfill`. `bf agents remove claude` undoes it.\n\n## What it sells that no other ad network can\n\n- **Command-level segments.** Advertisers buy \"developers currently running dbt,\" not \"developers.\" The command name is the only targeting signal. No keywords, no profiles, no behavior graph.\n- **Verified dwell.** A live line during a 15-minute compile is continuous, unskippable attention. There's no tab to switch away from without abandoning the build.\n- **CI earnings routing.** Via the GitHub Action, a repo points its build-log earnings at its maintainers. Your CI minutes fund the dependencies you build on.\n\n## Surfaces\n\n| Surface | What's wrapped | How |\n|---|---|---|\n| dbt + data stack | `dbt`, `bq`, `snowsql`, `spark-submit`, `sqlmesh` | `bf init` (curated set) or `bf wrap \u003ccmd\u003e` |\n| Any CLI tool | `cargo`, `docker`, `make`, `terraform`, `gradle`, … | `bf init` covers these, or `bf wrap \u003ccmd\u003e` / `bf init --all` |\n| Coding agents | Claude Code / Factory droid / Codex spinner verb | `bf agents install …` |\n| Scaffold screens | `npm create`, `cargo new`, `rails new`, … | automatic on a clean wrapped run |\n| CI build logs | the GitHub Action (`action.yml`) | maintainer-directed earnings |\n\n## Privacy\n\nThe CLI is structurally incapable of reading your code, command args, command output, or environment. The only fields it ever transmits:\n\n- **device id**, a random per-install id\n- **ad id**, the campaign the server chose to serve\n- **command name**, the bare basename, e.g. `dbt` or `cargo`, never the path\n- **visible seconds**, how long the line was actually on screen\n- **event kind**, a static label, `impression` or `click`\n\nNo args, no paths, no filenames, no env, no stdout or stderr contents. The source is open (MIT, ~600 lines of Go), so you can verify.\n\n## Economics\n\n- **Unit:** 1 impression = 5 visible seconds.\n- **Pricing:** advertisers buy blocks of 1,000 impressions (CPM). Clicks bill higher than impressions.\n- **Split:** users keep 50% of attributable revenue (`USER_SHARE = 0.5`).\n- **Balances** accrue per run and surface in `bf status` and the web dashboard.\n- **Payouts:** Stripe, once a balance crosses $25. Payout plumbing is planned, not live yet. Balances accrue today.\n- **Early inventory:** while the first advertiser slots sell, the slot is filled with house ads at `cpm = 0`. No money changes hands, but the slot is exercised and you see a real sponsored line.\n\n## Advertiser side\n\nAdvertisers self-serve through the portal at [backfill.sh/advertiser](https://backfill.sh/advertiser):\n\n- Sign in with a magic link, email only, no password.\n- Prepay an ad budget with a Stripe deposit.\n- Submit a campaign: ad text, an https link, a CPM, and optional command targeting (e.g. only on `dbt` runs).\n- Every campaign is reviewed and approved before it serves. You only pay for verified impression and click time, billed against your deposit.\n\n### Ad selection\n\nEvery candidate gets a single unified **eCPM in micros**, then the server picks the max under frequency-cap and flight-window gating. The components:\n\n- **Direct CPM**, what the advertiser pays per 1,000 impressions.\n- **Affiliate expected value** = `payout × conversion-rate prior`, converted to an eCPM equivalent.\n- **House floor**, the minimum to serve (currently 0 while house-ad inventory fills slots).\n\nBayesian shrinkage tempers noisy per-campaign conversion priors, so a campaign with 3 clicks doesn't outrank one with 3,000. The knobs in `web/lib/ecpm.ts`:\n\n- `PRIOR_CVR_BPS = 50` (a 0.5% prior conversion rate)\n- `PRIOR_STRENGTH = 500` (the prior weighted as 500 ghost conversions)\n\nA frequency cap stops a device from seeing the same ad back to back across runs, and flight windows gate serving to a campaign's scheduled dates.\n\n## Repo layout\n\n| Dir | What |\n|---|---|\n| `cli/` | `bf`, the Go wrapper (~600 lines). Runs the two ad surfaces. |\n| `web/` | Next.js (App Router): landing, advertiser portal, dashboard, ad-serve + event API, Postgres via Drizzle on Neon. |\n| `action.yml` | GitHub Action: the same model for CI build logs, with maintainer-directed earnings. |\n| `python/` | Thin Python wrapper shipped in the `backfill-cli` wheel: fetches + SHA-256-verifies the Go binary, re-signs it ad-hoc on macOS, then execs it. |\n\n## Tests\n\nWhat's covered today:\n\n- `cli/completion_test.go`, scaffold detection (the `create-*` / `cargo new` / `npm create` allowlist) and the one-line completion ad.\n- `web/lib/ads.test.ts`, eCPM ad selection: frequency cap, flight gating, deterministic tie-break.\n- `web/lib/ecpm.test.ts`, the eCPM math: direct CPM, affiliate expected value, Bayesian shrinkage with `PRIOR_CVR_BPS` and `PRIOR_STRENGTH`.\n- `web/lib/advertiser.test.ts`, advertiser balance math: deposits, full-CPM spend, balance, rounding.\n\nRun the web tests with `npm test` in `web/`. Run the CLI tests with `go test ./...` in `cli/`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshyamsivakumar%2Fbackfill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshyamsivakumar%2Fbackfill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshyamsivakumar%2Fbackfill/lists"}