Buy the right paint cans across a whole project — after openings, coats, waste, and compatible leftovers are accounted for.
CoatCart is a zero-dependency, offline command-line planner. It does more than divide area by coverage: for each finish, it searches the available can sizes and prices and returns the exact lowest-cost purchase, then breaks ties by least leftover paint and fewest cans.
The result is real, reusable data: a machine-readable report, a printable HTML plan, and a shopping-list CSV. View the generated demo report or read this page in 简体中文.
Paint calculators usually stop at “you need 8.2 litres.” The store sells discrete cans, different finishes cannot be mixed, and you may already own usable leftovers. Rounding each room independently or greedily buying the largest can can cost more than necessary.
CoatCart plans all finishes in one input while keeping their inventories separate:
surfaces - openings -> coats + waste -> compatible stock -> exact can optimizer
|
v
JSON + HTML + shopping CSV
It does not recommend colors, estimate labor, call a manufacturer API, or upload project data.
Requires Node.js 20 or newer.
git clone https://github.com/KanadeK/coatcart.git
cd coatcart
npm ci
npm run demoOpen docs/index.html, or inspect the three files in docs/demo/.
To install the released package directly from GitHub:
npm install --global https://github.com/KanadeK/coatcart/releases/download/v0.1.0/coatcart-0.1.0.tgz
coatcart demo --out coatcart-outputCopy examples/whole-home.json, then edit the finishes, areas, stock, and cans:
{
"project": "Upstairs refresh",
"measurement": "metric",
"currency": "USD",
"finishes": [
{
"id": "bedroom-walls",
"label": "Bedroom matte walls",
"coverage_per_volume": 10,
"coats": 2,
"waste_percent": 8,
"surfaces": [
{ "label": "Long walls", "width": 4.8, "height": 2.6, "count": 2 }
],
"openings": [
{ "label": "Door", "width": 0.9, "height": 2.1 }
],
"stock": [
{ "label": "Sealed leftover", "amount": 0.75 }
],
"packages": [
{ "label": "1 L can", "amount": 1, "price": 24 },
{ "label": "2.5 L can", "amount": 2.5, "price": 52 },
{ "label": "5 L can", "amount": 5, "price": 94 }
]
}
]
}Validate before planning:
coatcart validate upstairs.json
coatcart plan upstairs.json --out upstairs-planThe bundled whole-home example produces this purchase:
| Finish | Existing stock | Buy | Cost | Left after project |
|---|---|---|---|---|
| Sage matte walls | 0.75 L | 1 × 1 L, 1 × 2.5 L, 1 × 5 L | USD 170.00 | 0.275 L |
| Flat white ceiling | 0.4 L | 1 × 0.75 L, 1 × 2.5 L | USD 67.00 | 0.58 L |
| Cream satin trim | 1.75 L | Nothing | USD 0.00 | 0.21 L |
Total purchase cost: USD 237.00.
| Command | Purpose |
|---|---|
coatcart validate <input.json> |
Validate measurements and purchase data without writing reports. |
coatcart plan <input.json> --out <directory> |
Optimize every finish and write all report formats. |
coatcart demo --out <directory> |
Run the packaged whole-home example. |
coatcart --help |
Show the command contract. |
coatcart --version |
Show the package version. |
| Exit | Meaning |
|---|---|
0 |
The command succeeded and the plan is feasible. |
2 |
The command or JSON input is invalid; the error names the field or file to repair. |
3 |
The input is valid, but at least one finish cannot be fulfilled. Reports are still written. |
node bin/coatcart.js plan examples/stock-only-impossible.json --out coatcart-output/incompleteThis intentionally exits 3 and reports a 5.05 L shortfall because purchasing is disabled. Repair the source by doing one truthful thing: add enough compatible stock, set allow_purchase to true and supply real package options, or correct an inaccurate measurement. Then rerun the same command. CoatCart never silently changes coats, waste, or finish compatibility to force a green result.
See Troubleshooting for validation, permissions, install, CI, and release failures.
These are the same gates used by CI:
npm ci
npm test
npm run check
npm run accept:packagenpm run check performs syntax checks, runs all tests, regenerates the demo, proves the intentional exit-3 path, and inspects the package contents. npm run accept:package packs CoatCart, installs that tarball into a clean temporary prefix, and runs the installed demo.
Volume is converted to thousandths and money to cents. An unbounded dynamic program explores every reachable purchase volume through one less than the largest can beyond the deficit. Any solution farther away is dominated: removing one can still covers the deficit and cannot increase cost. Candidate plans are compared in the documented order: cost, leftover, can count.
Read Architecture, Input format, the design decision, and the documented differentiation search.
- One measurement system per project: metric (
m,m²,L) or imperial (ft,ft²,gal). - Stock is compatible only within its finish object.
- Volume precision is 0.001 unit and price precision is 0.01 currency unit.
- Prices are user-supplied whole-can prices; taxes, promotions, and labor are out of scope.
- No interactive editor or product catalog. The HTML is a generated evidence report, not a shell around future functionality.
Read CONTRIBUTING.md before changing the input or output contract. Please report vulnerabilities through the private route in SECURITY.md, not a public issue.
CoatCart is available under the MIT License.