bulk-bg-remove · v0.1.3 · 2026-10-30 · sha256 1f369cc3a1fcc4af

bulk-bg-remove v0.1.3A

Immutable. This exact content never changes and is served at /api/v1/blob/1f369cc3a1fcc4af.

---
name: bulk-bg-remove
description: >
  Remove image backgrounds locally and in bulk: one photo, a folder (searched
  recursively) or many folders, with BiRefNet and lighter models picked to fit
  the machine. No uploads, no API keys, no per-image cost. Writes lossless
  transparent PNG/WebP, or shop-ready photos (white or any colour, square,
  sized, JPEG), to a new "<name>-no-bg" folder next to the input and never
  touches the originals. Use when the user asks to remove, cut out or
  erase a background, make a transparent PNG, isolate a product, person, animal
  or object, prepare product or catalog photos, or replace remove.bg for a
  batch. Reads JPG, PNG, HEIC, WebP, AVIF, TIFF and BMP.
license: MIT
compatibility: >
  Needs uv (installs the engine and a matching Python 3.11-3.13 by itself).
  macOS, Linux and Windows; runs on the CPU (no GPU support yet). Product
  photos work from ~3.5 GB of free RAM (best results from ~12 GB); hair, fur,
  people and busy scenes need ~14.5 GB for the best model. Each model
  downloads once (40 MB to 1 GB).
allowed-tools: Bash(bulk-bg-remove:*) Bash(uv tool install bulk-bg-remove*) Bash(uv tool upgrade bulk-bg-remove*)
metadata:
  version: "0.1.3"
---

# bulk-bg-remove

Cut out the subject of every image the user points at, on this machine. The
engine is the `bulk-bg-remove` command: it scans, picks a model that fits the
free RAM, plans the run, processes the images and verifies every output. You
drive it with `--json` (one JSON object per line) and report back.

Run every command on its own: no `;`, `&&` or pipes. Harnesses approve shell
commands one at a time, and this skill pre-approves only `bulk-bg-remove …`
and `uv tool install|upgrade bulk-bg-remove`, so a chained command stops for
approval (or fails in a session nobody is watching).

## 1. Make sure the engine is installed

```bash
bulk-bg-remove --version
```

- Not found: run `uv tool install bulk-bg-remove` straight away (no need to
  check uv first: if uv is missing, that command says so).
- No `uv` either: stop and ask the user to install uv
  (https://docs.astral.sh/uv/getting-started/installation/), then retry. Do not
  run a remote install script without the user's OK. `pipx install bulk-bg-remove`
  also works.
- Older than this skill's `metadata.version`: `uv tool upgrade bulk-bg-remove`.
- Options and model names: `bulk-bg-remove --help`.

## 2. Look at the images, then plan

```bash
bulk-bg-remove "<path>" --dry-run --sample-sheet --json
```

Add `--profile <name>` right away if the user's words already say what the
images are. The dry-run loads no model and writes nothing next to the photos
(the sheet goes to a temporary folder; no need to mention or delete it). Read
the events:

- `scan.inventory`: images found, formats, and `skipped` (not images, hidden
  image files, earlier `-no-bg` output folders) with a reason each.
- `sample_sheet.path`: a contact sheet of up to 12 of the images. **Open it and
  look.** Pick the profile that fits most of them:

  | Profile | The images are mostly |
  |---|---|
  | `uniform` (default) | products and objects with clean outlines: bottles, boxes, shoes, tools, furniture |
  | `complex` | hair, fur, plants, wires, thin or see-through parts |
  | `people` | portraits or groups of people |
  | `scene` | cluttered scenes with no clean background |
  | `drawing` | cartoons, anime, flat illustration |

  The user's own words win over the sheet ("these are product shots"). With no
  clear majority, choose by the hardest part: any hair or fur → `complex`, even
  for portraits. If a folder really mixes kinds (products and portraits), offer
  to run its sub-folders separately with their own profiles.
- `plan`: `model` (what `auto` picked), `model_choice` (its score against the
  best model for that profile, and the RAM the best one needs), `eta_seconds`,
  `blockers`. If you picked a different profile than the first dry-run used,
  dry-run again with `--profile <name>` (no `--sample-sheet` needed).

**What the output is for.** Transparent cut-outs are the default. When the
user's words point to a shop (store, marketplace, Amazon, Shopify, catalogue,
"white background", "square"), add the finishing flags to the run:

| They want | Flags |
|---|---|
| Photos for a shop or marketplace | `--shop`: white, square, product filling ~85%, 2000 px, JPEG |
| A brand colour behind the product | `--background "#RRGGBB"` (plus `--square`, `--pad PCT`, `--size PX` as needed) |
| Transparent, but square and trimmed | `--square --pad 5` |

A shop with no further detail means `--shop`: do not stop to ask. Say in the
report that transparent cut-outs or a brand colour are one flag away.

## 3. Run

If `plan.blockers` is empty, start right away, no need to ask:

```bash
bulk-bg-remove "<path>" --profile <profile> [finishing flags] --review-sheets --json --yes
```

- `--yes` only skips the engine's own "Start?" prompt.
- Read the events from the command's own output; do not redirect it to a
  file (writing outside the project folder needs extra approval).
- stdout is pure JSON. stderr may show library warnings; judge the run by the
  exit code and the `finished` event. `worker_loading`/`worker_ready` can repeat
  mid-run when RAM is low (a worker restarting to free memory): not a crash.
- If `plan.eta_seconds` is over ~120, run it in the background if your harness
  allows, and report progress now and then from `job_done` events
  (`done`/`total`, `source`). Read a background run's output file with your
  file-reading tool, not with a shell command (`python`, `tail`…), which needs
  extra approval.
- Output: `shoe.jpg` → `shoe-no-bg/shoe.png` (`.jpg` with `--shop`); `products/` → `products-no-bg/`
  with sub-folders mirrored, next to the input. `-o DIR` overrides it. If two
  inputs share a name (`product.jpg` + `product.png`), both get their extension
  added (`product_jpg.png`, `product_png.png`); `job_started.destination` has
  the final name.
- Images already done are skipped on a re-run; `--force` redoes them. Changing
  the finishing flags (another colour, size or margin) redoes them by itself.

Exit codes: `0` all good · `1` some images failed, or the model could not be
prepared (see the `error` event) · `2` blocked by the plan · `130` interrupted.

## When the plan is blocked

Never work around a blocker on your own. Tell the user, in plain words, and
offer the choices:

- **The best model does not fit, a weaker one does** (the blocker mentions
  `--allow-fallback`): for this kind of image the light model is clearly worse
  (e.g. hair or plants: about 5/10 instead of 7.6/10). Offer: close some apps
  and retry, or go ahead with the lighter model by adding `--allow-fallback`.
  If the user asked for the best quality, say that the lighter model goes
  against it.
- **Not enough free RAM for any model**: say how much is free and how much the
  lightest model needs, then offer the ways to fit, checking each with a
  `--dry-run` before you suggest it (one per dry-run, `plan.blockers` empty means
  it fits):
  1. **Close some apps** (browsers, Docker, video tools) and retry: no quality lost.
  2. **`--fast-edges`**: skips edge matting, which needs the most RAM per image.
     Fine for products; hair, fur and plants come out coarser, so say so.
  3. **Large photos apart**: if `scan.inventory.max_megapixels` is high (20+),
     `--max-megapixels 24` (or lower) does the rest now; the large ones are
     listed as skipped with that reason. Run them later on their own, after
     closing apps or with `--fast-edges`.
  4. **Without the finishing** (`--shop`, `--background`, `--size`…), if that is
     what tips it over: transparent cut-outs now. Say plainly that adding the
     finish later means another run that needs the same RAM as this one.
  If `scan.hardware.ram_total_gb` itself is below what the lightest model needs
  even with these, closing apps cannot fix it: say this machine cannot run it.
- **Not enough disk**: say how much is needed and where.
- **No supported images**: say what was skipped and why.

The plan is the authority on RAM; `doctor` is only a quick check. Do not lower
`--min-free-ram` to force a start: the machine would swap.

When the model is lighter but within half a point of the best (for example
product photos on a laptop with little free RAM), `auto` uses it on its own;
mention it in one line of your report.

**"Best quality"** needs no extra flag: keep `--model auto`, never add
`--fast-edges`, and check `model_choice.gap == 0` (the best model for that
profile is running). If the gap is above 0, say so and offer to free RAM.

## 4. Review the cut-outs

The engine checks files, not looks. `--review-sheets` (step 3) adds a
`review_sheets` event just before `finished`: sheet PNGs in a temporary folder,
each showing up to 12 cut-outs numbered `#n`, as original | on black | on white.
With a `--background` they still show the transparent cut-out (a leftover of a
white background would be invisible on white); the finish itself is mechanical.

The sheets hold only the cut-outs that need eyes. A cut-out whose photo has a
plain background and that matches it exactly (nothing unlike the background
removed, no background colour left on the edge, the same mask on the mirrored
photo, and more: see `job_done.check`) is **certified** (`job_done.certified`)
and left off the sheets, except 1 in 20 kept as an `AUDIT` entry
(`review_sheets.audit`). Hair, fur, glass and busy backgrounds are never
certified; a white or clear product on white only when its whole outline still
stands out from the backdrop, so most of them are reviewed. If the user wants every
cut-out reviewed anyway, add `--review-all` to the run.

1. **Get a fresh pair of eyes.** If you can start a subagent (Claude Code: the
   `bg-reviewer` agent this plugin ships, or any subagent; Codex: a sub-agent),
   give it the sheet paths, the `review_sheets` event (it has each image's
   `zoom` close-up) and the full text of
   `references/reviewer.md`. It did not run the batch, so it sees what is
   there, not what you expect. With more than 4 sheets, split them across
   several reviewers in parallel: looking costs no engine RAM. If you cannot
   start one, review the sheets yourself with `references/reviewer.md`.
2. **Fix once, on the side.** For each `fix`, take the `source` of its `n`
   from the `review_sheets` event and run again into a retry folder next to
   the output, named so later scans skip it (`products/` → output
   `products-no-bg/`, retries `products-retry-no-bg/`), one run per set of
   flags, never two at once:

   ```bash
   bulk-bg-remove "<path>" -o "<name>-retry-no-bg" --profile <profile> --only <source> [--only <source> ...] <fix flags> [finishing flags] --review-sheets --review-all --json --yes
   ```

   Keep the first run's finishing flags (`--shop`, `--background`…) so the
   new file can replace the old one, and keep `--review-all`: every redo must
   reach the sheets to be compared, even one that now passes certification.

   `--only` takes the absolute `source`, a path relative to `<path>`, or a bare
   file name if it is unique, and keeps the usual output names (the same
   `label` as on the first sheets). A fix whose plan is blocked (e.g. `--profile
   complex` needs more RAM) goes through "When the plan is blocked".
3. **Keep the better one.** Give the reviewer the new sheets plus the earlier
   ones and ask it to compare (see "Comparing a fix" in `reviewer.md`). Copy a
   new cut-out over the first one (same `label` under the output folder) only
   when the reviewer says `keep: new`; a
   second pass can make a good cut-out worse. One round only: anything still
   wrong, and every `ask`, goes in your report with the sheet path so the user
   can see it. Mention the retry folder: the user can delete it.

If a reviewer flags an `AUDIT` entry, the certification missed a defect on these
photos: say so in the report and offer to re-run the batch with `--force
--review-sheets --review-all` (every cut-out reviewed).

Review by default; skip it only if the user wants speed over checking or says
not to. The sheets cover the images this run processed: on a re-run where
everything was already done there is no `review_sheets` event.

## 5. Report back

From the final `finished` event:

- **Done**: `processed` images in `output`. Mention any renamed output.
- **Failed** (`failed`): one line each, in plain words. `image file is
  truncated` or `cannot identify image` → the file is damaged; re-export or
  re-download it. Do not retry the same file in a loop.
- **Not processed** (`not_processed` and `scan.inventory.skipped`): group by
  reason ("3 files are not images: notes.txt, fake.pdf, page.jpg"); collapse
  long lists. Always mention hidden image files and earlier `-no-bg` folders if
  the user said "all the photos".
- **Warnings** (`warnings`, and `job_done.note`): `empty cut-out` (no subject
  found) and `nothing removed` deserve a line each; `edge matting skipped` means
  that image probably came out empty or ghostly; `edge matting at N MP to fit
  free RAM` means its edges are a bit softer than usual; `upscaled ×N from a
  small original` means a shop photo was enlarged and may look soft: suggest a
  bigger original.

Do not claim success for images listed in `failed`. `processed` counts only
outputs the engine verified (a readable file of the expected kind and size; an
empty or unchanged cut-out is listed in `warnings`), so no extra check is
needed for the report. A good report is 3-5 lines:

> Done: 37 of 38 photos cut out, in `~/Pictures/products-no-bg/` (transparent
> PNG, same size, originals untouched). Model: birefnet-general-lite.
> 1 failed: `IMG_0042.jpg` is damaged, re-export it.
> 2 files skipped: not images (notes.txt, list.pdf).
> Review: 35 fine; 2 had a green fringe on the hair and were redone with
> `--clean-edges`, now fine.

**Store and catalog photos.** Say what the output is. Transparent: the same
size as the original; offer `--shop` (white, square, 2000 px JPEG) or a brand
colour if their shop needs it. With `--shop`: ready to upload. Either way,
contact shadows under the product are removed; the engine does not add a
shadow.

## Options worth knowing

| Flag | Use it when |
|---|---|
| `--profile NAME` | Always, from step 2. |
| `--review-sheets` | Always, unless the user wants speed over checking (step 4). |
| `--only FILE` | Redoing chosen images after a review (step 4), with `-o` to a temporary folder. Implies `--force` for them. |
| `--model NAME` | The user names a model, or `references/models.md` says so for a special case. Default `auto`. |
| `--format webp` | The user wants smaller files (still lossless). Default PNG. |
| `--shop` | Photos for a shop or marketplace: `--background white --square --pad 7.5 --size 2000 --format jpg`; any of them can be overridden. |
| `--background COLOUR` | `white`, `black` or `#RRGGBB` behind the product. Needed for `--format jpg`. The hex is painted in the photo's own colour profile: on wide-gamut (Display P3) photos a brand colour can look slightly off; white and black are exact. |
| `--square` · `--pad PCT` · `--size PX` | Square canvas around the subject · margin on each side, % of the canvas · longest side in pixels. |
| `--clean-edges` | A coloured halo from the old background shows on hair or fur (green screen, strong colours). Recolours semi-transparent edge pixels only. |
| `--fast-edges` | Speed matters more than hair and fur detail. Never when the user asked for the best quality. |
| `-o DIR` | The user wants the output somewhere specific. |

The subject's pixels are copied bit for bit (except with `--clean-edges`, and
only on edge pixels), and the output keeps the input's exact size. Finishing
is the opt-in exception: `--background` blends only the edge pixels, `--square`
and `--pad` crop, `--size` resamples, and JPEG is lossy (quality 95). No model
keeps clear glass see-through; warn the user if the photos are glassware on a
coloured background.

## Checking the machine

`bulk-bg-remove doctor --json` prints one object: `ready`, `checks` (HEIC,
AVIF, WebP support, free RAM), `auto` (the model `auto` would pick right now
for each profile) and `models` (each model's RAM need and whether it fits).
It exits 2 when not ready; `checks` says why. A missing HEIC or AVIF check only
matters if the folder has those files.

## For people, not agents

If the user would rather drag and drop photos themselves, tell them to run
`bulk-bg-remove` with no arguments: it opens a local page in the browser where
they can drop files or folders or paste images. Output lands in
`~/Downloads/<name>-no-bg/`.

## References

- `references/models.md`: every model's scores by kind of image, speed and RAM.
- `references/reviewer.md`: how to review the cut-outs, the defects and their fixes.
- `references/json-events.md`: every `--json` event and its fields.
- `references/troubleshooting.md`: install, model download, RAM and format problems.